mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-27 03:41:11 +01:00
A bunch more theme stuff
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
.cm-cursor {
|
||||
@apply border-fg !important;
|
||||
/* Widen the cursor */
|
||||
@apply border-l-2;
|
||||
@apply border-l-[2px];
|
||||
}
|
||||
|
||||
&.cm-focused {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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',
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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[]),
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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]',
|
||||
|
||||
Reference in New Issue
Block a user