mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 00:58:32 +02:00
Lots more theme stuff
This commit is contained in:
Binary file not shown.
@@ -33,7 +33,7 @@ function App() {
|
|||||||
<div className="grid grid-rows-[auto_1fr] h-full overflow-hidden">
|
<div className="grid grid-rows-[auto_1fr] h-full overflow-hidden">
|
||||||
<HStack
|
<HStack
|
||||||
as={WindowDragRegion}
|
as={WindowDragRegion}
|
||||||
className="px-3 bg-background text-sm text-gray-900 border-b border-b-gray-100 pt-[1px]"
|
className="px-3 bg-background text-sm text-gray-900 border-b border-b-gray-200 pt-[1px]"
|
||||||
items="center"
|
items="center"
|
||||||
>
|
>
|
||||||
{request.name}
|
{request.name}
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import { forwardRef } from 'react';
|
|||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
const colorStyles = {
|
const colorStyles = {
|
||||||
default: 'hover:bg-gray-500/10 text-gray-600',
|
default: 'hover:bg-gray-700/10 text-gray-700 hover:text-gray-900',
|
||||||
gray: 'text-gray-800 bg-gray-100 hover:bg-gray-500/20',
|
gray: 'text-gray-800 bg-gray-100 hover:bg-gray-500/20 hover:text-gray-900',
|
||||||
tint: 'text-white/90 hover:text-white hover:bg-white/20',
|
|
||||||
primary: 'bg-blue-400',
|
primary: 'bg-blue-400',
|
||||||
secondary: 'bg-violet-400',
|
secondary: 'bg-violet-400',
|
||||||
warning: 'bg-orange-400',
|
warning: 'bg-orange-400',
|
||||||
|
|||||||
105
src-web/components/Colors.tsx
Normal file
105
src-web/components/Colors.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import { HStack, VStack } from './Stacks';
|
||||||
|
|
||||||
|
export function Colors() {
|
||||||
|
return (
|
||||||
|
<HStack>
|
||||||
|
<VStack>
|
||||||
|
<Color className="bg-gray-50" />
|
||||||
|
<Color className="bg-gray-100" />
|
||||||
|
<Color className="bg-gray-200" />
|
||||||
|
<Color className="bg-gray-300" />
|
||||||
|
<Color className="bg-gray-400" />
|
||||||
|
<Color className="bg-gray-500" />
|
||||||
|
<Color className="bg-gray-600" />
|
||||||
|
<Color className="bg-gray-700" />
|
||||||
|
<Color className="bg-gray-800" />
|
||||||
|
<Color className="bg-gray-900" />
|
||||||
|
<Color className="bg-gray-950" />
|
||||||
|
</VStack>
|
||||||
|
<VStack>
|
||||||
|
<Color className="bg-red-50" />
|
||||||
|
<Color className="bg-red-100" />
|
||||||
|
<Color className="bg-red-200" />
|
||||||
|
<Color className="bg-red-300" />
|
||||||
|
<Color className="bg-red-400" />
|
||||||
|
<Color className="bg-red-500" />
|
||||||
|
<Color className="bg-red-600" />
|
||||||
|
<Color className="bg-red-700" />
|
||||||
|
<Color className="bg-red-800" />
|
||||||
|
<Color className="bg-red-900" />
|
||||||
|
<Color className="bg-red-950" />
|
||||||
|
</VStack>
|
||||||
|
<VStack>
|
||||||
|
<Color className="bg-orange-50" />
|
||||||
|
<Color className="bg-orange-100" />
|
||||||
|
<Color className="bg-orange-200" />
|
||||||
|
<Color className="bg-orange-300" />
|
||||||
|
<Color className="bg-orange-400" />
|
||||||
|
<Color className="bg-orange-500" />
|
||||||
|
<Color className="bg-orange-600" />
|
||||||
|
<Color className="bg-orange-700" />
|
||||||
|
<Color className="bg-orange-800" />
|
||||||
|
<Color className="bg-orange-900" />
|
||||||
|
<Color className="bg-orange-950" />
|
||||||
|
</VStack>
|
||||||
|
<VStack>
|
||||||
|
<Color className="bg-yellow-50" />
|
||||||
|
<Color className="bg-yellow-100" />
|
||||||
|
<Color className="bg-yellow-200" />
|
||||||
|
<Color className="bg-yellow-300" />
|
||||||
|
<Color className="bg-yellow-400" />
|
||||||
|
<Color className="bg-yellow-500" />
|
||||||
|
<Color className="bg-yellow-600" />
|
||||||
|
<Color className="bg-yellow-700" />
|
||||||
|
<Color className="bg-yellow-800" />
|
||||||
|
<Color className="bg-yellow-900" />
|
||||||
|
<Color className="bg-yellow-950" />
|
||||||
|
</VStack>
|
||||||
|
<VStack>
|
||||||
|
<Color className="bg-green-50" />
|
||||||
|
<Color className="bg-green-100" />
|
||||||
|
<Color className="bg-green-200" />
|
||||||
|
<Color className="bg-green-300" />
|
||||||
|
<Color className="bg-green-400" />
|
||||||
|
<Color className="bg-green-500" />
|
||||||
|
<Color className="bg-green-600" />
|
||||||
|
<Color className="bg-green-700" />
|
||||||
|
<Color className="bg-green-800" />
|
||||||
|
<Color className="bg-green-900" />
|
||||||
|
<Color className="bg-green-950" />
|
||||||
|
</VStack>
|
||||||
|
<VStack>
|
||||||
|
<Color className="bg-blue-50" />
|
||||||
|
<Color className="bg-blue-100" />
|
||||||
|
<Color className="bg-blue-200" />
|
||||||
|
<Color className="bg-blue-300" />
|
||||||
|
<Color className="bg-blue-400" />
|
||||||
|
<Color className="bg-blue-500" />
|
||||||
|
<Color className="bg-blue-600" />
|
||||||
|
<Color className="bg-blue-700" />
|
||||||
|
<Color className="bg-blue-800" />
|
||||||
|
<Color className="bg-blue-900" />
|
||||||
|
<Color className="bg-blue-950" />
|
||||||
|
</VStack>
|
||||||
|
<VStack>
|
||||||
|
<Color className="bg-violet-50" />
|
||||||
|
<Color className="bg-violet-100" />
|
||||||
|
<Color className="bg-violet-200" />
|
||||||
|
<Color className="bg-violet-300" />
|
||||||
|
<Color className="bg-violet-400" />
|
||||||
|
<Color className="bg-violet-500" />
|
||||||
|
<Color className="bg-violet-600" />
|
||||||
|
<Color className="bg-violet-700" />
|
||||||
|
<Color className="bg-violet-800" />
|
||||||
|
<Color className="bg-violet-900" />
|
||||||
|
<Color className="bg-violet-950" />
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Color({ className }: { className: string }) {
|
||||||
|
return <div className={classnames(className, 'w-full h-5')} />;
|
||||||
|
}
|
||||||
@@ -28,14 +28,15 @@ export function Dialog({
|
|||||||
<D.Root open={open} onOpenChange={onOpenChange}>
|
<D.Root open={open} onOpenChange={onOpenChange}>
|
||||||
<D.Portal container={document.querySelector<HTMLElement>('#radix-portal')}>
|
<D.Portal container={document.querySelector<HTMLElement>('#radix-portal')}>
|
||||||
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
|
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
|
||||||
<D.Overlay className="fixed inset-0 bg-gray-900 dark:bg-background opacity-80 shadow-lg" />
|
<D.Overlay className="fixed inset-0 bg-gray-900 dark:bg-black/50" />
|
||||||
<D.Content>
|
<D.Content>
|
||||||
<div className={classnames(className, 'fixed inset-0 pointer-events-none')}>
|
<div className={classnames(className, 'fixed inset-0')}>
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
className,
|
className,
|
||||||
'absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] bg-gray-50',
|
'absolute z-10 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] bg-gray-100',
|
||||||
'w-[20rem] max-h-[80vh] p-5 rounded-lg overflow-auto',
|
'w-[20rem] max-h-[80vh] p-5 rounded-lg overflow-auto',
|
||||||
|
'border border-gray-200 shadow-lg',
|
||||||
wide && 'w-[80vw] max-w-[50rem]',
|
wide && 'w-[80vw] max-w-[50rem]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ const DropdownMenuContent = forwardRef<HTMLDivElement, D.DropdownMenuContentProp
|
|||||||
align="start"
|
align="start"
|
||||||
className={classnames(
|
className={classnames(
|
||||||
className,
|
className,
|
||||||
'bg-background rounded-md shadow-lg p-1.5 border border-gray-100',
|
'bg-background rounded-md shadow-lg p-1.5 border border-gray-200',
|
||||||
'overflow-auto m-1',
|
'overflow-auto m-1',
|
||||||
)}
|
)}
|
||||||
style={styles}
|
style={styles}
|
||||||
|
|||||||
@@ -6,13 +6,12 @@
|
|||||||
|
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
@apply inset-0;
|
@apply inset-0;
|
||||||
position: absolute !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
@apply w-full block text-[0.85rem];
|
@apply w-full block text-base;
|
||||||
|
|
||||||
&.cm-focused {
|
&.cm-focused {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
@@ -28,6 +27,7 @@
|
|||||||
|
|
||||||
.cm-gutters {
|
.cm-gutters {
|
||||||
@apply border-0 text-gray-500 text-opacity-30;
|
@apply border-0 text-gray-500 text-opacity-30;
|
||||||
|
|
||||||
.cm-gutterElement {
|
.cm-gutterElement {
|
||||||
@apply cursor-default;
|
@apply cursor-default;
|
||||||
}
|
}
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-widget {
|
.placeholder-widget {
|
||||||
@apply text-xs text-white/90 bg-blue-400/80 py-[0.5px] px-1 mx-[1px] rounded cursor-default hover:bg-blue-400 hover:text-white;
|
@apply text-[0.9em] text-gray-900 bg-gray-200 px-2 border border-background py-0.5
|
||||||
text-shadow: 0 0 1px rgba(0, 0, 0, 0.9);
|
rounded cursor-default hover:bg-gray-300 hover:text-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-scroller {
|
.cm-scroller {
|
||||||
font-family: inherit;
|
|
||||||
overflow: hidden !important;;
|
overflow: hidden !important;;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +61,8 @@
|
|||||||
|
|
||||||
.cm-multiline {
|
.cm-multiline {
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
@apply h-full;
|
@apply h-full text-[0.95em];
|
||||||
|
position: absolute !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-scroller {
|
.cm-scroller {
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .cm-selectionBackground {
|
.cm-editor .cm-selectionBackground {
|
||||||
@apply bg-gray-200;
|
@apply bg-gray-400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor.cm-focused .cm-selectionBackground {
|
.cm-editor.cm-focused .cm-selectionBackground {
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
@apply bg-gray-100 bg-opacity-30 rounded-full;
|
@apply bg-gray-400 bg-opacity-30 rounded-full;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
|
|
||||||
/* NOTE: Extra selector required to override default styles */
|
/* NOTE: Extra selector required to override default styles */
|
||||||
.cm-tooltip.cm-tooltip {
|
.cm-tooltip.cm-tooltip {
|
||||||
@apply shadow-lg bg-background rounded overflow-hidden text-gray-900 border border-gray-100/70 z-50 pointer-events-auto;
|
@apply shadow-lg bg-background rounded overflow-hidden text-gray-900 border border-gray-200 z-50 pointer-events-auto;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@apply transition-none;
|
@apply transition-none;
|
||||||
@@ -172,15 +172,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& > ul > li {
|
& > ul > li {
|
||||||
@apply cursor-default px-2 rounded-sm text-gray-500 h-7 flex items-center;
|
@apply cursor-default px-2 rounded-sm text-gray-600 h-7 flex items-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > ul > li[aria-selected] {
|
& > ul > li[aria-selected] {
|
||||||
@apply bg-gray-50 text-gray-800;
|
@apply bg-gray-100 text-gray-900;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > ul > li:hover {
|
& > ul > li:hover {
|
||||||
@apply text-gray-700;
|
@apply text-gray-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-completionIcon {
|
.cm-completionIcon {
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ const colsClasses = {
|
|||||||
none: 'grid-cols-none',
|
none: 'grid-cols-none',
|
||||||
1: 'grid-cols-1',
|
1: 'grid-cols-1',
|
||||||
2: 'grid-cols-2',
|
2: 'grid-cols-2',
|
||||||
3: 'grid-cols-2',
|
3: 'grid-cols-3',
|
||||||
|
11: 'grid-cols-11',
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowsClasses = {
|
const rowsClasses = {
|
||||||
none: 'grid-rows-none',
|
none: 'grid-rows-none',
|
||||||
1: 'grid-rows-1',
|
1: 'grid-rows-1',
|
||||||
2: 'grid-rows-2',
|
2: 'grid-rows-2',
|
||||||
3: 'grid-rows-2',
|
3: 'grid-rows-3',
|
||||||
|
11: 'grid-rows-11',
|
||||||
};
|
};
|
||||||
|
|
||||||
const gapClasses = {
|
const gapClasses = {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function Input({
|
|||||||
className={classnames(
|
className={classnames(
|
||||||
containerClassName,
|
containerClassName,
|
||||||
'relative w-full rounded-md text-gray-900 bg-gray-200/10',
|
'relative w-full rounded-md text-gray-900 bg-gray-200/10',
|
||||||
'border border-gray-50 focus-within:border-blue-400/40',
|
'border border-gray-200 focus-within:border-blue-400/40',
|
||||||
size === 'md' && 'h-10',
|
size === 'md' && 'h-10',
|
||||||
size === 'sm' && 'h-8',
|
size === 'sm' && 'h-8',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -46,19 +46,16 @@ export function ResponsePane({ requestId, className }: Props) {
|
|||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
className,
|
className,
|
||||||
'max-h-full h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 bg-gray-100 rounded-md overflow-hidden border border-gray-50',
|
'max-h-full h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 bg-gray-100 rounded-md overflow-hidden border border-gray-200',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/*<HStack as={WindowDragRegion} items="center" className="pl-1.5 pr-1">*/}
|
{/*<HStack as={WindowDragRegion} items="center" className="pl-1.5 pr-1">*/}
|
||||||
{/*</HStack>*/}
|
{/*</HStack>*/}
|
||||||
{response?.error && (
|
|
||||||
<div className="text-white bg-red-500 px-2 py-1 rounded">{response.error}</div>
|
|
||||||
)}
|
|
||||||
{response && (
|
{response && (
|
||||||
<>
|
<>
|
||||||
<HStack
|
<HStack
|
||||||
items="center"
|
items="center"
|
||||||
className="italic text-gray-500 text-sm w-full mb-1 flex-shrink-0 pl-2"
|
className="italic text-gray-600 text-sm w-full mb-1 flex-shrink-0 pl-2"
|
||||||
>
|
>
|
||||||
<div className="whitespace-nowrap">
|
<div className="whitespace-nowrap">
|
||||||
{response.status}
|
{response.status}
|
||||||
@@ -107,7 +104,11 @@ export function ResponsePane({ requestId, className }: Props) {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
{viewMode === 'pretty' && contentForIframe !== null ? (
|
{response?.error ? (
|
||||||
|
<div className="p-1">
|
||||||
|
<div className="text-white bg-red-500 px-3 py-2 rounded">{response.error}</div>
|
||||||
|
</div>
|
||||||
|
) : viewMode === 'pretty' && contentForIframe !== null ? (
|
||||||
<div className="px-2 pb-2">
|
<div className="px-2 pb-2">
|
||||||
<iframe
|
<iframe
|
||||||
title="Response preview"
|
title="Response preview"
|
||||||
@@ -118,7 +119,7 @@ export function ResponsePane({ requestId, className }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
) : response?.body ? (
|
) : response?.body ? (
|
||||||
<Editor
|
<Editor
|
||||||
className="mr-1 !bg-gray-100"
|
className="!bg-gray-100"
|
||||||
valueKey={`${contentType}:${response.body}`}
|
valueKey={`${contentType}:${response.body}`}
|
||||||
defaultValue={response?.body}
|
defaultValue={response?.body}
|
||||||
contentType={contentType}
|
contentType={contentType}
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
|
|||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(className, 'w-52 bg-gray-100 h-full border-r border-gray-200')}
|
||||||
className,
|
|
||||||
'w-52 bg-violet-600 dark:bg-violet-50 h-full border-gray-100/50 relative z-10',
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<HStack as={WindowDragRegion} items="center" justify="end">
|
<HStack as={WindowDragRegion} items="center" justify="end">
|
||||||
@@ -37,6 +34,7 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<IconButton size="sm" icon="camera" onClick={() => setOpen(true)} />
|
||||||
<IconButton
|
<IconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
icon={appearance === 'dark' ? 'moon' : 'sun'}
|
icon={appearance === 'dark' ? 'moon' : 'sun'}
|
||||||
@@ -54,19 +52,7 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
|
|||||||
{requests.map((r) => (
|
{requests.map((r) => (
|
||||||
<SidebarItem key={r.id} request={r} active={r.id === activeRequestId} />
|
<SidebarItem key={r.id} request={r} active={r.id === activeRequestId} />
|
||||||
))}
|
))}
|
||||||
<div>
|
{/*<Colors />*/}
|
||||||
<div className="w-10 h-5 bg-blue-50" />
|
|
||||||
<div className="w-10 h-5 bg-blue-100" />
|
|
||||||
<div className="w-10 h-5 bg-blue-200" />
|
|
||||||
<div className="w-10 h-5 bg-blue-300" />
|
|
||||||
<div className="w-10 h-5 bg-blue-400" />
|
|
||||||
<div className="w-10 h-5 bg-blue-500" />
|
|
||||||
<div className="w-10 h-5 bg-blue-600" />
|
|
||||||
<div className="w-10 h-5 bg-blue-700" />
|
|
||||||
<div className="w-10 h-5 bg-blue-800" />
|
|
||||||
<div className="w-10 h-5 bg-blue-900" />
|
|
||||||
<div className="w-10 h-5 bg-blue-950" />
|
|
||||||
</div>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -77,9 +63,8 @@ function SidebarItem({ request, active }: { request: HttpRequest; active: boolea
|
|||||||
<li key={request.id}>
|
<li key={request.id}>
|
||||||
<Button
|
<Button
|
||||||
as={Link}
|
as={Link}
|
||||||
color="tint"
|
|
||||||
to={`/workspaces/${request.workspaceId}/requests/${request.id}`}
|
to={`/workspaces/${request.workspaceId}/requests/${request.id}`}
|
||||||
className={classnames('w-full', active && 'bg-gray-500/[0.1] text-gray-900')}
|
className={classnames('w-full', active ? 'bg-gray-200/70 text-gray-900' : 'text-gray-500')}
|
||||||
size="xs"
|
size="xs"
|
||||||
justify="start"
|
justify="start"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function UrlBar({
|
|||||||
hideLabel
|
hideLabel
|
||||||
useEditor={{ useTemplating: true, contentType: 'url' }}
|
useEditor={{ useTemplating: true, contentType: 'url' }}
|
||||||
size="sm"
|
size="sm"
|
||||||
className="font-mono text-sm"
|
className="font-mono"
|
||||||
name="url"
|
name="url"
|
||||||
label="Enter URL"
|
label="Enter URL"
|
||||||
containerClassName={className}
|
containerClassName={className}
|
||||||
|
|||||||
@@ -1,32 +1,18 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { generateColorVariant, toTailwindVariable } from './theme';
|
import { generateColorVariant, toTailwindVariable } from './theme';
|
||||||
|
|
||||||
describe('suite name', () => {
|
describe('Generate colors', () => {
|
||||||
it('Generates dark variants', () => {
|
it('Generates dark colors', () => {
|
||||||
expect(generateColorVariant('blue', 50, 'dark')).toEqual('hsl(240,100%,5.0%)');
|
expect(generateColorVariant('hsl(0,0%,50%)', 50, 'dark', 0.2, 0.8)).toBe('hsl(0,0%,14.0%)');
|
||||||
expect(generateColorVariant('blue', 100, 'dark')).toEqual('hsl(240,100%,10.0%)');
|
expect(generateColorVariant('hsl(0,0%,50%)', 950, 'dark', 0.2, 0.8)).toBe('hsl(0,0%,77.0%)');
|
||||||
expect(generateColorVariant('blue', 200, 'dark')).toEqual('hsl(240,100%,20.0%)');
|
expect(generateColorVariant('hsl(0,0%,50%)', 50, 'dark', 0.4, 0.6)).toBe('hsl(0,0%,23.0%)');
|
||||||
expect(generateColorVariant('blue', 300, 'dark')).toEqual('hsl(240,100%,30.0%)');
|
expect(generateColorVariant('hsl(0,0%,50%)', 950, 'dark', 0.4, 0.6)).toBe('hsl(0,0%,59.0%)');
|
||||||
expect(generateColorVariant('blue', 400, 'dark')).toEqual('hsl(240,100%,40.0%)');
|
|
||||||
expect(generateColorVariant('blue', 500, 'dark')).toEqual('hsl(240,100%,50.0%)');
|
|
||||||
expect(generateColorVariant('blue', 600, 'dark')).toEqual('hsl(240,100%,60.0%)');
|
|
||||||
expect(generateColorVariant('blue', 700, 'dark')).toEqual('hsl(240,100%,70.0%)');
|
|
||||||
expect(generateColorVariant('blue', 800, 'dark')).toEqual('hsl(240,100%,80.0%)');
|
|
||||||
expect(generateColorVariant('blue', 900, 'dark')).toEqual('hsl(240,100%,90.0%)');
|
|
||||||
expect(generateColorVariant('blue', 950, 'dark')).toEqual('hsl(240,100%,95.0%)');
|
|
||||||
});
|
});
|
||||||
it('Generates light variants', () => {
|
it('Generates light colors', () => {
|
||||||
expect(generateColorVariant('blue', 50, 'light')).toEqual('hsl(240,100%,95.0%)');
|
expect(generateColorVariant('hsl(0,0%,50%)', 50, 'light', 0.2, 0.8)).toBe('hsl(0,0%,77.0%)');
|
||||||
expect(generateColorVariant('blue', 100, 'light')).toEqual('hsl(240,100%,90.0%)');
|
expect(generateColorVariant('hsl(0,0%,50%)', 950, 'light', 0.2, 0.8)).toBe('hsl(0,0%,14.0%)');
|
||||||
expect(generateColorVariant('blue', 200, 'light')).toEqual('hsl(240,100%,80.0%)');
|
expect(generateColorVariant('hsl(0,0%,50%)', 50, 'light', 0.4, 0.6)).toBe('hsl(0,0%,59.0%)');
|
||||||
expect(generateColorVariant('blue', 300, 'light')).toEqual('hsl(240,100%,70.0%)');
|
expect(generateColorVariant('hsl(0,0%,50%)', 950, 'light', 0.4, 0.6)).toBe('hsl(0,0%,23.0%)');
|
||||||
expect(generateColorVariant('blue', 400, 'light')).toEqual('hsl(240,100%,60.0%)');
|
|
||||||
expect(generateColorVariant('blue', 500, 'light')).toEqual('hsl(240,100%,50.0%)');
|
|
||||||
expect(generateColorVariant('blue', 600, 'light')).toEqual('hsl(240,100%,40.0%)');
|
|
||||||
expect(generateColorVariant('blue', 700, 'light')).toEqual('hsl(240,100%,30.0%)');
|
|
||||||
expect(generateColorVariant('blue', 800, 'light')).toEqual('hsl(240,100%,20.0%)');
|
|
||||||
expect(generateColorVariant('blue', 900, 'light')).toEqual('hsl(240,100%,10.0%)');
|
|
||||||
expect(generateColorVariant('blue', 950, 'light')).toEqual('hsl(240,100%,5.0%)');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export type AppThemeLayer = 'root' | 'sidebar' | 'titlebar' | 'content' | 'above
|
|||||||
|
|
||||||
export interface AppThemeLayerStyle {
|
export interface AppThemeLayerStyle {
|
||||||
colors: Record<AppThemeColor, string>;
|
colors: Record<AppThemeColor, string>;
|
||||||
|
blackPoint?: number;
|
||||||
|
whitePoint?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ThemeColorObj {
|
interface ThemeColorObj {
|
||||||
@@ -51,7 +53,15 @@ export function generateCSS(t: AppTheme): ThemeColorObj[] {
|
|||||||
for (const color of colorNames) {
|
for (const color of colorNames) {
|
||||||
const rawValue = rootColors[color];
|
const rawValue = rootColors[color];
|
||||||
if (!rawValue) continue;
|
if (!rawValue) continue;
|
||||||
colors.push(...generateColors(color, rawValue, t.appearance));
|
colors.push(
|
||||||
|
...generateColors(
|
||||||
|
color,
|
||||||
|
rawValue,
|
||||||
|
t.appearance,
|
||||||
|
t.layers.root?.blackPoint,
|
||||||
|
t.layers.root?.whitePoint,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return colors;
|
return colors;
|
||||||
@@ -61,39 +71,45 @@ export function generateColors(
|
|||||||
name: AppThemeColor,
|
name: AppThemeColor,
|
||||||
color: string,
|
color: string,
|
||||||
appearance: Appearance,
|
appearance: Appearance,
|
||||||
|
blackPoint = 0,
|
||||||
|
whitePoint = 1,
|
||||||
): ThemeColorObj[] {
|
): ThemeColorObj[] {
|
||||||
const colors = [];
|
const colors = [];
|
||||||
for (const variant of appThemeVariants) {
|
for (const variant of appThemeVariants) {
|
||||||
colors.push({ name, variant, cssColor: generateColorVariant(color, variant, appearance) });
|
colors.push({
|
||||||
|
name,
|
||||||
|
variant,
|
||||||
|
cssColor: generateColorVariant(color, variant, appearance, blackPoint, whitePoint),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lightnessMap: Record<Appearance, Record<AppThemeColorVariant, number>> = {
|
const lightnessMap: Record<Appearance, Record<AppThemeColorVariant, number>> = {
|
||||||
light: {
|
light: {
|
||||||
50: 1,
|
50: 0.98,
|
||||||
100: 0.8,
|
100: 0.88,
|
||||||
200: 0.7,
|
200: 0.7,
|
||||||
300: 0.5,
|
300: 0.5,
|
||||||
400: 0.3,
|
400: 0.3,
|
||||||
500: 0.1,
|
500: 0,
|
||||||
600: -0.2,
|
600: -0.2,
|
||||||
700: -0.3,
|
700: -0.3,
|
||||||
800: -0.5,
|
800: -0.45,
|
||||||
900: -0.7,
|
900: -0.6,
|
||||||
950: -0.8,
|
950: -0.8,
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
50: -0.95,
|
50: -0.98,
|
||||||
100: -0.8,
|
100: -0.93,
|
||||||
200: -0.6,
|
200: -0.78,
|
||||||
300: -0.4,
|
300: -0.63,
|
||||||
400: -0.2,
|
400: -0.44,
|
||||||
500: 0,
|
500: -0.2,
|
||||||
600: 0.2,
|
600: 0,
|
||||||
700: 0.4,
|
700: 0.3,
|
||||||
800: 0.5,
|
800: 0.6,
|
||||||
900: 0.7,
|
900: 0.8,
|
||||||
950: 0.9,
|
950: 0.9,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -102,10 +118,16 @@ export function generateColorVariant(
|
|||||||
color: string,
|
color: string,
|
||||||
variant: AppThemeColorVariant,
|
variant: AppThemeColorVariant,
|
||||||
appearance: Appearance,
|
appearance: Appearance,
|
||||||
|
blackPoint = 0,
|
||||||
|
whitePoint = 1,
|
||||||
): string {
|
): string {
|
||||||
const { hsl } = parseColor(color || '');
|
const { hsl } = parseColor(color || '');
|
||||||
const lightnessMod = lightnessMap[appearance][variant];
|
// const lightnessMod = lightnessMap[appearance][variant];
|
||||||
const newL = hsl[2] + (100 - hsl[2]) * lightnessMod;
|
const lightnessMod = (appearance === 'dark' ? 1 : -1) * ((variant / 1000) * 2 - 1);
|
||||||
|
const newL =
|
||||||
|
lightnessMod > 0
|
||||||
|
? hsl[2] + (100 * whitePoint - hsl[2]) * lightnessMod
|
||||||
|
: hsl[2] + hsl[2] * (1 - blackPoint) * lightnessMod;
|
||||||
return `hsl(${hsl[0]},${hsl[1]}%,${newL.toFixed(1)}%)`;
|
return `hsl(${hsl[0]},${hsl[1]}%,${newL.toFixed(1)}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ const darkTheme: AppTheme = {
|
|||||||
appearance: 'dark',
|
appearance: 'dark',
|
||||||
layers: {
|
layers: {
|
||||||
root: {
|
root: {
|
||||||
|
blackPoint: 0.15,
|
||||||
|
whitePoint: 0.95,
|
||||||
colors: {
|
colors: {
|
||||||
gray: '#69789b',
|
gray: '#656196',
|
||||||
red: '#ff1c1c',
|
red: '#ff2727',
|
||||||
orange: '#ff9411',
|
orange: '#ff9411',
|
||||||
yellow: '#ffff1f',
|
yellow: '#dede34',
|
||||||
green: '#35ff35',
|
green: '#39da39',
|
||||||
blue: '#1365ff',
|
blue: '#2e91ff',
|
||||||
pink: '#ff74ff',
|
pink: '#ff74ff',
|
||||||
violet: '#873fff',
|
violet: '#a853ff',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -27,8 +29,10 @@ const lightTheme: AppTheme = {
|
|||||||
appearance: 'light',
|
appearance: 'light',
|
||||||
layers: {
|
layers: {
|
||||||
root: {
|
root: {
|
||||||
|
blackPoint: 0.1,
|
||||||
|
whitePoint: 1,
|
||||||
colors: {
|
colors: {
|
||||||
gray: '#69789b',
|
gray: '#7f8fb0',
|
||||||
red: '#e13939',
|
red: '#e13939',
|
||||||
orange: '#da881f',
|
orange: '#da881f',
|
||||||
yellow: '#e3b22d',
|
yellow: '#e3b22d',
|
||||||
@@ -63,7 +67,7 @@ export function setAppearance(a?: Appearance) {
|
|||||||
document.documentElement.setAttribute('data-appearance', appearance);
|
document.documentElement.setAttribute('data-appearance', appearance);
|
||||||
document.documentElement.setAttribute('data-theme', theme.name);
|
document.documentElement.setAttribute('data-theme', theme.name);
|
||||||
|
|
||||||
let existingStyleEl = document.head.querySelector(`style[data-theme-definition="${theme.name}"]`);
|
let existingStyleEl = document.head.querySelector(`style[data-theme-definition]`);
|
||||||
if (!existingStyleEl) {
|
if (!existingStyleEl) {
|
||||||
const styleEl = document.createElement('style');
|
const styleEl = document.createElement('style');
|
||||||
document.head.appendChild(styleEl);
|
document.head.appendChild(styleEl);
|
||||||
@@ -71,11 +75,16 @@ export function setAppearance(a?: Appearance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
existingStyleEl.textContent = [
|
existingStyleEl.textContent = [
|
||||||
`[data-theme="${theme.name}"] {`,
|
`/* ${darkTheme.name} */`,
|
||||||
...generateCSS(theme).map(toTailwindVariable),
|
`[data-appearance="dark"] {`,
|
||||||
|
...generateCSS(darkTheme).map(toTailwindVariable),
|
||||||
|
'}',
|
||||||
|
`/* ${lightTheme.name} */`,
|
||||||
|
`[data-appearance="light"] {`,
|
||||||
|
...generateCSS(lightTheme).map(toTailwindVariable),
|
||||||
'}',
|
'}',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
existingStyleEl.setAttribute('data-theme-definition', theme.name);
|
existingStyleEl.setAttribute('data-theme-definition', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreferredAppearance(): Appearance {
|
export function getPreferredAppearance(): Appearance {
|
||||||
|
|||||||
@@ -20,14 +20,15 @@ module.exports = {
|
|||||||
white: 'hsl(0 100% 100% / <alpha-value>)',
|
white: 'hsl(0 100% 100% / <alpha-value>)',
|
||||||
black: 'hsl(0 100% 0% / <alpha-value>)',
|
black: 'hsl(0 100% 0% / <alpha-value>)',
|
||||||
background: 'hsl(var(--color-background) / <alpha-value>)',
|
background: 'hsl(var(--color-background) / <alpha-value>)',
|
||||||
placeholder: 'hsl(var(--color-gray-200) / <alpha-value>)',
|
placeholder: 'hsl(var(--color-gray-300) / <alpha-value>)',
|
||||||
gray: color('gray'),
|
red: color('red'),
|
||||||
orange: color('orange'),
|
orange: color('orange'),
|
||||||
|
yellow: color('yellow'),
|
||||||
|
gray: color('gray'),
|
||||||
blue: color('blue'),
|
blue: color('blue'),
|
||||||
green: color('green'),
|
green: color('green'),
|
||||||
pink: color('pink'),
|
pink: color('pink'),
|
||||||
violet: color('violet'),
|
violet: color('violet'),
|
||||||
red: color('red'),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user