Theme system refactor (#31)

This commit is contained in:
Gregory Schier
2024-05-21 17:56:06 -07:00
committed by GitHub
parent 8606940dee
commit 83aaeb94f6
82 changed files with 909 additions and 739 deletions

View File

@@ -4,19 +4,18 @@ import type { ReactNode } from 'react';
interface Props {
children: ReactNode;
className?: string;
color?: 'danger' | 'warning' | 'success' | 'gray';
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
}
export function Banner({ children, className, color = 'gray' }: Props) {
export function Banner({ children, className, color = 'secondary' }: Props) {
return (
<div>
<div
className={classNames(
className,
`x-theme-banner--${color}`,
'border border-dashed italic px-3 py-2 rounded select-auto cursor-text',
color === 'gray' && 'border-gray-500/60 bg-gray-300/10 text-gray-800',
color === 'warning' && 'border-orange-500/60 bg-orange-300/10 text-orange-800',
color === 'danger' && 'border-red-500/60 bg-red-300/10 text-red-800',
color === 'success' && 'border-violet-500/60 bg-violet-300/10 text-violet-800',
'border-background-highlight bg-background-highlight-secondary text-fg',
)}
>
{children}

View File

@@ -7,7 +7,15 @@ import { Icon } from './Icon';
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color'> & {
innerClassName?: string;
color?: 'custom' | 'default' | 'gray' | 'primary' | 'secondary' | 'warning' | 'danger';
color?:
| 'custom'
| 'default'
| 'secondary'
| 'primary'
| 'info'
| 'success'
| 'warning'
| 'danger';
variant?: 'border' | 'solid';
isLoading?: boolean;
size?: 'xs' | 'sm' | 'md';
@@ -48,6 +56,10 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
const classes = classNames(
className,
'x-theme-button',
`x-theme-button--${variant}`,
`x-theme-button--${variant}--${color}`,
'text-fg',
'max-w-full min-w-0', // Help with truncation
'hocus:opacity-100', // Force opacity for certain hover effects
'whitespace-nowrap outline-none',
@@ -59,46 +71,26 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
size === 'md' && 'h-md px-3',
size === 'sm' && 'h-sm px-2.5 text-sm',
size === 'xs' && 'h-xs px-2 text-sm',
// Solids
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' &&
'text-gray-700 enabled:hocus:bg-gray-700/10 enabled:hocus:text-gray-800 ring-blue-400',
variant === 'solid' &&
color === 'gray' &&
'text-gray-800 bg-gray-200/70 enabled:hocus:bg-gray-200 ring-blue-400',
variant === 'solid' &&
color === 'primary' &&
'bg-blue-400 text-white ring-blue-700 enabled:hocus:bg-blue-500',
variant === 'solid' &&
color === 'secondary' &&
'bg-violet-400 text-white ring-violet-700 enabled:hocus:bg-violet-500',
variant === 'solid' &&
color === 'warning' &&
'bg-orange-400 text-white ring-orange-700 enabled:hocus:bg-orange-500',
variant === 'solid' &&
color === 'danger' &&
'bg-red-400 text-white ring-red-700 enabled:hocus:bg-red-500',
'enabled:hocus:bg-background-highlight ring-fg-info',
// Borders
variant === 'border' && 'border',
variant === 'border' &&
color !== 'custom' &&
color !== 'default' &&
'border-fg-subtler text-fg-subtle enabled:hocus:border-fg-subtle enabled:hocus:bg-background-highlight enabled:hocus:text-fg ring-fg-subtler',
variant === 'border' &&
color === 'default' &&
'border-highlight text-gray-700 enabled:hocus:border-focus enabled:hocus:text-gray-800 ring-blue-500/50',
variant === 'border' &&
color === 'gray' &&
'border-gray-500/70 text-gray-700 enabled:hocus:bg-gray-500/20 enabled:hocus:text-gray-800 ring-blue-500/50',
variant === 'border' &&
color === 'primary' &&
'border-blue-500/70 text-blue-700 enabled:hocus:border-blue-500 ring-blue-500/50',
variant === 'border' &&
color === 'secondary' &&
'border-violet-500/70 text-violet-700 enabled:hocus:border-violet-500 ring-violet-500/50',
variant === 'border' &&
color === 'warning' &&
'border-orange-500/70 text-orange-700 enabled:hocus:border-orange-500 ring-orange-500/50',
variant === 'border' &&
color === 'danger' &&
'border-red-500/70 text-red-700 enabled:hocus:border-red-500 ring-red-500/50',
'border-background-highlight enabled:hocus:border-fg-subtler enabled:hocus:bg-background-highlight-secondary',
);
const buttonRef = useRef<HTMLButtonElement>(null);

View File

@@ -28,14 +28,14 @@ export function Checkbox({
as="label"
space={2}
alignItems="center"
className={classNames(className, 'text-gray-900 text-sm', disabled && 'opacity-disabled')}
className={classNames(className, 'text-fg text-sm', disabled && 'opacity-disabled')}
>
<div className={classNames(inputWrapperClassName, 'relative flex')}>
<input
aria-hidden
className={classNames(
'appearance-none w-4 h-4 flex-shrink-0 border border-highlight',
'rounded hocus:border-focus hocus:bg-focus/[5%] outline-none ring-0',
'appearance-none w-4 h-4 flex-shrink-0 border border-background-highlight',
'rounded hocus:border-border-focus hocus:bg-focus/[5%] outline-none ring-0',
)}
type="checkbox"
disabled={disabled}

View File

@@ -12,7 +12,7 @@ export function CountBadge({ count, className }: Props) {
aria-hidden
className={classNames(
className,
'opacity-70 border border-highlight text-4xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
'opacity-70 border border-background-highlight-secondary text-4xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
)}
>
{count}

View File

@@ -50,7 +50,7 @@ export function Dialog({
return (
<Overlay open={open} onClose={onClose} portalName="dialog">
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className="x-theme-dialog absolute inset-0 flex items-center justify-center pointer-events-none">
<div
role="dialog"
aria-labelledby={titleId}
@@ -63,9 +63,9 @@ export function Dialog({
className={classNames(
className,
'grid grid-rows-[auto_auto_minmax(0,1fr)]',
'relative bg-gray-50 pointer-events-auto',
'relative bg-background pointer-events-auto',
'rounded-lg',
'dark:border border-highlight shadow shadow-black/10',
'border border-background-highlight shadow-lg shadow-[rgba(0,0,0,0.1)]',
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
size === 'sm' && 'w-[25rem] max-h-[80vh]',
size === 'md' && 'w-[45rem] max-h-[80vh]',
@@ -83,7 +83,7 @@ export function Dialog({
)}
{description ? (
<p className="px-6 text-gray-700" id={descriptionId}>
<p className="px-6 text-fg-subtle" id={descriptionId}>
{description}
</p>
) : (

View File

@@ -23,14 +23,14 @@ import React, {
import { useKey, useWindowSize } from 'react-use';
import type { HotkeyAction } from '../../hooks/useHotKey';
import { useHotKey } from '../../hooks/useHotKey';
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import { getNodeText } from '../../lib/getNodeText';
import { Overlay } from '../Overlay';
import { Button } from './Button';
import { HotKey } from './HotKey';
import { Icon } from './Icon';
import { Separator } from './Separator';
import { HStack, VStack } from './Stacks';
import { Icon } from './Icon';
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
export type DropdownItemSeparator = {
type: 'separator';
@@ -413,7 +413,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
)}
{isOpen && (
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}>
<div>
<div className="x-theme-dialog">
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={handleClose} />
<motion.div
tabIndex={0}
@@ -431,7 +431,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
<span
aria-hidden
style={triangleStyles}
className="bg-gray-50 absolute rotate-45 border-gray-200 border-t border-l"
className="bg-background absolute rotate-45 border-background-highlight border-t border-l"
/>
)}
{containerStyles && (
@@ -440,22 +440,24 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
style={menuStyles}
className={classNames(
className,
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
'border-gray-200 overflow-auto mb-1 mx-0.5',
'h-auto bg-background rounded-md shadow-lg py-1.5 border',
'border-background-highlight overflow-auto mb-1 mx-0.5',
)}
>
{filter && (
<HStack
space={2}
alignItems="center"
className="pb-0.5 px-1.5 mb-2 text-xs border border-highlight mx-2 rounded font-mono h-2xs"
className="pb-0.5 px-1.5 mb-2 text-xs border border-background-highlight-secondary mx-2 rounded font-mono h-2xs"
>
<Icon icon="search" size="xs" className="text-gray-700" />
<div className="text-gray-800">{filter}</div>
<Icon icon="search" size="xs" className="text-fg-subtle" />
<div className="text-fg">{filter}</div>
</HStack>
)}
{filteredItems.length === 0 && (
<span className="text-gray-500 text-sm text-center px-2 py-1">No matches</span>
<span className="text-fg-subtler text-sm text-center px-2 py-1">
No matches
</span>
)}
{filteredItems.map((item, i) => {
if (item.type === 'separator') {
@@ -531,15 +533,17 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
justify="start"
leftSlot={item.leftSlot && <div className="pr-2 flex justify-start">{item.leftSlot}</div>}
rightSlot={rightSlot && <div className="ml-auto pl-3">{rightSlot}</div>}
innerClassName="!text-left"
color="custom"
className={classNames(
className,
'h-xs', // More compact
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm text-gray-700 whitespace-nowrap',
'focus:bg-highlight focus:text-gray-800 rounded',
item.variant === 'danger' && 'text-red-600',
item.variant === 'notify' && 'text-pink-600',
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm whitespace-nowrap',
'focus:bg-background-highlight focus:text-fg rounded',
item.variant === 'default' && 'text-fg-subtle',
item.variant === 'danger' && 'text-fg-danger',
item.variant === 'notify' && 'text-fg-primary',
)}
innerClassName="!text-left"
{...props}
>
<div

View File

@@ -5,7 +5,10 @@ interface Props {
export function DurationTag({ total, headers }: Props) {
return (
<span title={`HEADER: ${formatMillis(headers)}\nTOTAL: ${formatMillis(total)}`}>
<span
className="font-mono"
title={`HEADER: ${formatMillis(headers)}\nTOTAL: ${formatMillis(total)}`}
>
{formatMillis(total)}
</span>
);

View File

@@ -5,7 +5,9 @@
@apply w-full block text-base;
.cm-cursor {
@apply border-gray-800 !important;
@apply border-fg !important;
/* Widen the cursor */
@apply border-l-2;
}
&.cm-focused {
@@ -17,7 +19,7 @@
}
.cm-line {
@apply text-gray-800 pl-1 pr-1.5;
@apply text-fg pl-1 pr-1.5;
}
.cm-placeholder {
@@ -47,7 +49,7 @@
/* Style gutters */
.cm-gutters {
@apply border-0 text-gray-500/50;
@apply border-0 text-fg-subtler bg-transparent;
.cm-gutterElement {
@apply cursor-default;
@@ -55,19 +57,13 @@
}
.placeholder-widget {
@apply text-xs text-violet-700 dark:text-violet-700 px-1 mx-[0.5px] rounded cursor-default dark:shadow;
/* Colors */
@apply bg-background text-fg-subtle border-background-highlight-secondary;
@apply hover:border-background-highlight hover:text-fg hover:bg-background-highlight-secondary;
/* NOTE: Background and border are translucent so we can see text selection through it */
@apply bg-violet-500/20 border border-violet-500/20 border-opacity-40;
/* Bring above on hover */
@apply hover:z-10 relative;
@apply border px-1 mx-[0.5px] rounded cursor-default dark:shadow;
-webkit-text-security: none;
&.placeholder-widget-error {
@apply text-red-700 dark:text-red-800 bg-red-300/30 border-red-300/80 border-opacity-40 hover:border-red-300 hover:bg-red-300/40;
}
}
.hyperlink-widget {
@@ -137,7 +133,7 @@
.cm-editor .fold-gutter-icon::after {
@apply block w-1.5 h-1.5 border-transparent -rotate-45
border-l border-b border-l-[currentColor] border-b-[currentColor] content-[''];
border-l border-b border-l-[currentColor] border-b-[currentColor] content-[''];
}
.cm-editor .fold-gutter-icon[data-open] {
@@ -149,12 +145,12 @@
}
.cm-editor .fold-gutter-icon:hover {
@apply text-gray-900 bg-gray-300/50;
@apply text-fg bg-background-highlight;
}
.cm-editor .cm-foldPlaceholder {
@apply px-2 border border-gray-400/50 bg-gray-300/50;
@apply hover:text-gray-800 hover:border-gray-400;
@apply px-2 border border-fg-subtler bg-background-highlight;
@apply hover:text-fg hover:border-fg-subtle;
@apply cursor-default !important;
}
@@ -164,11 +160,13 @@
.cm-wrapper:not(.cm-readonly) .cm-editor {
&.cm-focused .cm-activeLineGutter {
@apply text-gray-600;
@apply text-fg-subtle;
}
}
.cm-wrapper.cm-readonly .cm-editor {
.cm-cursor {
@apply border-l-2 border-gray-800;
@apply border-fg-danger !important;
}
}
@@ -187,18 +185,18 @@
}
.cm-tooltip.cm-tooltip-hover {
@apply shadow-lg bg-gray-100 rounded text-gray-700 border border-gray-500 z-50 pointer-events-auto text-xs;
@apply shadow-lg bg-background rounded text-fg-subtle border border-fg-subtler z-50 pointer-events-auto text-xs;
@apply px-2 py-1;
a {
@apply text-gray-800;
@apply text-fg;
&:hover {
@apply underline;
}
&::after {
@apply text-gray-800 bg-gray-800 h-3 w-3 ml-1;
@apply text-fg bg-fg-secondary h-3 w-3 ml-1;
content: '';
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='black' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
-webkit-mask-size: contain;
@@ -210,7 +208,7 @@
/* NOTE: Extra selector required to override default styles */
.cm-tooltip.cm-tooltip-autocomplete,
.cm-tooltip.cm-completionInfo {
@apply shadow-lg bg-gray-100 rounded text-gray-700 border border-gray-300 z-50 pointer-events-auto text-xs;
@apply shadow-lg bg-background rounded text-fg-subtle border border-background-highlight z-50 pointer-events-auto text-xs;
.cm-completionIcon {
@apply italic font-mono;
@@ -286,11 +284,11 @@
}
& > ul > li {
@apply cursor-default px-2 rounded-sm text-gray-600 h-7 flex items-center;
@apply cursor-default px-2 rounded-sm text-fg-subtle h-7 flex items-center;
}
& > ul > li[aria-selected] {
@apply bg-highlight text-gray-900;
@apply bg-background-highlight-secondary text-fg;
}
.cm-completionIcon {
@@ -298,7 +296,7 @@
}
.cm-completionLabel {
@apply text-gray-700;
@apply text-fg-subtle;
}
.cm-completionDetail {
@@ -308,7 +306,7 @@
}
.cm-editor .cm-panels {
@apply bg-gray-100 backdrop-blur-sm p-1 mb-1 text-gray-800 z-20 rounded-md;
@apply bg-background-highlight-secondary backdrop-blur-sm p-1 mb-1 text-fg z-20 rounded-md;
input,
button {
@@ -316,19 +314,21 @@
}
button {
@apply appearance-none bg-none bg-gray-200 hocus:bg-gray-300 hocus:text-gray-950 border-0 text-gray-800 cursor-default;
@apply border-fg-subtler bg-background-highlight text-fg hover:border-fg-info;
@apply appearance-none bg-none cursor-default;
}
button[name='close'] {
@apply text-gray-600 hocus:text-gray-900 px-2 -mr-1.5 !important;
@apply text-fg-subtle hocus:text-fg px-2 -mr-1.5 !important;
}
input {
@apply bg-gray-50 border border-gray-500/50 focus:border-focus outline-none;
@apply bg-background border-background-highlight focus:border-border-focus;
@apply border outline-none cursor-text;
}
label {
@apply focus-within:text-gray-950;
@apply focus-within:text-fg;
}
/* Hide the "All" button */

View File

@@ -206,7 +206,6 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
view = new EditorView({ state, parent: container });
cm.current = { view, languageCompartment };
syncGutterBg({ parent: container, bgClassList });
if (autoFocus) {
view.focus();
}
@@ -270,7 +269,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
ref={initEditorRef}
className={classNames(
className,
'cm-wrapper text-base bg-gray-50',
'cm-wrapper text-base',
type === 'password' && 'cm-obscure-text',
heightMode === 'auto' ? 'cm-auto-height' : 'cm-full-height',
singleLine ? 'cm-singleline' : 'cm-multiline',
@@ -361,19 +360,6 @@ function getExtensions({
];
}
const syncGutterBg = ({
parent,
bgClassList,
}: {
parent: HTMLDivElement;
bgClassList: string[];
}) => {
const gutterEl = parent.querySelector<HTMLDivElement>('.cm-gutters');
if (gutterEl) {
gutterEl?.classList.add(...bgClassList);
}
};
const placeholderElFromText = (text: string) => {
const el = document.createElement('div');
el.innerHTML = text.replace('\n', '<br/>');

View File

@@ -42,23 +42,23 @@ import { url } from './url/extension';
export const myHighlightStyle = HighlightStyle.define([
{
tag: [t.documentMeta, t.blockComment, t.lineComment, t.docComment, t.comment],
color: 'hsl(var(--color-gray-600))',
color: 'var(--fg-subtler)',
fontStyle: 'italic',
},
{
tag: [t.paren],
color: 'hsl(var(--color-gray-900))',
color: 'var(--fg)',
},
{
tag: [t.name, t.tagName, t.angleBracket, t.docString, t.number],
color: 'hsl(var(--color-blue-600))',
color: 'var(--fg-info)',
},
{ tag: [t.variableName], color: 'hsl(var(--color-green-600))' },
{ tag: [t.bool], color: 'hsl(var(--color-pink-600))' },
{ tag: [t.attributeName, t.propertyName], color: 'hsl(var(--color-violet-600))' },
{ tag: [t.attributeValue], color: 'hsl(var(--color-orange-600))' },
{ tag: [t.string], color: 'hsl(var(--color-yellow-600))' },
{ tag: [t.keyword, t.meta, t.operator], color: 'hsl(var(--color-red-600))' },
{ tag: [t.variableName], color: 'var(--fg-success)' },
{ tag: [t.bool], color: 'var(--fg-info)' }, // TODO: Should be pink
{ 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)' },
]);
const myTheme = EditorView.theme({}, { dark: true });
@@ -123,6 +123,7 @@ export const baseExtensions = [
dropCursor(),
drawSelection(),
autocompletion({
tooltipClass: () => 'x-theme-dialog',
closeOnBlur: true, // Set to `false` for debugging in devtools without closing it
compareCompletions: (a, b) => {
// Don't sort completions at all, only on boost

View File

@@ -11,8 +11,10 @@ class PlaceholderWidget extends WidgetType {
}
toDOM() {
const elt = document.createElement('span');
elt.className = `placeholder-widget ${
!this.isExistingVariable ? 'placeholder-widget-error' : ''
elt.className = `x-theme-placeholder-widget placeholder-widget ${
this.isExistingVariable
? 'x-theme-placeholder-widget--primary'
: 'x-theme-placeholder-widget--danger'
}`;
elt.title = !this.isExistingVariable ? 'Variable not found in active environment' : '';
elt.textContent = this.name;

View File

@@ -10,7 +10,7 @@ export function FormattedError({ children }: Props) {
<pre
className={classNames(
'w-full text-sm select-auto cursor-text bg-gray-100 p-3 rounded',
'whitespace-pre-wrap border border-red-500 border-dashed overflow-x-auto',
'whitespace-pre-wrap border border-fg-danger border-dashed overflow-x-auto',
)}
>
{children}

View File

@@ -11,7 +11,7 @@ export function Heading({ className, size = 1, ...props }: Props) {
<Component
className={classNames(
className,
'font-semibold text-gray-900',
'font-semibold text-fg',
size === 1 && 'text-2xl',
size === 2 && 'text-xl',
size === 3 && 'text-lg',

View File

@@ -22,7 +22,7 @@ export function HotKey({ action, className, variant }: Props) {
className={classNames(
className,
variant === 'with-bg' && 'rounded border',
'text-gray-1000 text-opacity-disabled',
'text-fg-subtler',
)}
>
{labelParts.map((char, index) => (

View File

@@ -7,5 +7,5 @@ interface Props {
export function HotKeyLabel({ action }: Props) {
const label = useHotKeyLabel(action);
return <span>{label}</span>;
return <span className="text-fg-subtle">{label}</span>;
}

View File

@@ -11,7 +11,7 @@ interface Props {
export const HotKeyList = ({ hotkeys, bottomSlot }: Props) => {
return (
<div className="h-full flex items-center justify-center text-gray-700 text-sm">
<div className="h-full flex items-center justify-center text-sm">
<VStack space={2}>
{hotkeys.map((hotkey) => (
<HStack key={hotkey} className="grid grid-cols-2">

View File

@@ -27,7 +27,7 @@ export function HttpMethodTag({ request, className }: Props) {
const m = method.toLowerCase();
return (
<span className={classNames(className, 'text-2xs font-mono opacity-50')}>
<span className={classNames(className, 'text-2xs font-mono text-fg-subtle')}>
{methodMap[m] ?? m.slice(0, 3).toUpperCase()}
</span>
);

View File

@@ -1,5 +1,5 @@
import * as lucide from 'lucide-react';
import classNames from 'classnames';
import * as lucide from 'lucide-react';
import type { HTMLAttributes } from 'react';
import { memo } from 'react';
@@ -44,6 +44,7 @@ const icons = {
moreVertical: lucide.MoreVerticalIcon,
paste: lucide.ClipboardPasteIcon,
pencil: lucide.PencilIcon,
pin: lucide.PinIcon,
plug: lucide.Plug,
plus: lucide.PlusIcon,
plusCircle: lucide.PlusCircleIcon,

View File

@@ -52,7 +52,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
size={size}
className={classNames(
className,
'relative flex-shrink-0 text-gray-700 hover:text-gray-1000',
'group/button relative flex-shrink-0 text-fg-subtle',
'!px-0',
size === 'md' && 'w-9',
size === 'sm' && 'w-8',
@@ -71,6 +71,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
spin={spin}
className={classNames(
iconClassName,
'group-hover/button:text-fg',
props.disabled && 'opacity-70',
confirmed && 'text-green-600',
)}

View File

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

View File

@@ -135,7 +135,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
htmlFor={id}
className={classNames(
labelClassName,
'text-sm text-gray-900 whitespace-nowrap',
'text-sm text-fg whitespace-nowrap',
hideLabel && 'sr-only',
)}
>
@@ -145,10 +145,10 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
alignItems="stretch"
className={classNames(
containerClassName,
'relative w-full rounded-md text-gray-900',
'relative w-full rounded-md text-fg',
'border',
focused ? 'border-focus' : 'border-highlight',
!isValid && '!border-invalid',
focused ? 'border-border-focus' : 'border-background-highlight',
!isValid && '!border-fg-danger',
size === 'md' && 'min-h-md',
size === 'sm' && 'min-h-sm',
size === 'xs' && 'min-h-xs',
@@ -186,7 +186,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
title={obscured ? `Show ${label}` : `Obscure ${label}`}
size="xs"
className="mr-0.5 group/obscure !h-auto my-0.5"
iconClassName="text-gray-500 group-hover/obscure:text-gray-800"
iconClassName="text-fg-subtle group-hover/obscure:text-fg"
iconSize="sm"
icon={obscured ? 'eye' : 'eyeClosed'}
onClick={() => setObscured((o) => !o)}

View File

@@ -41,7 +41,7 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
: null,
isExpandable: Object.keys(attrValue).length > 0,
label: isExpanded ? `{${Object.keys(attrValue).length || ' '}}` : `{⋯}`,
labelClassName: 'text-gray-600',
labelClassName: 'text-fg-subtler',
};
} else if (jsonType === '[object Array]') {
return {
@@ -59,7 +59,7 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
: null,
isExpandable: attrValue.length > 0,
label: isExpanded ? `[${attrValue.length || ' '}]` : `[⋯]`,
labelClassName: 'text-gray-600',
labelClassName: 'text-subtler',
};
} else {
return {
@@ -67,19 +67,17 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
isExpandable: false,
label: jsonType === '[object String]' ? `"${attrValue}"` : `${attrValue}`,
labelClassName: classNames(
jsonType === '[object Boolean]' && 'text-pink-600',
jsonType === '[object Number]' && 'text-blue-600',
jsonType === '[object String]' && 'text-yellow-600',
jsonType === '[object Null]' && 'text-red-600',
jsonType === '[object Boolean]' && 'text-fg-primary',
jsonType === '[object Number]' && 'text-fg-info',
jsonType === '[object String]' && 'text-fg-notice',
jsonType === '[object Null]' && 'text-fg-danger',
),
};
}
}, [attrValue, attrKeyJsonPath, isExpanded, depth]);
const labelEl = (
<span className={classNames(labelClassName, 'select-text group-hover:text-gray-800')}>
{label}
</span>
<span className={classNames(labelClassName, 'select-text group-hover:text-fg')}>{label}</span>
);
return (
<div className={classNames(/*depth === 0 && '-ml-4',*/ 'font-mono text-2xs')}>
@@ -91,18 +89,18 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
icon="chevronRight"
className={classNames(
'left-0 absolute transition-transform flex items-center',
'text-gray-600 group-hover:text-gray-900',
'text-fg-subtler group-hover:text-fg-subtle',
isExpanded ? 'rotate-90' : '',
)}
/>
<span className="text-violet-600 group-hover:text-violet-700 mr-1.5 whitespace-nowrap">
<span className="text-fg-primary group-hover:text-fg-primary mr-1.5 whitespace-nowrap">
{attrKey === undefined ? '$' : attrKey}:
</span>
{labelEl}
</button>
) : (
<>
<span className="text-violet-600 mr-1.5 pl-4 whitespace-nowrap select-text">
<span className="text-fg-primary mr-1.5 pl-4 whitespace-nowrap select-text">
{attrKey}:
</span>
{labelEl}

View File

@@ -30,7 +30,7 @@ export function KeyValueRow({ label, value, labelClassName }: Props) {
return (
<>
<td
className={classNames('py-0.5 pr-2 text-gray-700 select-text cursor-text', labelClassName)}
className={classNames('py-0.5 pr-2 text-fg-subtle select-text cursor-text', labelClassName)}
>
{label}
</td>

View File

@@ -388,7 +388,7 @@ function PairEditorRow({
{pairContainer.pair.isFile ? (
<Button
size="xs"
color="gray"
color="secondary"
className="font-mono text-xs"
onClick={async (e) => {
e.preventDefault();

View File

@@ -40,7 +40,7 @@ export function Select<T extends string>({
htmlFor={id}
className={classNames(
labelClassName,
'text-sm text-gray-900 whitespace-nowrap',
'text-sm text-fg whitespace-nowrap',
hideLabel && 'sr-only',
)}
>
@@ -52,7 +52,7 @@ export function Select<T extends string>({
onChange={(e) => onChange(e.target.value as T)}
className={classNames(
'font-mono text-xs border w-full outline-none bg-transparent pl-2 pr-7',
'border-highlight focus:border-focus',
'bg-background-highlight-secondary border-background-highlight focus:border-border-focus',
size === 'xs' && 'h-xs',
size === 'sm' && 'h-sm',
size === 'md' && 'h-md',

View File

@@ -8,19 +8,13 @@ interface Props {
children?: ReactNode;
}
export function Separator({
className,
variant = 'primary',
orientation = 'horizontal',
children,
}: Props) {
export function Separator({ className, orientation = 'horizontal', children }: Props) {
return (
<div role="separator" className={classNames(className, 'flex items-center')}>
{children && <div className="text-xs text-gray-500 mr-2 whitespace-nowrap">{children}</div>}
{children && <div className="text-xs text-fg-subtler mr-2 whitespace-nowrap">{children}</div>}
<div
className={classNames(
variant === 'primary' && 'bg-highlight',
variant === 'secondary' && 'bg-highlightSecondary',
'bg-background-highlight',
orientation === 'horizontal' && 'w-full h-[1px]',
orientation === 'vertical' && 'h-full w-[1px]',
)}

View File

@@ -21,7 +21,7 @@ export function SizeTag({ contentLength }: Props) {
}
return (
<span title={`${contentLength} bytes`}>
<span className="font-mono" title={`${contentLength} bytes`}>
{Math.round(num * 10) / 10} {unit}
</span>
);

View File

@@ -10,17 +10,18 @@ interface Props {
export function StatusTag({ response, className, showReason }: Props) {
const { status } = response;
const label = status < 100 ? 'ERR' : status;
const category = `${status}`[0];
return (
<span
className={classNames(
className,
'font-mono',
status >= 0 && status < 100 && 'text-red-600',
status >= 100 && status < 200 && 'text-green-600',
status >= 200 && status < 300 && 'text-green-600',
status >= 300 && status < 400 && 'text-pink-600',
status >= 400 && status < 500 && 'text-orange-600',
status >= 500 && 'text-red-600',
category === '0' && 'text-fg-danger',
category === '1' && 'text-fg-info',
category === '2' && 'text-fg-success',
category === '3' && 'text-fg-primary',
category === '4' && 'text-fg-warning',
category === '5' && 'text-fg-danger',
)}
>
{label} {showReason && response.statusReason && response.statusReason}

View File

@@ -82,7 +82,7 @@ export function Tabs({
{tabs.map((t) => {
const isActive = t.value === value;
const btnClassName = classNames(
isActive ? 'text-gray-800' : 'text-gray-600 hover:text-gray-700',
isActive ? 'text-fg' : 'text-fg-subtler hover:text-fg-subtle',
'!px-2 ml-[1px]',
);
@@ -108,7 +108,7 @@ export function Tabs({
icon="chevronDown"
className={classNames(
'-mr-1.5 mt-0.5',
isActive ? 'opacity-100' : 'opacity-20',
isActive ? 'text-fg-subtle' : 'opacity-50',
)}
/>
}

View File

@@ -3,9 +3,9 @@ import { motion } from 'framer-motion';
import type { ReactNode } from 'react';
import React from 'react';
import { useKey } from 'react-use';
import { IconButton } from './IconButton';
import type { IconProps } from './Icon';
import { Icon } from './Icon';
import { IconButton } from './IconButton';
export interface ToastProps {
children: ReactNode;
@@ -52,14 +52,15 @@ export function Toast({
transition={{ duration: 0.2 }}
className={classNames(
className,
'x-theme-dialog',
'pointer-events-auto',
'relative bg-gray-50 dark:bg-gray-100 pointer-events-auto',
'relative bg-background pointer-events-auto',
'rounded-lg',
'border border-highlightSecondary dark:border-highlight shadow-xl',
'border border-background-highlight dark:border-background-highlight-secondary shadow-xl',
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
'w-[22rem] max-h-[80vh]',
'm-2 grid grid-cols-[1fr_auto]',
'text-gray-700',
'text-fg',
)}
>
<div className="px-3 py-2 flex items-center gap-2">
@@ -67,10 +68,10 @@ export function Toast({
<Icon
icon={ICONS[variant]}
className={classNames(
variant === 'success' && 'text-green-500',
variant === 'warning' && 'text-orange-500',
variant === 'error' && 'text-red-500',
variant === 'copied' && 'text-violet-500',
variant === 'success' && 'text-fg-success',
variant === 'warning' && 'text-fg-warning',
variant === 'error' && 'text-fg-danger',
variant === 'copied' && 'text-fg-primary',
)}
/>
)}
@@ -82,7 +83,7 @@ export function Toast({
<IconButton
color="custom"
className="opacity-50"
className="opacity-60"
title="Dismiss"
icon="x"
onClick={onClose}
@@ -91,7 +92,7 @@ export function Toast({
{timeout != null && (
<div className="w-full absolute bottom-0 left-0 right-0">
<motion.div
className="bg-highlight h-0.5"
className="bg-background-highlight h-0.5"
initial={{ width: '100%' }}
animate={{ width: '0%', opacity: 0.2 }}
transition={{ duration: timeout / 1000, ease: 'linear' }}

View File

@@ -1,17 +0,0 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
interface Props {
className?: string;
children?: ReactNode;
}
export function WindowDragRegion({ className, ...props }: Props) {
return (
<div
data-tauri-drag-region
className={classNames(className, 'w-full flex-shrink-0')}
{...props}
/>
);
}