A bunch more theme stuff

This commit is contained in:
Gregory Schier
2024-05-22 23:14:53 -07:00
parent 83aaeb94f6
commit 8e662e6feb
61 changed files with 8374 additions and 530 deletions

View File

@@ -4,7 +4,7 @@ import type { ReactNode } from 'react';
interface Props {
children: ReactNode;
className?: string;
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
color?: 'primary' | 'secondary' | 'success' | 'notice' | 'warning' | 'danger';
}
export function Banner({ children, className, color = 'secondary' }: Props) {

View File

@@ -14,11 +14,12 @@ export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color'> & {
| 'primary'
| 'info'
| 'success'
| 'notice'
| 'warning'
| 'danger';
variant?: 'border' | 'solid';
isLoading?: boolean;
size?: 'xs' | 'sm' | 'md';
size?: '2xs' | 'xs' | 'sm' | 'md';
justify?: 'start' | 'center';
type?: 'button' | 'submit';
forDropdown?: boolean;
@@ -60,24 +61,27 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
`x-theme-button--${variant}`,
`x-theme-button--${variant}--${color}`,
'text-fg',
'border', // They all have borders to ensure the same width
'max-w-full min-w-0', // Help with truncation
'hocus:opacity-100', // Force opacity for certain hover effects
'whitespace-nowrap outline-none',
'flex-shrink-0 flex items-center',
'focus-visible-or-class:ring rounded-md',
'focus-visible-or-class:ring',
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
justify === 'start' && 'justify-start',
justify === 'center' && 'justify-center',
size === 'md' && 'h-md px-3',
size === 'sm' && 'h-sm px-2.5 text-sm',
size === 'xs' && 'h-xs px-2 text-sm',
size === 'md' && 'h-md px-3 rounded-md',
size === 'sm' && 'h-sm px-2.5 text-sm rounded-md',
size === 'xs' && 'h-xs px-2 text-sm rounded-md',
size === '2xs' && 'h-5 px-1 text-xs rounded',
// Solids
variant === 'solid' && 'border-transparent',
variant === 'solid' && color === 'custom' && 'ring-blue-400',
variant === 'solid' &&
color !== 'custom' &&
color !== 'default' &&
'bg-background enabled:hocus:bg-background-highlight ring-background-highlight-secondary',
variant === 'solid' && color === 'custom' && 'ring-blue-400',
variant === 'solid' &&
color === 'default' &&
'enabled:hocus:bg-background-highlight ring-fg-info',

View File

@@ -30,7 +30,7 @@ export function Checkbox({
alignItems="center"
className={classNames(className, 'text-fg text-sm', disabled && 'opacity-disabled')}
>
<div className={classNames(inputWrapperClassName, 'relative flex')}>
<div className={classNames(inputWrapperClassName, 'x-theme-input', 'relative flex')}>
<input
aria-hidden
className={classNames(

View File

@@ -94,7 +94,7 @@ export function Dialog({
className={classNames(
'h-full w-full grid grid-cols-[minmax(0,1fr)]',
!noPadding && 'px-6 py-2',
!noScroll && 'overflow-y-auto',
!noScroll && 'overflow-y-auto overflow-x-hidden',
)}
>
{children}

View File

@@ -413,7 +413,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
)}
{isOpen && (
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}>
<div className="x-theme-dialog">
<div className="x-theme-menu">
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={handleClose} />
<motion.div
tabIndex={0}

View File

@@ -7,7 +7,7 @@
.cm-cursor {
@apply border-fg !important;
/* Widen the cursor */
@apply border-l-2;
@apply border-l-[2px];
}
&.cm-focused {

View File

@@ -283,7 +283,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
}
return (
<div className="group relative h-full w-full">
<div className="group relative h-full w-full x-theme-editor bg-background">
{cmContainer}
{decoratedActions && (
<HStack

View File

@@ -46,45 +46,23 @@ export const myHighlightStyle = HighlightStyle.define([
fontStyle: 'italic',
},
{
tag: [t.paren],
tag: [t.paren, t.bracket, t.brace],
color: 'var(--fg)',
},
{
tag: [t.name, t.tagName, t.angleBracket, t.docString, t.number],
tag: [t.link, t.name, t.tagName, t.angleBracket, t.docString, t.number],
color: 'var(--fg-info)',
},
{ tag: [t.variableName], color: 'var(--fg-success)' },
{ tag: [t.bool], color: 'var(--fg-info)' }, // TODO: Should be pink
{ tag: [t.bool], color: 'var(--fg-warning)' },
{ tag: [t.attributeName, t.propertyName], color: 'var(--fg-primary)' },
{ tag: [t.attributeValue], color: 'var(--fg-warning)' },
{ tag: [t.string], color: 'var(--fg-warning)' }, // TODO: Should be yellow
{ tag: [t.keyword, t.meta, t.operator], color: 'var(--fg-danger)' },
{ tag: [t.string], color: 'var(--fg-notice)' },
{ tag: [t.atom, t.meta, t.operator, t.bool, t.null, t.keyword], color: 'var(--fg-danger)' },
]);
const myTheme = EditorView.theme({}, { dark: true });
// export const defaultHighlightStyle = HighlightStyle.define([
// { tag: t.meta, color: '#404740' },
// { tag: t.link, textDecoration: 'underline' },
// { tag: t.heading, textDecoration: 'underline', fontWeight: 'bold' },
// { tag: t.emphasis, fontStyle: 'italic' },
// { tag: t.strong, fontWeight: 'bold' },
// { tag: t.strikethrough, textDecoration: 'line-through' },
// { tag: t.keyword, color: '#708' },
// { tag: [t.atom, t.bool, t.url, t.contentSeparator, t.labelName], color: '#219' },
// { tag: [t.literal, t.inserted], color: '#164' },
// { tag: [t.string, t.deleted], color: '#a11' },
// { tag: [t.regexp, t.escape, t.special(t.string)], color: '#e40' },
// { tag: t.definition(t.variableName), color: '#00f' },
// { tag: t.local(t.variableName), color: '#30a' },
// { tag: [t.typeName, t.namespace], color: '#085' },
// { tag: t.className, color: '#167' },
// { tag: [t.special(t.variableName), t.macroName], color: '#256' },
// { tag: t.definition(t.propertyName), color: '#00c' },
// { tag: t.comment, color: '#940' },
// { tag: t.invalid, color: '#f00' },
// ]);
const syntaxExtensions: Record<string, LanguageSupport> = {
'application/graphql': graphqlLanguageSupport(),
'application/json': json(),

View File

@@ -66,7 +66,7 @@ const icons = {
export interface IconProps {
icon: keyof typeof icons;
className?: string;
size?: 'xs' | 'sm' | 'md' | 'lg';
size?: '2xs' | 'xs' | 'sm' | 'md' | 'lg';
spin?: boolean;
title?: string;
}
@@ -83,6 +83,7 @@ export const Icon = memo(function Icon({ icon, spin, size = 'md', className, tit
size === 'md' && 'h-4 w-4',
size === 'sm' && 'h-3.5 w-3.5',
size === 'xs' && 'h-3 w-3',
size === '2xs' && 'h-2.5 w-2.5',
spin && 'animate-spin',
)}
/>

View File

@@ -57,6 +57,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
size === 'md' && 'w-9',
size === 'sm' && 'w-8',
size === 'xs' && 'w-6',
size === '2xs' && 'w-5',
)}
{...props}
>

View File

@@ -6,8 +6,8 @@ export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanEleme
<code
className={classNames(
className,
'font-mono text-xs bg-background-highlight-secondary',
'px-1.5 py-0.5 rounded text-fg shadow-inner',
'font-mono text-xs bg-background-highlight-secondary border border-background-highlight',
'px-1.5 py-0.5 rounded text-fg-info shadow-inner',
)}
{...props}
/>

View File

@@ -145,6 +145,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
alignItems="stretch"
className={classNames(
containerClassName,
'x-theme-input',
'relative w-full rounded-md text-fg',
'border',
focused ? 'border-border-focus' : 'border-background-highlight',

View File

@@ -1,3 +1,4 @@
import type { ReactNode } from 'react';
import { useMemo } from 'react';
import type { DropdownItem, DropdownItemSeparator, DropdownProps } from './Dropdown';
import { Dropdown } from './Dropdown';
@@ -9,6 +10,7 @@ export type RadioDropdownItem<T = string | null> =
label: string;
shortLabel?: string;
value: T;
rightSlot?: ReactNode;
}
| DropdownItemSeparator;
@@ -37,9 +39,10 @@ export function RadioDropdown<T = string | null>({
key: item.label,
label: item.label,
shortLabel: item.shortLabel,
rightSlot: item.rightSlot,
onSelect: () => onChange(item.value),
leftSlot: <Icon icon={value === item.value ? 'check' : 'empty'} />,
};
} as DropdownProps['items'][0];
}
}),
...((extraItems ? [{ type: 'separator' }, ...extraItems] : []) as DropdownItem[]),

View File

@@ -7,12 +7,17 @@ interface Props<T extends string> {
labelClassName?: string;
hideLabel?: boolean;
value: T;
options: { label: string; value: T }[];
options: SelectOption<T>[];
onChange: (value: T) => void;
size?: 'xs' | 'sm' | 'md' | 'lg';
className?: string;
}
export interface SelectOption<T extends string> {
label: string;
value: T;
}
export function Select<T extends string>({
labelPosition = 'top',
name,
@@ -30,6 +35,7 @@ export function Select<T extends string>({
<div
className={classNames(
className,
'x-theme-input',
'w-full',
'pointer-events-auto', // Just in case we're placing in disabled parent
labelPosition === 'left' && 'flex items-center gap-2',

View File

@@ -1,7 +1,6 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
import { memo, useCallback, useEffect, useRef } from 'react';
import { Button } from '../Button';
import { Icon } from '../Icon';
import type { RadioDropdownProps } from '../RadioDropdown';
import { RadioDropdown } from '../RadioDropdown';
@@ -25,6 +24,7 @@ interface Props {
tabListClassName?: string;
className?: string;
children: ReactNode;
addBorders?: boolean;
}
export function Tabs({
@@ -35,6 +35,7 @@ export function Tabs({
tabs,
className,
tabListClassName,
addBorders,
}: Props) {
const ref = useRef<HTMLDivElement | null>(null);
@@ -78,12 +79,15 @@ export function Tabs({
'-ml-5 pl-3 pr-1 py-1',
)}
>
<HStack space={2} className="flex-shrink-0">
<HStack space={2} className="h-full flex-shrink-0">
{tabs.map((t) => {
const isActive = t.value === value;
const btnClassName = classNames(
isActive ? 'text-fg' : 'text-fg-subtler hover:text-fg-subtle',
'h-full flex items-center text-sm rounded',
'!px-2 ml-[1px]',
addBorders && 'border',
isActive ? 'text-fg' : 'text-fg-subtler hover:text-fg-subtle',
isActive && addBorders ? 'border-background-highlight' : 'border-transparent',
);
if ('options' in t) {
@@ -97,39 +101,34 @@ export function Tabs({
value={t.options.value}
onChange={t.options.onChange}
>
<Button
<button
color="custom"
size="sm"
onClick={isActive ? undefined : () => handleTabChange(t.value)}
className={btnClassName}
rightSlot={
<Icon
size="sm"
icon="chevronDown"
className={classNames(
'-mr-1.5 mt-0.5',
isActive ? 'text-fg-subtle' : 'opacity-50',
)}
/>
}
>
{option && 'shortLabel' in option
? option.shortLabel
: option?.label ?? 'Unknown'}
</Button>
<TabAccent enabled isActive={isActive} />
<Icon
size="sm"
icon="chevronDown"
className={classNames('ml-1', isActive ? 'text-fg-subtle' : 'opacity-50')}
/>
</button>
</RadioDropdown>
);
} else {
return (
<Button
<button
key={t.value}
color="custom"
size="sm"
onClick={() => handleTabChange(t.value)}
className={btnClassName}
>
{t.label}
</Button>
<TabAccent enabled isActive={isActive} />
</button>
);
}
})}
@@ -161,3 +160,14 @@ export const TabContent = memo(function TabContent({
</div>
);
});
function TabAccent({ isActive, enabled }: { isActive: boolean; enabled: boolean }) {
return (
<div
className={classNames(
'w-full opacity-40 border-b-2',
isActive && enabled ? 'border-b-background-highlight' : 'border-b-transparent',
)}
/>
);
}

View File

@@ -52,11 +52,11 @@ export function Toast({
transition={{ duration: 0.2 }}
className={classNames(
className,
'x-theme-dialog',
'x-theme-toast',
'pointer-events-auto',
'relative bg-background pointer-events-auto',
'rounded-lg',
'border border-background-highlight dark:border-background-highlight-secondary shadow-xl',
'border border-background-highlight shadow-lg',
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
'w-[22rem] max-h-[80vh]',
'm-2 grid grid-cols-[1fr_auto]',