@jbpark/ui-kit 7.1.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Button.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { PresetColor } from "./lib/colors.mjs";
1
+ import { DeprecatedPresetColor, PresetColor } from "./lib/colors.mjs";
2
2
  import * as React$1 from "react";
3
3
  //#region src/components/atoms/button.d.ts
4
4
  type NativeButtonProps = React$1.ComponentProps<'button'> & {
@@ -43,8 +43,13 @@ interface Props extends Omit<NativeButtonProps, 'size' | 'variant' | 'type'> {
43
43
  /**
44
44
  * Color, independent of `variant`. Defaults to `'default'`, or to whatever
45
45
  * `type` maps to when `type` is set. `danger` overrides this.
46
+ *
47
+ * Preset hues use Tailwind's colour names (#342). The pre-8.0 spellings
48
+ * `magenta`/`geekblue`/`gold`/`volcano` still work and render unchanged, but
49
+ * are deprecated — use `fuchsia`/`indigo`/`amber` instead (`volcano` has no
50
+ * successor; it is `orange` one lightness step darker).
46
51
  */
47
- color?: PresetColor | 'default' | 'primary' | 'danger';
52
+ color?: PresetColor | DeprecatedPresetColor | 'default' | 'primary' | 'danger';
48
53
  loading?: boolean | {
49
54
  icon: React$1.ReactNode;
50
55
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Button.d.mts","names":[],"sources":["../src/components/atoms/button.tsx"],"mappings":";;;KAaK,oBAAoB,QAAM;EAC7B;;UAGe,cAAc,KAC7B;EAGA,OAAO,QAAM;EACb;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;EAkBA;;;;;EAKA;;;;;;EAMA;EACA;;;;;EAKA,QAAQ;EACR;IAAsB,MAAM,QAAM;;;cAqF9B,WAAU,MAAA,WAAA,MAAA,SAAA,UAAA,MAAA,OAAA,OAAA,OAAA,UAAA,SAAA,QAAA,SAAA,UAAA,gBAAA,SAiBb,UAAK,QAAA,IAAA"}
1
+ {"version":3,"file":"Button.d.mts","names":[],"sources":["../src/components/atoms/button.tsx"],"mappings":";;;KAaK,oBAAoB,QAAM;EAC7B;;UAGe,cAAc,KAC7B;EAGA,OAAO,QAAM;EACb;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;EAkBA;;;;;EAKA;;;;;;EAMA;EACA;;;;;;;;;;EAUA,QACE,cAAc;EAChB;IAAsB,MAAM,QAAM;;;cAqF9B,WAAU,MAAA,WAAA,MAAA,SAAA,UAAA,MAAA,OAAA,OAAA,OAAA,UAAA,SAAA,QAAA,SAAA,UAAA,gBAAA,SAiBb,UAAK,QAAA,IAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Button.mjs","names":[],"sources":["../src/components/atoms/button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { LoaderCircle } from 'lucide-react';\n\nimport { useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { INTERACTIVE_CHASSIS } from '../../lib/chassis';\nimport { type PresetColor } from '../../lib/colors';\n\ntype NativeButtonProps = React.ComponentProps<'button'> & {\n asChild?: boolean;\n};\n\nexport interface Props extends Omit<\n NativeButtonProps,\n 'size' | 'variant' | 'type'\n> {\n icon?: React.ReactNode;\n block?: boolean;\n danger?: boolean;\n disabled?: boolean;\n size?: 'small' | 'middle' | 'large';\n /**\n * Syntactic sugar for a `(color, variant)` pair. **Will follow `variant` and\n * `color` if provided** — `type` only fills in the axes you leave unset.\n *\n * | `type` | equivalent to |\n * | --------- | -------------------------------------- |\n * | `primary` | `color=\"primary\" variant=\"solid\"` |\n * | `default` | `color=\"default\" variant=\"outlined\"` |\n * | `dashed` | `color=\"default\" variant=\"dashed\"` |\n * | `text` | `color=\"default\" variant=\"text\"` |\n * | `link` | `color=\"primary\" variant=\"link\"` |\n *\n * So `<Button type=\"primary\">` is the same as\n * `<Button color=\"primary\" variant=\"solid\">`, and\n * `<Button type=\"primary\" variant=\"filled\">` keeps the primary color but\n * renders filled. `danger` still overrides the resolved color.\n */\n type?: 'primary' | 'default' | 'dashed' | 'text' | 'link';\n /**\n * Visual fill style, independent of `color`. Defaults to `'outlined'`.\n * Takes precedence over whatever `type` would have supplied.\n */\n variant?: 'solid' | 'outlined' | 'dashed' | 'filled' | 'text' | 'link';\n /**\n * Native `<button>` `type` (`button`/`submit`/`reset`), kept separate\n * from the antd-style `type` above. Defaults to `'button'` so a Button\n * placed inside a `<form>` doesn't submit it unless explicitly opted in.\n */\n htmlType?: 'button' | 'submit' | 'reset';\n shape?: 'default' | 'circle' | 'round';\n /**\n * Color, independent of `variant`. Defaults to `'default'`, or to whatever\n * `type` maps to when `type` is set. `danger` overrides this.\n */\n color?: PresetColor | 'default' | 'primary' | 'danger';\n loading?: boolean | { icon: React.ReactNode };\n}\n\n// Chassis absorbed from core/button's cva base (#278 ③ / #294 Phase 4). The\n// shared focus/aria set lives in INTERACTIVE_CHASSIS; a Button additionally\n// disables and sizes its icons. Kept as constants so the absorb relocates this\n// treatment rather than dropping it (the #177 class of silent regression the\n// #300/#301 net guards).\nconst BUTTON_CHASSIS = cn(\n INTERACTIVE_CHASSIS,\n 'outline-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n);\n\n// Presentation the atom builds on. The size/variant/shape classes below still\n// override the incidental leftovers exactly as they did when this wrapped\n// core/button, so the rendered output is unchanged (verified against a\n// pre-refactor render matrix).\nconst BUTTON_BASE = cn(\n BUTTON_CHASSIS,\n 'gap-2 whitespace-nowrap font-medium transition-all',\n 'has-[>svg]:px-3',\n);\n\nconst variantClasses: Record<string, string> = {\n // solid used to inherit its fill from core/button's default variant; carry it\n // explicitly now that the primitive is absorbed. A colored solid overrides\n // this with the --btn-* custom properties further down.\n solid: cn('bg-primary text-primary-foreground', 'hover:bg-primary/90'),\n outlined: cn(\n 'border border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n 'bg-background text-foreground',\n 'hover:bg-accent',\n ),\n dashed: cn(\n 'border border-dashed border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n 'bg-background text-foreground',\n 'hover:bg-accent',\n ),\n filled: cn('bg-muted text-foreground', 'hover:bg-muted/80'),\n text: cn('bg-transparent text-foreground', 'hover:bg-accent'),\n link: cn(\n 'bg-transparent text-primary',\n 'underline-offset-4 hover:underline',\n 'hover:bg-primary/10',\n ),\n};\n\n/**\n * antd-style syntactic sugar: each `type` expands to a `(color, variant)` pair.\n * Explicit `color`/`variant` props win over whatever the `type` maps to, so\n * `type` only fills in the axes the caller left unset.\n *\n * Mirrors antd: `<Button type=\"primary\">` ≡ `<Button color=\"primary\" variant=\"solid\">`.\n */\nconst typeToColorVariant: Record<\n NonNullable<Props['type']>,\n { color: NonNullable<Props['color']>; variant: NonNullable<Props['variant']> }\n> = {\n primary: { color: 'primary', variant: 'solid' },\n default: { color: 'default', variant: 'outlined' },\n dashed: { color: 'default', variant: 'dashed' },\n text: { color: 'default', variant: 'text' },\n link: { color: 'primary', variant: 'link' },\n};\n\nconst sizesClasses: Record<string, string> = {\n small: 'h-6 px-3 text-sm',\n middle: 'h-8 px-4 text-base',\n large: 'h-10 px-5 text-lg',\n};\n\nconst iconClasses: Record<string, string> = {\n small: `size-6 [&_svg:not([class*='size-'])]:size-3`,\n middle: `size-8 [&_svg:not([class*='size-'])]:size-4`,\n large: `size-10 [&_svg:not([class*='size-'])]:size-5`,\n};\n\nconst shapesClasses = {\n default: 'rounded-sm',\n circle: 'rounded-full',\n round: 'rounded-2xl',\n};\n\nconst Button = ({\n icon,\n className,\n type,\n variant,\n htmlType = 'button',\n size,\n color,\n shape,\n block = false,\n disabled,\n loading,\n danger,\n asChild = false,\n children,\n onMouseDown,\n ...props\n}: Props) => {\n const { componentSize, defaultProps } = useConfig();\n const buttonDefaults = defaultProps?.button as\n Partial<Pick<Props, 'shape' | 'variant' | 'color'>> | undefined;\n const resolvedSize = size ?? componentSize ?? 'middle';\n const resolvedShape = shape ?? buttonDefaults?.shape ?? 'default';\n const iconOnly = icon && !children;\n // `type` is syntactic sugar that expands to a (color, variant) pair; explicit\n // `color`/`variant` win over it, so `type` only fills the axes left unset.\n // A `Config` default sits below the call-site `type` (which is more specific)\n // but above the library built-in. `danger` still trumps everything, matching\n // antd. Defaults live here rather than on the parameters so `type` gets a\n // chance to supply them first.\n const typeDefaults = type ? typeToColorVariant[type] : undefined;\n const resolvedVariant =\n variant ?? typeDefaults?.variant ?? buttonDefaults?.variant ?? 'outlined';\n const computedColor = danger\n ? 'danger'\n : (color ?? typeDefaults?.color ?? buttonDefaults?.color ?? 'default');\n const colored = computedColor !== 'default';\n const isLoading = !!loading;\n\n const Comp = asChild ? Slot : 'button';\n\n const displayIcon = loading ? (\n typeof loading === 'object' ? (\n loading.icon\n ) : (\n <LoaderCircle className=\"animate-spin\" />\n )\n ) : (\n icon\n );\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(\n BUTTON_BASE,\n 'inline-flex items-center justify-center gap-x-2',\n 'rounded-lg',\n 'cursor-pointer',\n 'h-auto py-0',\n 'transition-all',\n variantClasses[resolvedVariant],\n sizesClasses[resolvedSize],\n iconOnly && ['p-0', iconClasses[resolvedSize]],\n shapesClasses[resolvedShape],\n block && 'w-full',\n colored &&\n (resolvedVariant === 'solid'\n ? [\n 'bg-(--btn-bg)',\n 'hover:bg-(--btn-bg-hover)',\n 'active:bg-(--btn-bg-active)',\n 'text-(--btn-fg)',\n 'border-(--btn-border)',\n ]\n : [\n 'text-(--btn-bg)',\n 'border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n //\n ]),\n className,\n //\n )}\n type={htmlType}\n disabled={disabled || isLoading}\n aria-busy={isLoading}\n data-color={computedColor}\n onMouseDown={e => {\n onMouseDown?.(e);\n }}\n {...props}\n >\n {displayIcon}\n {children}\n </Comp>\n );\n};\n\nexport default Button;\n"],"mappings":";;;;;;;;;AAqEA,MAAM,iBAAiB,GACrB,qBACA,gBACA,oDACA,mFACF;AAMA,MAAM,cAAc,GAClB,gBACA,sDACA,iBACF;AAEA,MAAM,iBAAyC;CAI7C,OAAO,GAAG,sCAAsC,qBAAqB;CACrE,UAAU,GACR,yEACA,iCACA,iBACF;CACA,QAAQ,GACN,uFACA,iCACA,iBACF;CACA,QAAQ,GAAG,4BAA4B,mBAAmB;CAC1D,MAAM,GAAG,kCAAkC,iBAAiB;CAC5D,MAAM,GACJ,+BACA,sCACA,qBACF;AACF;;;;;;;;AASA,MAAM,qBAGF;CACF,SAAS;EAAE,OAAO;EAAW,SAAS;CAAQ;CAC9C,SAAS;EAAE,OAAO;EAAW,SAAS;CAAW;CACjD,QAAQ;EAAE,OAAO;EAAW,SAAS;CAAS;CAC9C,MAAM;EAAE,OAAO;EAAW,SAAS;CAAO;CAC1C,MAAM;EAAE,OAAO;EAAW,SAAS;CAAO;AAC5C;AAEA,MAAM,eAAuC;CAC3C,OAAO;CACP,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,cAAsC;CAC1C,OAAO;CACP,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,gBAAgB;CACpB,SAAS;CACT,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,UAAU,EACd,MACA,WACA,MACA,SACA,WAAW,UACX,MACA,OACA,OACA,QAAQ,OACR,UACA,SACA,QACA,UAAU,OACV,UACA,aACA,GAAG,YACQ;CACX,MAAM,EAAE,eAAe,iBAAiB,UAAU;CAClD,MAAM,iBAAiB,cAAc;CAErC,MAAM,eAAe,QAAQ,iBAAiB;CAC9C,MAAM,gBAAgB,SAAS,gBAAgB,SAAS;CACxD,MAAM,WAAW,QAAQ,CAAC;CAO1B,MAAM,eAAe,OAAO,mBAAmB,QAAQ,KAAA;CACvD,MAAM,kBACJ,WAAW,cAAc,WAAW,gBAAgB,WAAW;CACjE,MAAM,gBAAgB,SAClB,WACC,SAAS,cAAc,SAAS,gBAAgB,SAAS;CAC9D,MAAM,UAAU,kBAAkB;CAClC,MAAM,YAAY,CAAC,CAAC;CAEpB,MAAM,OAAO,UAAU,OAAO;CAE9B,MAAM,cAAc,UAClB,OAAO,YAAY,WACjB,QAAQ,OAER,oBAAC,cAAD,EAAc,WAAU,eAAgB,CAAA,IAG1C;CAGF,OACE,qBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,aACA,mDACA,cACA,kBACA,eACA,kBACA,eAAe,kBACf,aAAa,eACb,YAAY,CAAC,OAAO,YAAY,aAAa,GAC7C,cAAc,gBACd,SAAS,UACT,YACG,oBAAoB,UACjB;GACE;GACA;GACA;GACA;GACA;EACF,IACA,CACE,mBACA,gEAEF,IACN,SAEF;EACA,MAAM;EACN,UAAU,YAAY;EACtB,aAAW;EACX,cAAY;EACZ,cAAa,MAAK;GAChB,cAAc,CAAC;EACjB;EACA,GAAI;EAtCN,UAAA,CAwCG,aACA,QACG;;AAEV"}
1
+ {"version":3,"file":"Button.mjs","names":[],"sources":["../src/components/atoms/button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { LoaderCircle } from 'lucide-react';\n\nimport { useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { INTERACTIVE_CHASSIS } from '../../lib/chassis';\nimport { type DeprecatedPresetColor, type PresetColor } from '../../lib/colors';\n\ntype NativeButtonProps = React.ComponentProps<'button'> & {\n asChild?: boolean;\n};\n\nexport interface Props extends Omit<\n NativeButtonProps,\n 'size' | 'variant' | 'type'\n> {\n icon?: React.ReactNode;\n block?: boolean;\n danger?: boolean;\n disabled?: boolean;\n size?: 'small' | 'middle' | 'large';\n /**\n * Syntactic sugar for a `(color, variant)` pair. **Will follow `variant` and\n * `color` if provided** — `type` only fills in the axes you leave unset.\n *\n * | `type` | equivalent to |\n * | --------- | -------------------------------------- |\n * | `primary` | `color=\"primary\" variant=\"solid\"` |\n * | `default` | `color=\"default\" variant=\"outlined\"` |\n * | `dashed` | `color=\"default\" variant=\"dashed\"` |\n * | `text` | `color=\"default\" variant=\"text\"` |\n * | `link` | `color=\"primary\" variant=\"link\"` |\n *\n * So `<Button type=\"primary\">` is the same as\n * `<Button color=\"primary\" variant=\"solid\">`, and\n * `<Button type=\"primary\" variant=\"filled\">` keeps the primary color but\n * renders filled. `danger` still overrides the resolved color.\n */\n type?: 'primary' | 'default' | 'dashed' | 'text' | 'link';\n /**\n * Visual fill style, independent of `color`. Defaults to `'outlined'`.\n * Takes precedence over whatever `type` would have supplied.\n */\n variant?: 'solid' | 'outlined' | 'dashed' | 'filled' | 'text' | 'link';\n /**\n * Native `<button>` `type` (`button`/`submit`/`reset`), kept separate\n * from the antd-style `type` above. Defaults to `'button'` so a Button\n * placed inside a `<form>` doesn't submit it unless explicitly opted in.\n */\n htmlType?: 'button' | 'submit' | 'reset';\n shape?: 'default' | 'circle' | 'round';\n /**\n * Color, independent of `variant`. Defaults to `'default'`, or to whatever\n * `type` maps to when `type` is set. `danger` overrides this.\n *\n * Preset hues use Tailwind's colour names (#342). The pre-8.0 spellings\n * `magenta`/`geekblue`/`gold`/`volcano` still work and render unchanged, but\n * are deprecated — use `fuchsia`/`indigo`/`amber` instead (`volcano` has no\n * successor; it is `orange` one lightness step darker).\n */\n color?:\n PresetColor | DeprecatedPresetColor | 'default' | 'primary' | 'danger';\n loading?: boolean | { icon: React.ReactNode };\n}\n\n// Chassis absorbed from core/button's cva base (#278 ③ / #294 Phase 4). The\n// shared focus/aria set lives in INTERACTIVE_CHASSIS; a Button additionally\n// disables and sizes its icons. Kept as constants so the absorb relocates this\n// treatment rather than dropping it (the #177 class of silent regression the\n// #300/#301 net guards).\nconst BUTTON_CHASSIS = cn(\n INTERACTIVE_CHASSIS,\n 'outline-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n);\n\n// Presentation the atom builds on. The size/variant/shape classes below still\n// override the incidental leftovers exactly as they did when this wrapped\n// core/button, so the rendered output is unchanged (verified against a\n// pre-refactor render matrix).\nconst BUTTON_BASE = cn(\n BUTTON_CHASSIS,\n 'gap-2 whitespace-nowrap font-medium transition-all',\n 'has-[>svg]:px-3',\n);\n\nconst variantClasses: Record<string, string> = {\n // solid used to inherit its fill from core/button's default variant; carry it\n // explicitly now that the primitive is absorbed. A colored solid overrides\n // this with the --btn-* custom properties further down.\n solid: cn('bg-primary text-primary-foreground', 'hover:bg-primary/90'),\n outlined: cn(\n 'border border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n 'bg-background text-foreground',\n 'hover:bg-accent',\n ),\n dashed: cn(\n 'border border-dashed border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n 'bg-background text-foreground',\n 'hover:bg-accent',\n ),\n filled: cn('bg-muted text-foreground', 'hover:bg-muted/80'),\n text: cn('bg-transparent text-foreground', 'hover:bg-accent'),\n link: cn(\n 'bg-transparent text-primary',\n 'underline-offset-4 hover:underline',\n 'hover:bg-primary/10',\n ),\n};\n\n/**\n * antd-style syntactic sugar: each `type` expands to a `(color, variant)` pair.\n * Explicit `color`/`variant` props win over whatever the `type` maps to, so\n * `type` only fills in the axes the caller left unset.\n *\n * Mirrors antd: `<Button type=\"primary\">` ≡ `<Button color=\"primary\" variant=\"solid\">`.\n */\nconst typeToColorVariant: Record<\n NonNullable<Props['type']>,\n { color: NonNullable<Props['color']>; variant: NonNullable<Props['variant']> }\n> = {\n primary: { color: 'primary', variant: 'solid' },\n default: { color: 'default', variant: 'outlined' },\n dashed: { color: 'default', variant: 'dashed' },\n text: { color: 'default', variant: 'text' },\n link: { color: 'primary', variant: 'link' },\n};\n\nconst sizesClasses: Record<string, string> = {\n small: 'h-6 px-3 text-sm',\n middle: 'h-8 px-4 text-base',\n large: 'h-10 px-5 text-lg',\n};\n\nconst iconClasses: Record<string, string> = {\n small: `size-6 [&_svg:not([class*='size-'])]:size-3`,\n middle: `size-8 [&_svg:not([class*='size-'])]:size-4`,\n large: `size-10 [&_svg:not([class*='size-'])]:size-5`,\n};\n\nconst shapesClasses = {\n default: 'rounded-sm',\n circle: 'rounded-full',\n round: 'rounded-2xl',\n};\n\nconst Button = ({\n icon,\n className,\n type,\n variant,\n htmlType = 'button',\n size,\n color,\n shape,\n block = false,\n disabled,\n loading,\n danger,\n asChild = false,\n children,\n onMouseDown,\n ...props\n}: Props) => {\n const { componentSize, defaultProps } = useConfig();\n const buttonDefaults = defaultProps?.button as\n Partial<Pick<Props, 'shape' | 'variant' | 'color'>> | undefined;\n const resolvedSize = size ?? componentSize ?? 'middle';\n const resolvedShape = shape ?? buttonDefaults?.shape ?? 'default';\n const iconOnly = icon && !children;\n // `type` is syntactic sugar that expands to a (color, variant) pair; explicit\n // `color`/`variant` win over it, so `type` only fills the axes left unset.\n // A `Config` default sits below the call-site `type` (which is more specific)\n // but above the library built-in. `danger` still trumps everything, matching\n // antd. Defaults live here rather than on the parameters so `type` gets a\n // chance to supply them first.\n const typeDefaults = type ? typeToColorVariant[type] : undefined;\n const resolvedVariant =\n variant ?? typeDefaults?.variant ?? buttonDefaults?.variant ?? 'outlined';\n const computedColor = danger\n ? 'danger'\n : (color ?? typeDefaults?.color ?? buttonDefaults?.color ?? 'default');\n const colored = computedColor !== 'default';\n const isLoading = !!loading;\n\n const Comp = asChild ? Slot : 'button';\n\n const displayIcon = loading ? (\n typeof loading === 'object' ? (\n loading.icon\n ) : (\n <LoaderCircle className=\"animate-spin\" />\n )\n ) : (\n icon\n );\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(\n BUTTON_BASE,\n 'inline-flex items-center justify-center gap-x-2',\n 'rounded-lg',\n 'cursor-pointer',\n 'h-auto py-0',\n 'transition-all',\n variantClasses[resolvedVariant],\n sizesClasses[resolvedSize],\n iconOnly && ['p-0', iconClasses[resolvedSize]],\n shapesClasses[resolvedShape],\n block && 'w-full',\n colored &&\n (resolvedVariant === 'solid'\n ? [\n 'bg-(--btn-bg)',\n 'hover:bg-(--btn-bg-hover)',\n 'active:bg-(--btn-bg-active)',\n 'text-(--btn-fg)',\n 'border-(--btn-border)',\n ]\n : [\n 'text-(--btn-bg)',\n 'border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n //\n ]),\n className,\n //\n )}\n type={htmlType}\n disabled={disabled || isLoading}\n aria-busy={isLoading}\n data-color={computedColor}\n onMouseDown={e => {\n onMouseDown?.(e);\n }}\n {...props}\n >\n {displayIcon}\n {children}\n </Comp>\n );\n};\n\nexport default Button;\n"],"mappings":";;;;;;;;;AA2EA,MAAM,iBAAiB,GACrB,qBACA,gBACA,oDACA,mFACF;AAMA,MAAM,cAAc,GAClB,gBACA,sDACA,iBACF;AAEA,MAAM,iBAAyC;CAI7C,OAAO,GAAG,sCAAsC,qBAAqB;CACrE,UAAU,GACR,yEACA,iCACA,iBACF;CACA,QAAQ,GACN,uFACA,iCACA,iBACF;CACA,QAAQ,GAAG,4BAA4B,mBAAmB;CAC1D,MAAM,GAAG,kCAAkC,iBAAiB;CAC5D,MAAM,GACJ,+BACA,sCACA,qBACF;AACF;;;;;;;;AASA,MAAM,qBAGF;CACF,SAAS;EAAE,OAAO;EAAW,SAAS;CAAQ;CAC9C,SAAS;EAAE,OAAO;EAAW,SAAS;CAAW;CACjD,QAAQ;EAAE,OAAO;EAAW,SAAS;CAAS;CAC9C,MAAM;EAAE,OAAO;EAAW,SAAS;CAAO;CAC1C,MAAM;EAAE,OAAO;EAAW,SAAS;CAAO;AAC5C;AAEA,MAAM,eAAuC;CAC3C,OAAO;CACP,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,cAAsC;CAC1C,OAAO;CACP,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,gBAAgB;CACpB,SAAS;CACT,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,UAAU,EACd,MACA,WACA,MACA,SACA,WAAW,UACX,MACA,OACA,OACA,QAAQ,OACR,UACA,SACA,QACA,UAAU,OACV,UACA,aACA,GAAG,YACQ;CACX,MAAM,EAAE,eAAe,iBAAiB,UAAU;CAClD,MAAM,iBAAiB,cAAc;CAErC,MAAM,eAAe,QAAQ,iBAAiB;CAC9C,MAAM,gBAAgB,SAAS,gBAAgB,SAAS;CACxD,MAAM,WAAW,QAAQ,CAAC;CAO1B,MAAM,eAAe,OAAO,mBAAmB,QAAQ,KAAA;CACvD,MAAM,kBACJ,WAAW,cAAc,WAAW,gBAAgB,WAAW;CACjE,MAAM,gBAAgB,SAClB,WACC,SAAS,cAAc,SAAS,gBAAgB,SAAS;CAC9D,MAAM,UAAU,kBAAkB;CAClC,MAAM,YAAY,CAAC,CAAC;CAEpB,MAAM,OAAO,UAAU,OAAO;CAE9B,MAAM,cAAc,UAClB,OAAO,YAAY,WACjB,QAAQ,OAER,oBAAC,cAAD,EAAc,WAAU,eAAgB,CAAA,IAG1C;CAGF,OACE,qBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,aACA,mDACA,cACA,kBACA,eACA,kBACA,eAAe,kBACf,aAAa,eACb,YAAY,CAAC,OAAO,YAAY,aAAa,GAC7C,cAAc,gBACd,SAAS,UACT,YACG,oBAAoB,UACjB;GACE;GACA;GACA;GACA;GACA;EACF,IACA,CACE,mBACA,gEAEF,IACN,SAEF;EACA,MAAM;EACN,UAAU,YAAY;EACtB,aAAW;EACX,cAAY;EACZ,cAAa,MAAK;GAChB,cAAc,CAAC;EACjB;EACA,GAAI;EAtCN,UAAA,CAwCG,aACA,QACG;;AAEV"}
@@ -0,0 +1,51 @@
1
+ import CodeMirror, { Extension } from "@uiw/react-codemirror";
2
+ //#region src/components/atoms/code-editor.d.ts
3
+ interface Props extends Omit<React.ComponentProps<typeof CodeMirror>, 'value' | 'extensions' | 'onChange' | 'ref' | 'theme'> {
4
+ value: string;
5
+ /** Passed through to CodeMirror; `'100%'` fills the wrapper. */
6
+ height?: string;
7
+ /**
8
+ * `'light'`/`'dark'` pick the VSCode theme pair, `'auto'` (the default)
9
+ * follows the nearest `Config`'s `theme.dark`, and an `Extension` is any
10
+ * CodeMirror theme of your own.
11
+ *
12
+ * This narrows CodeMirror's own meaning for two of the strings — upstream,
13
+ * `'light'`/`'dark'` are *its* built-in themes — so that the two named
14
+ * options match what the component actually renders by default. `'none'`
15
+ * still means what it does upstream: no theme extension, style it yourself.
16
+ */
17
+ theme?: 'light' | 'dark' | 'auto' | 'none' | Extension;
18
+ /** Appended after the built-in JS/TS + line-wrapping extensions. */
19
+ extensions?: Extension[];
20
+ /**
21
+ * Renders a read-through unified diff against `original` instead of a plain
22
+ * document. `mergeControls` defaults to `false` (display-only review, no
23
+ * accept/reject gutters).
24
+ */
25
+ diff?: {
26
+ original: string;
27
+ mergeControls?: boolean;
28
+ };
29
+ /**
30
+ * Formatter run on the Cmd/Ctrl+S value before it is written back. Omitted,
31
+ * the shortcut still fires `onSave` — it just doesn't reformat. Kept as an
32
+ * injected function so this package carries no formatter dependency of its
33
+ * own (prettier is ~9.6 MB); pass the one your app already owns.
34
+ */
35
+ formatCode?: (code: string) => Promise<string>;
36
+ className?: string;
37
+ onChange?: (value: string) => void;
38
+ /** Fires on Cmd/Ctrl+S with the value that was written back. */
39
+ onSave?: (value: string) => void;
40
+ /**
41
+ * `null` on a clean save; a string carries the message a failed `formatCode`
42
+ * threw. Named `onFormatError` rather than `onError` because the base props
43
+ * already carry the DOM `onError` handler, and silently shadowing it would
44
+ * be worse than the extra word.
45
+ */
46
+ onFormatError?: (error: string | null) => void;
47
+ }
48
+ declare const CodeEditor: ({ value, theme, height, className, extensions: extraExtensions, diff, formatCode, onChange, onSave, onFormatError, ...props }: Props) => import("react").JSX.Element;
49
+ //#endregion
50
+ export { Props, CodeEditor as default };
51
+ //# sourceMappingURL=CodeEditor.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CodeEditor.d.mts","names":[],"sources":["../src/components/atoms/code-editor.tsx"],"mappings":";;UA2BiB,cAAc,KAC7B,MAAM,sBAAsB;EAG5B;;EAEA;;;;;;;;;;;EAWA,6CAA6C;;EAE7C,aAAa;;;;;;EAMb;IACE;IACA;;;;;;;;EAQF,cAAc,iBAAiB;EAC/B;EACA,YAAY;;EAEZ,UAAU;;;;;;;EAOV,iBAAiB;;cAGb,eAAc,OAAA,OAAA,QAAA,WAAA,YAAA,iBAAA,MAAA,YAAA,UAAA,QAAA,kBAAA,SAYjB,0BAAK,IAAA"}
@@ -0,0 +1,87 @@
1
+ "use client";
2
+ import { cn } from "./utils.mjs";
3
+ import { useIsDarkActive } from "./providers/config/dark-mode.mjs";
4
+ import { useCallback, useMemo, useRef } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ import { javascript } from "@codemirror/lang-javascript";
7
+ import { unifiedMergeView } from "@codemirror/merge";
8
+ import { vscodeDark, vscodeLight } from "@uiw/codemirror-theme-vscode";
9
+ import CodeMirror from "@uiw/react-codemirror";
10
+ import { EditorView } from "codemirror";
11
+ //#region src/components/atoms/code-editor.tsx
12
+ const CodeEditor = ({ value, theme = "auto", height, className, extensions: extraExtensions, diff, formatCode, onChange, onSave, onFormatError, ...props }) => {
13
+ const editorRef = useRef(null);
14
+ const isDarkActive = useIsDarkActive();
15
+ const resolvedTheme = useMemo(() => {
16
+ if (theme === "auto") return isDarkActive ? vscodeDark : vscodeLight;
17
+ if (theme === "light") return vscodeLight;
18
+ if (theme === "dark") return vscodeDark;
19
+ return theme;
20
+ }, [theme, isDarkActive]);
21
+ const extensions = useMemo(() => {
22
+ const base = [javascript({
23
+ jsx: true,
24
+ typescript: true
25
+ }), EditorView.lineWrapping];
26
+ if (diff) base.push(unifiedMergeView({
27
+ original: diff.original,
28
+ mergeControls: diff.mergeControls ?? false
29
+ }));
30
+ return [...base, ...extraExtensions ?? []];
31
+ }, [diff, extraExtensions]);
32
+ const save = useCallback(async (val) => {
33
+ const view = editorRef.current?.view;
34
+ if (!view) return;
35
+ try {
36
+ const currentLength = view.state.doc.length;
37
+ const cursorPos = view.state.selection.main.head;
38
+ const next = formatCode ? await formatCode(val) : val;
39
+ if (currentLength !== view.state.doc.length) {
40
+ onFormatError?.(null);
41
+ return;
42
+ }
43
+ if (next !== val) view.dispatch(view.state.update({
44
+ changes: {
45
+ from: 0,
46
+ to: currentLength,
47
+ insert: next
48
+ },
49
+ selection: { anchor: Math.min(cursorPos, next.length) }
50
+ }));
51
+ onChange?.(next);
52
+ onSave?.(next);
53
+ onFormatError?.(null);
54
+ } catch (e) {
55
+ onFormatError?.(e instanceof Error ? e.message : String(e));
56
+ }
57
+ }, [
58
+ formatCode,
59
+ onChange,
60
+ onSave,
61
+ onFormatError
62
+ ]);
63
+ const onKeyDown = useCallback((e) => {
64
+ if ((e.metaKey || e.ctrlKey) && e.key === "s") {
65
+ e.preventDefault();
66
+ save(editorRef.current?.view?.state.doc.toString() ?? value);
67
+ }
68
+ }, [save, value]);
69
+ return /* @__PURE__ */ jsx("div", {
70
+ "data-slot": "code-editor",
71
+ className: cn(className),
72
+ onKeyDown,
73
+ children: /* @__PURE__ */ jsx(CodeMirror, {
74
+ ref: editorRef,
75
+ theme: resolvedTheme,
76
+ height: height || "100%",
77
+ value,
78
+ extensions,
79
+ onChange,
80
+ ...props
81
+ })
82
+ });
83
+ };
84
+ //#endregion
85
+ export { CodeEditor as default };
86
+
87
+ //# sourceMappingURL=CodeEditor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CodeEditor.mjs","names":[],"sources":["../src/components/atoms/code-editor.tsx"],"sourcesContent":["'use client';\n\n// Subpath-only component (`@jbpark/ui-kit/CodeEditor`). Deliberately NOT\n// re-exported from `atoms/index.ts` — CodeMirror is an *optional* peer\n// dependency, and an ESM re-export from the root barrel is eager, so\n// `import { Button } from '@jbpark/ui-kit'` would fail to resolve for every\n// consumer that never asked for an editor. Keeping it behind its own subpath\n// is what makes the peer set genuinely opt-in (#346).\nimport { useCallback, useMemo, useRef } from 'react';\n\nimport { javascript } from '@codemirror/lang-javascript';\nimport { unifiedMergeView } from '@codemirror/merge';\nimport { vscodeDark, vscodeLight } from '@uiw/codemirror-theme-vscode';\nimport CodeMirror, {\n type Extension,\n type ReactCodeMirrorRef,\n} from '@uiw/react-codemirror';\nimport { EditorView } from 'codemirror';\n\nimport { cn } from '@repo/ui/utils';\n\n// Relative rather than `@repo/ui/providers`, because this hook is deliberately\n// absent from that barrel — it is a published subpath, and the hook is internal.\n// Both specifiers resolve to the same files, so there is still one Config\n// context.\nimport { useIsDarkActive } from '../../providers/config';\n\nexport interface Props extends Omit<\n React.ComponentProps<typeof CodeMirror>,\n 'value' | 'extensions' | 'onChange' | 'ref' | 'theme'\n> {\n value: string;\n /** Passed through to CodeMirror; `'100%'` fills the wrapper. */\n height?: string;\n /**\n * `'light'`/`'dark'` pick the VSCode theme pair, `'auto'` (the default)\n * follows the nearest `Config`'s `theme.dark`, and an `Extension` is any\n * CodeMirror theme of your own.\n *\n * This narrows CodeMirror's own meaning for two of the strings — upstream,\n * `'light'`/`'dark'` are *its* built-in themes — so that the two named\n * options match what the component actually renders by default. `'none'`\n * still means what it does upstream: no theme extension, style it yourself.\n */\n theme?: 'light' | 'dark' | 'auto' | 'none' | Extension;\n /** Appended after the built-in JS/TS + line-wrapping extensions. */\n extensions?: Extension[];\n /**\n * Renders a read-through unified diff against `original` instead of a plain\n * document. `mergeControls` defaults to `false` (display-only review, no\n * accept/reject gutters).\n */\n diff?: {\n original: string;\n mergeControls?: boolean;\n };\n /**\n * Formatter run on the Cmd/Ctrl+S value before it is written back. Omitted,\n * the shortcut still fires `onSave` — it just doesn't reformat. Kept as an\n * injected function so this package carries no formatter dependency of its\n * own (prettier is ~9.6 MB); pass the one your app already owns.\n */\n formatCode?: (code: string) => Promise<string>;\n className?: string;\n onChange?: (value: string) => void;\n /** Fires on Cmd/Ctrl+S with the value that was written back. */\n onSave?: (value: string) => void;\n /**\n * `null` on a clean save; a string carries the message a failed `formatCode`\n * threw. Named `onFormatError` rather than `onError` because the base props\n * already carry the DOM `onError` handler, and silently shadowing it would\n * be worse than the extra word.\n */\n onFormatError?: (error: string | null) => void;\n}\n\nconst CodeEditor = ({\n value,\n theme = 'auto',\n height,\n className,\n extensions: extraExtensions,\n diff,\n formatCode,\n onChange,\n onSave,\n onFormatError,\n ...props\n}: Props) => {\n const editorRef = useRef<ReactCodeMirrorRef>(null);\n const isDarkActive = useIsDarkActive();\n\n const resolvedTheme = useMemo(() => {\n if (theme === 'auto') {\n return isDarkActive ? vscodeDark : vscodeLight;\n }\n if (theme === 'light') {\n return vscodeLight;\n }\n if (theme === 'dark') {\n return vscodeDark;\n }\n\n // `'none'` and a caller-supplied Extension both mean what they mean\n // upstream, so hand them straight to CodeMirror.\n return theme;\n }, [theme, isDarkActive]);\n\n const extensions = useMemo(() => {\n const base: Extension[] = [\n javascript({ jsx: true, typescript: true }),\n EditorView.lineWrapping,\n ];\n\n if (diff) {\n base.push(\n unifiedMergeView({\n original: diff.original,\n mergeControls: diff.mergeControls ?? false,\n }),\n );\n }\n\n return [...base, ...(extraExtensions ?? [])];\n }, [diff, extraExtensions]);\n\n const save = useCallback(\n async (val: string) => {\n const view = editorRef.current?.view;\n if (!view) {\n return;\n }\n\n try {\n const currentLength = view.state.doc.length;\n const cursorPos = view.state.selection.main.head;\n const next = formatCode ? await formatCode(val) : val;\n\n // Formatting is async, so the document may have moved on while it ran\n // — writing back then would clobber keystrokes typed in the meantime.\n if (currentLength !== view.state.doc.length) {\n onFormatError?.(null);\n return;\n }\n\n // Skip the transaction when the text is unchanged (the common case\n // without a `formatCode`): replacing the doc with itself would push a\n // pointless undo entry. The callbacks still fire, so a save handler\n // sees every Cmd+S.\n if (next !== val) {\n view.dispatch(\n view.state.update({\n changes: { from: 0, to: currentLength, insert: next },\n selection: { anchor: Math.min(cursorPos, next.length) },\n }),\n );\n }\n\n onChange?.(next);\n onSave?.(next);\n onFormatError?.(null);\n } catch (e) {\n onFormatError?.(e instanceof Error ? e.message : String(e));\n }\n },\n [formatCode, onChange, onSave, onFormatError],\n );\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 's') {\n e.preventDefault();\n save(editorRef.current?.view?.state.doc.toString() ?? value);\n }\n },\n [save, value],\n );\n\n return (\n <div\n data-slot=\"code-editor\"\n className={cn(\n className,\n // ...\n )}\n onKeyDown={onKeyDown}\n >\n <CodeMirror\n ref={editorRef}\n theme={resolvedTheme}\n height={height || '100%'}\n value={value}\n extensions={extensions}\n onChange={onChange}\n {...props}\n />\n </div>\n );\n};\n\nexport default CodeEditor;\n"],"mappings":";;;;;;;;;;;AA4EA,MAAM,cAAc,EAClB,OACA,QAAQ,QACR,QACA,WACA,YAAY,iBACZ,MACA,YACA,UACA,QACA,eACA,GAAG,YACQ;CACX,MAAM,YAAY,OAA2B,IAAI;CACjD,MAAM,eAAe,gBAAgB;CAErC,MAAM,gBAAgB,cAAc;EAClC,IAAI,UAAU,QACZ,OAAO,eAAe,aAAa;EAErC,IAAI,UAAU,SACZ,OAAO;EAET,IAAI,UAAU,QACZ,OAAO;EAKT,OAAO;CACT,GAAG,CAAC,OAAO,YAAY,CAAC;CAExB,MAAM,aAAa,cAAc;EAC/B,MAAM,OAAoB,CACxB,WAAW;GAAE,KAAK;GAAM,YAAY;EAAK,CAAC,GAC1C,WAAW,YACb;EAEA,IAAI,MACF,KAAK,KACH,iBAAiB;GACf,UAAU,KAAK;GACf,eAAe,KAAK,iBAAiB;EACvC,CAAC,CACH;EAGF,OAAO,CAAC,GAAG,MAAM,GAAI,mBAAmB,CAAC,CAAE;CAC7C,GAAG,CAAC,MAAM,eAAe,CAAC;CAE1B,MAAM,OAAO,YACX,OAAO,QAAgB;EACrB,MAAM,OAAO,UAAU,SAAS;EAChC,IAAI,CAAC,MACH;EAGF,IAAI;GACF,MAAM,gBAAgB,KAAK,MAAM,IAAI;GACrC,MAAM,YAAY,KAAK,MAAM,UAAU,KAAK;GAC5C,MAAM,OAAO,aAAa,MAAM,WAAW,GAAG,IAAI;GAIlD,IAAI,kBAAkB,KAAK,MAAM,IAAI,QAAQ;IAC3C,gBAAgB,IAAI;IACpB;GACF;GAMA,IAAI,SAAS,KACX,KAAK,SACH,KAAK,MAAM,OAAO;IAChB,SAAS;KAAE,MAAM;KAAG,IAAI;KAAe,QAAQ;IAAK;IACpD,WAAW,EAAE,QAAQ,KAAK,IAAI,WAAW,KAAK,MAAM,EAAE;GACxD,CAAC,CACH;GAGF,WAAW,IAAI;GACf,SAAS,IAAI;GACb,gBAAgB,IAAI;EACtB,SAAS,GAAG;GACV,gBAAgB,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;EAC5D;CACF,GACA;EAAC;EAAY;EAAU;EAAQ;CAAa,CAC9C;CAEA,MAAM,YAAY,aACf,MAA2C;EAC1C,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,KAAK;GAC7C,EAAE,eAAe;GACjB,KAAK,UAAU,SAAS,MAAM,MAAM,IAAI,SAAS,KAAK,KAAK;EAC7D;CACF,GACA,CAAC,MAAM,KAAK,CACd;CAEA,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,SAEF;EACW;EAEX,UAAA,oBAAC,YAAD;GACE,KAAK;GACL,OAAO;GACP,QAAQ,UAAU;GACX;GACK;GACF;GACV,GAAI;EACL,CAAA;CACE,CAAA;AAET"}
package/dist/Tag.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { PresetColor } from "./lib/colors.mjs";
1
+ import { DeprecatedPresetColor, PresetColor } from "./lib/colors.mjs";
2
2
  import * as React$1 from "react";
3
3
  //#region src/components/atoms/tag.d.ts
4
4
  type NativeSpanProps = React$1.ComponentProps<'span'> & {
@@ -14,13 +14,18 @@ interface Props extends Omit<NativeSpanProps, 'variant' | 'color'> {
14
14
  /**
15
15
  * Colour. Every value — the semantic states (`primary`/`success`/`warning`/
16
16
  * `danger`) as well as the palette shared with `Button` (`blue`, `red`,
17
- * `gold`, …) — resolves through the same `data-color` + `--tag-*` system
17
+ * `amber`, …) — resolves through the same `data-color` + `--tag-*` system
18
18
  * (#320), so any of them can be re-themed the same way.
19
19
  *
20
20
  * `success`/`warning` have no `Button` counterpart on purpose: a Tag marks
21
21
  * state, which is why antd's Tag also carries them while its Button does not.
22
+ *
23
+ * Preset hues use Tailwind's colour names (#342). The pre-8.0 spellings
24
+ * `magenta`/`geekblue`/`gold`/`volcano` still work and render unchanged, but
25
+ * are deprecated — use `fuchsia`/`indigo`/`amber` instead (`volcano` has no
26
+ * successor; it is `orange` one lightness step darker).
22
27
  */
23
- color?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | PresetColor;
28
+ color?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | PresetColor | DeprecatedPresetColor;
24
29
  }
25
30
  declare const Tag: ({ className, variant, color, asChild, children, ...props }: Props) => React$1.JSX.Element;
26
31
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"Tag.d.mts","names":[],"sources":["../src/components/atoms/tag.tsx"],"mappings":";;;KAYK,kBAAkB,QAAM;EAC3B;;UAGe,cAAc,KAAK;;;;;;EAMlC;;;;;;;;;;EAUA,mEAC6D;;cA+BzD,QAAO,WAAA,SAAA,OAAA,SAAA,aAAA,SAOV,UAAK,QAAA,IAAA"}
1
+ {"version":3,"file":"Tag.d.mts","names":[],"sources":["../src/components/atoms/tag.tsx"],"mappings":";;;KAYK,kBAAkB,QAAM;EAC3B;;UAGe,cAAc,KAAK;;;;;;EAMlC;;;;;;;;;;;;;;;EAeA,mEAMI,cACA;;cA+BA,QAAO,WAAA,SAAA,OAAA,SAAA,aAAA,SAOV,UAAK,QAAA,IAAA"}
package/dist/Tag.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Tag.mjs","names":[],"sources":["../src/components/atoms/tag.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\n\nimport { useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { INTERACTIVE_CHASSIS } from '../../lib/chassis';\nimport { type PresetColor } from '../../lib/colors';\n\ntype NativeSpanProps = React.ComponentProps<'span'> & {\n asChild?: boolean;\n};\n\nexport interface Props extends Omit<NativeSpanProps, 'variant' | 'color'> {\n /**\n * Fill style, aligned with `Button`'s vocabulary (#320). The pre-7.0\n * `'default'` spelling was removed — use `'filled'`, which renders\n * identically.\n */\n variant?: 'filled' | 'outlined';\n /**\n * Colour. Every value — the semantic states (`primary`/`success`/`warning`/\n * `danger`) as well as the palette shared with `Button` (`blue`, `red`,\n * `gold`, …) — resolves through the same `data-color` + `--tag-*` system\n * (#320), so any of them can be re-themed the same way.\n *\n * `success`/`warning` have no `Button` counterpart on purpose: a Tag marks\n * state, which is why antd's Tag also carries them while its Button does not.\n */\n color?:\n 'default' | 'primary' | 'success' | 'warning' | 'danger' | PresetColor;\n}\n\n// Base absorbed from core/badge's cva base + its `outline` variant, which Tag\n// always rendered as (#278 ③ / #294 Phase 4). The shared focus/aria/centering\n// set lives in INTERACTIVE_CHASSIS; a Tag adds badge geometry and its svg\n// sizing but — unlike Button — no `disabled:*` (a Tag is not disableable). The\n// rounded/padding leftovers are overridden by the atom classes below exactly as\n// before, so the rendered output is unchanged (verified against a pre-refactor\n// render matrix).\nconst TAG_BASE = cn(\n INTERACTIVE_CHASSIS,\n 'border w-fit whitespace-nowrap overflow-hidden',\n 'text-xs font-medium gap-1 transition-[color,box-shadow]',\n '[&>svg]:size-3 [&>svg]:pointer-events-none',\n 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',\n);\n\n// Every colour — semantic states and shared presets alike — resolves from the\n// `--tag-*` custom properties that `globals.css` sets per `data-color` (#320).\n// Before 7.0 only the presets took this path while the states carried bespoke\n// Tailwind classes, which meant `[data-color]` hooks and `--tag-*` overrides\n// worked on some colours but silently not on others.\n//\n// --tag-bg the hue; the border in `outlined`\n// --tag-fg text colour (defaults to --tag-bg)\n// --tag-tint the fill behind `filled` (defaults to a 10% --tag-bg wash)\nconst fillClasses = cn('border-transparent bg-(--tag-tint) text-(--tag-fg)');\n\nconst outlinedClasses = cn('border-(--tag-bg) text-(--tag-fg)');\n\nconst Tag = ({\n className,\n variant,\n color,\n asChild = false,\n children,\n ...props\n}: Props) => {\n const { defaultProps } = useConfig();\n const tagDefaults = defaultProps?.tag as\n Partial<Pick<Props, 'variant' | 'color'>> | undefined;\n // An explicit prop wins over a `Config` default, which wins over the built-in\n // — the same resolution order `Button` uses.\n const resolvedVariant = variant ?? tagDefaults?.variant ?? 'filled';\n const resolvedColor = color ?? tagDefaults?.color ?? 'default';\n const isOutlined = resolvedVariant === 'outlined';\n const Comp = asChild ? Slot : 'span';\n\n return (\n <Comp\n data-slot=\"badge\"\n // Emitted for every colour, not just the presets, so consumers can hook\n // `[data-color]` / `[data-variant]` uniformly. `data-variant` is what\n // lets `globals.css` give `default` a different text colour when\n // outlined without reintroducing a second class path here.\n data-color={resolvedColor}\n data-variant={resolvedVariant}\n className={cn(\n TAG_BASE,\n 'rounded-full px-2.5 py-1',\n 'text-xs font-medium',\n isOutlined ? outlinedClasses : fillClasses,\n className,\n //\n )}\n {...props}\n >\n {children}\n </Comp>\n );\n};\n\nexport default Tag;\n"],"mappings":";;;;;;;;AA2CA,MAAM,WAAW,GACf,qBACA,kDACA,2DACA,8CACA,wEACF;AAWA,MAAM,cAAc,GAAG,oDAAoD;AAE3E,MAAM,kBAAkB,GAAG,mCAAmC;AAE9D,MAAM,OAAO,EACX,WACA,SACA,OACA,UAAU,OACV,UACA,GAAG,YACQ;CACX,MAAM,EAAE,iBAAiB,UAAU;CACnC,MAAM,cAAc,cAAc;CAIlC,MAAM,kBAAkB,WAAW,aAAa,WAAW;CAC3D,MAAM,gBAAgB,SAAS,aAAa,SAAS;CAIrD,OACE,oBAHW,UAAU,OAAO,QAG5B;EACE,aAAU;EAKV,cAAY;EACZ,gBAAc;EACd,WAAW,GACT,UACA,4BACA,uBAfa,oBAAoB,aAgBpB,kBAAkB,aAC/B,SAEF;EACA,GAAI;EAEH;CACG,CAAA;AAEV"}
1
+ {"version":3,"file":"Tag.mjs","names":[],"sources":["../src/components/atoms/tag.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\n\nimport { useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { INTERACTIVE_CHASSIS } from '../../lib/chassis';\nimport { type DeprecatedPresetColor, type PresetColor } from '../../lib/colors';\n\ntype NativeSpanProps = React.ComponentProps<'span'> & {\n asChild?: boolean;\n};\n\nexport interface Props extends Omit<NativeSpanProps, 'variant' | 'color'> {\n /**\n * Fill style, aligned with `Button`'s vocabulary (#320). The pre-7.0\n * `'default'` spelling was removed — use `'filled'`, which renders\n * identically.\n */\n variant?: 'filled' | 'outlined';\n /**\n * Colour. Every value — the semantic states (`primary`/`success`/`warning`/\n * `danger`) as well as the palette shared with `Button` (`blue`, `red`,\n * `amber`, …) — resolves through the same `data-color` + `--tag-*` system\n * (#320), so any of them can be re-themed the same way.\n *\n * `success`/`warning` have no `Button` counterpart on purpose: a Tag marks\n * state, which is why antd's Tag also carries them while its Button does not.\n *\n * Preset hues use Tailwind's colour names (#342). The pre-8.0 spellings\n * `magenta`/`geekblue`/`gold`/`volcano` still work and render unchanged, but\n * are deprecated — use `fuchsia`/`indigo`/`amber` instead (`volcano` has no\n * successor; it is `orange` one lightness step darker).\n */\n color?:\n | 'default'\n | 'primary'\n | 'success'\n | 'warning'\n | 'danger'\n | PresetColor\n | DeprecatedPresetColor;\n}\n\n// Base absorbed from core/badge's cva base + its `outline` variant, which Tag\n// always rendered as (#278 ③ / #294 Phase 4). The shared focus/aria/centering\n// set lives in INTERACTIVE_CHASSIS; a Tag adds badge geometry and its svg\n// sizing but — unlike Button — no `disabled:*` (a Tag is not disableable). The\n// rounded/padding leftovers are overridden by the atom classes below exactly as\n// before, so the rendered output is unchanged (verified against a pre-refactor\n// render matrix).\nconst TAG_BASE = cn(\n INTERACTIVE_CHASSIS,\n 'border w-fit whitespace-nowrap overflow-hidden',\n 'text-xs font-medium gap-1 transition-[color,box-shadow]',\n '[&>svg]:size-3 [&>svg]:pointer-events-none',\n 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',\n);\n\n// Every colour — semantic states and shared presets alike — resolves from the\n// `--tag-*` custom properties that `globals.css` sets per `data-color` (#320).\n// Before 7.0 only the presets took this path while the states carried bespoke\n// Tailwind classes, which meant `[data-color]` hooks and `--tag-*` overrides\n// worked on some colours but silently not on others.\n//\n// --tag-bg the hue; the border in `outlined`\n// --tag-fg text colour (defaults to --tag-bg)\n// --tag-tint the fill behind `filled` (defaults to a 10% --tag-bg wash)\nconst fillClasses = cn('border-transparent bg-(--tag-tint) text-(--tag-fg)');\n\nconst outlinedClasses = cn('border-(--tag-bg) text-(--tag-fg)');\n\nconst Tag = ({\n className,\n variant,\n color,\n asChild = false,\n children,\n ...props\n}: Props) => {\n const { defaultProps } = useConfig();\n const tagDefaults = defaultProps?.tag as\n Partial<Pick<Props, 'variant' | 'color'>> | undefined;\n // An explicit prop wins over a `Config` default, which wins over the built-in\n // — the same resolution order `Button` uses.\n const resolvedVariant = variant ?? tagDefaults?.variant ?? 'filled';\n const resolvedColor = color ?? tagDefaults?.color ?? 'default';\n const isOutlined = resolvedVariant === 'outlined';\n const Comp = asChild ? Slot : 'span';\n\n return (\n <Comp\n data-slot=\"badge\"\n // Emitted for every colour, not just the presets, so consumers can hook\n // `[data-color]` / `[data-variant]` uniformly. `data-variant` is what\n // lets `globals.css` give `default` a different text colour when\n // outlined without reintroducing a second class path here.\n data-color={resolvedColor}\n data-variant={resolvedVariant}\n className={cn(\n TAG_BASE,\n 'rounded-full px-2.5 py-1',\n 'text-xs font-medium',\n isOutlined ? outlinedClasses : fillClasses,\n className,\n //\n )}\n {...props}\n >\n {children}\n </Comp>\n );\n};\n\nexport default Tag;\n"],"mappings":";;;;;;;;AAsDA,MAAM,WAAW,GACf,qBACA,kDACA,2DACA,8CACA,wEACF;AAWA,MAAM,cAAc,GAAG,oDAAoD;AAE3E,MAAM,kBAAkB,GAAG,mCAAmC;AAE9D,MAAM,OAAO,EACX,WACA,SACA,OACA,UAAU,OACV,UACA,GAAG,YACQ;CACX,MAAM,EAAE,iBAAiB,UAAU;CACnC,MAAM,cAAc,cAAc;CAIlC,MAAM,kBAAkB,WAAW,aAAa,WAAW;CAC3D,MAAM,gBAAgB,SAAS,aAAa,SAAS;CAIrD,OACE,oBAHW,UAAU,OAAO,QAG5B;EACE,aAAU;EAKV,cAAY;EACZ,gBAAc;EACd,WAAW,GACT,UACA,4BACA,uBAfa,oBAAoB,aAgBpB,kBAAkB,aAC/B,SAEF;EACA,GAAI;EAEH;CACG,CAAA;AAEV"}
@@ -3,10 +3,43 @@
3
3
  * The shared preset colour palette. `Button` and `Tag` both express these via
4
4
  * the `--btn-*` / `--tag-*` custom-property systems in `globals.css` (keyed off
5
5
  * `data-color`), so the same word means the same hue on either component
6
- * (#320). Keep this list in sync with the `[data-color='…']` rules there.
6
+ * (#320).
7
+ *
8
+ * The names track **Tailwind's own palette names** (#342). They always did
9
+ * track Tailwind's *values* — every preset was a Tailwind hue, just spelled
10
+ * with antd's vocabulary — so four of them said one thing and rendered
11
+ * another: `magenta` rendered Tailwind's `fuchsia`, `geekblue` rendered
12
+ * `indigo`, `gold` rendered `amber`. Aligning the spelling makes the name and
13
+ * the value agree, and lets `globals.css` resolve each entry straight from the
14
+ * matching `--color-*` theme variable instead of a hand-copied hex.
15
+ *
16
+ * Keep this list in sync with the `[data-color='…']` rules there.
7
17
  */
8
- declare const PRESET_COLORS: readonly ["blue", "purple", "cyan", "green", "magenta", "pink", "red", "orange", "yellow", "volcano", "geekblue", "lime", "gold"];
18
+ declare const PRESET_COLORS: readonly ["blue", "purple", "cyan", "green", "fuchsia", "pink", "red", "orange", "yellow", "indigo", "lime", "amber"];
9
19
  type PresetColor = (typeof PRESET_COLORS)[number];
20
+ /**
21
+ * Pre-8.0 spellings, still accepted and still rendering exactly what they
22
+ * rendered before — they are aliases, not a behaviour change:
23
+ *
24
+ * | deprecated | renders | canonical spelling |
25
+ * | ---------- | ----------------- | ----------------------------- |
26
+ * | `magenta` | `--color-fuchsia-500` | `fuchsia` |
27
+ * | `geekblue` | `--color-indigo-500` | `indigo` |
28
+ * | `gold` | `--color-amber-500` | `amber` |
29
+ * | `volcano` | `--color-orange-600` | *(none — see below)* |
30
+ *
31
+ * `volcano` is the odd one out and has no canonical successor. Every other
32
+ * preset is a distinct hue at Tailwind's `500` step, but `volcano` is
33
+ * `orange-600` — the same hue as `orange`, one step darker. That makes it a
34
+ * lightness variant masquerading as a hue, so it doesn't belong in a list
35
+ * whose axis is "one entry per hue". It keeps rendering `orange-600` until the
36
+ * next major; callers who want that exact shade can set `--btn-bg`/`--tag-bg`
37
+ * directly.
38
+ *
39
+ * All four are removed in the next major.
40
+ */
41
+ declare const DEPRECATED_PRESET_COLORS: readonly ["magenta", "geekblue", "gold", "volcano"];
42
+ type DeprecatedPresetColor = (typeof DEPRECATED_PRESET_COLORS)[number];
10
43
  //#endregion
11
- export { PRESET_COLORS, PresetColor };
44
+ export { DEPRECATED_PRESET_COLORS, DeprecatedPresetColor, PRESET_COLORS, PresetColor };
12
45
  //# sourceMappingURL=colors.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"colors.d.mts","names":[],"sources":["../../src/lib/colors.ts"],"mappings":";;;;;;;cAMa;KAgBD,sBAAsB"}
1
+ {"version":3,"file":"colors.d.mts","names":[],"sources":["../../src/lib/colors.ts"],"mappings":";;;;;;;;;;;;;;;;;cAgBa;KAeD,sBAAsB;;;;;;;;;;;;;;;;;;;;;;cAuBrB;KAOD,gCAAgC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../../../src/providers/config/config.tsx"],"mappings":";;;UAuHU;EACR,QAAQ;EACR,SAAS;EACT,qBAAqB;EACrB,gBAAgB;EAChB;EACA,eAAe;EACf;EACA,QAAQ,MAAM;EACd,UAAU,MAAM;;cAGZ,WAAU,OAAA,QAAA,cAAA,eAAA,WAAA,cAAA,WAAA,OAAA,YAUb,0BAAK,IAAA"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../../src/providers/config/config.tsx"],"mappings":";;;UAoEU;EACR,QAAQ;EACR,SAAS;EACT,qBAAqB;EACrB,gBAAgB;EAChB;EACA,eAAe;EACf;EACA,QAAQ,MAAM;EACd,UAAU,MAAM;;cAGZ,WAAU,OAAA,QAAA,cAAA,eAAA,WAAA,cAAA,WAAA,OAAA,YAUb,0BAAK,IAAA"}
@@ -1,21 +1,12 @@
1
1
  "use client";
2
2
  import { cn } from "../../utils.mjs";
3
3
  import { Context, DEFAULT_LOCALE, useConfig } from "./context.mjs";
4
+ import { resolveDark, useSystemPrefersDark } from "./dark-mode.mjs";
4
5
  import { registerRootConfig } from "./registry.mjs";
5
- import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from "react";
6
+ import { useCallback, useEffect, useMemo, useState } from "react";
6
7
  import { DirectionProvider } from "@radix-ui/react-direction";
7
8
  import { jsx } from "react/jsx-runtime";
8
9
  //#region src/providers/config/config.tsx
9
- const DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
10
- const subscribeToSystemColorScheme = (callback) => {
11
- if (typeof window === "undefined") return () => {};
12
- const mql = window.matchMedia(DARK_MEDIA_QUERY);
13
- mql.addEventListener("change", callback);
14
- return () => mql.removeEventListener("change", callback);
15
- };
16
- const getSystemPrefersDark = () => typeof window !== "undefined" && window.matchMedia(DARK_MEDIA_QUERY).matches;
17
- const getServerSnapshot = () => false;
18
- const useSystemPrefersDark = () => useSyncExternalStore(subscribeToSystemColorScheme, getSystemPrefersDark, getServerSnapshot);
19
10
  const tokenToCssVar = {
20
11
  colorPrimary: "--primary",
21
12
  colorPrimaryForeground: "--primary-foreground",
@@ -36,26 +27,8 @@ const tokenToCssVar = {
36
27
  colorInput: "--input",
37
28
  colorRing: "--ring",
38
29
  borderRadius: "--radius",
39
- btnBackground: "--btn-bg",
40
- btnBackgroundHover: "--btn-bg-hover",
41
- btnBackgroundActive: "--btn-bg-active",
42
- btnBorder: "--btn-border",
43
- btnForeground: "--btn-fg",
44
- sidebar: "--sidebar",
45
- sidebarForeground: "--sidebar-foreground",
46
- sidebarPrimary: "--sidebar-primary",
47
- sidebarPrimaryForeground: "--sidebar-primary-foreground",
48
- sidebarAccent: "--sidebar-accent",
49
- sidebarAccentForeground: "--sidebar-accent-foreground",
50
- sidebarBorder: "--sidebar-border",
51
- sidebarRing: "--sidebar-ring",
52
- chart1: "--chart-1",
53
- chart2: "--chart-2",
54
- chart3: "--chart-3",
55
- chart4: "--chart-4",
56
- chart5: "--chart-5",
57
- fontSans: "--font-sans",
58
- fontMono: "--font-mono"
30
+ fontSans: "--ui-font-sans",
31
+ fontMono: "--ui-font-mono"
59
32
  };
60
33
  const buildCssVars = (token) => {
61
34
  if (!token) return {};
@@ -92,7 +65,7 @@ const Config = ({ theme = {}, locale = {}, getContainer, componentSize, directio
92
65
  ...locale
93
66
  }), [parentLocaleKey, localeKey]);
94
67
  const systemPrefersDark = useSystemPrefersDark();
95
- const isDarkActive = mergedTheme.dark === "system" ? systemPrefersDark : mergedTheme.dark === "dark";
68
+ const isDarkActive = resolveDark(mergedTheme.dark, systemPrefersDark);
96
69
  const cssVars = useMemo(() => buildCssVars(isDarkActive ? {
97
70
  ...mergedTheme.token,
98
71
  ...mergedTheme.darkToken
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../../src/providers/config/config.tsx"],"sourcesContent":["'use client';\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from 'react';\n\nimport { DirectionProvider } from '@radix-ui/react-direction';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { Context, DEFAULT_LOCALE, useConfig } from './context';\nimport { registerRootConfig } from './registry';\nimport type {\n ComponentSize,\n DefaultProps,\n Locale,\n ThemeConfig,\n ThemeToken,\n} from './types';\n\nconst DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';\n\nconst subscribeToSystemColorScheme = (callback: () => void) => {\n if (typeof window === 'undefined') {\n return () => {};\n }\n\n const mql = window.matchMedia(DARK_MEDIA_QUERY);\n mql.addEventListener('change', callback);\n return () => mql.removeEventListener('change', callback);\n};\n\nconst getSystemPrefersDark = () =>\n typeof window !== 'undefined' && window.matchMedia(DARK_MEDIA_QUERY).matches;\n\n// No `window` during SSR, so there's no way to know the visitor's actual\n// preference before hydration — assume light (the conservative default)\n// and let useSyncExternalStore correct it client-side once matchMedia is\n// available, same flash-of-incorrect-guess tradeoff already accepted for\n// Sider's breakpoint prop elsewhere in this library.\nconst getServerSnapshot = () => false;\n\nconst useSystemPrefersDark = () =>\n useSyncExternalStore(\n subscribeToSystemColorScheme,\n getSystemPrefersDark,\n getServerSnapshot,\n );\n\nconst tokenToCssVar: Record<keyof ThemeToken, string> = {\n colorPrimary: '--primary',\n colorPrimaryForeground: '--primary-foreground',\n colorBackground: '--background',\n colorForeground: '--foreground',\n colorCard: '--card',\n colorCardForeground: '--card-foreground',\n colorPopover: '--popover',\n colorPopoverForeground: '--popover-foreground',\n colorSecondary: '--secondary',\n colorSecondaryForeground: '--secondary-foreground',\n colorMuted: '--muted',\n colorMutedForeground: '--muted-foreground',\n colorAccent: '--accent',\n colorAccentForeground: '--accent-foreground',\n colorDestructive: '--destructive',\n colorBorder: '--border',\n colorInput: '--input',\n colorRing: '--ring',\n borderRadius: '--radius',\n\n btnBackground: '--btn-bg',\n btnBackgroundHover: '--btn-bg-hover',\n btnBackgroundActive: '--btn-bg-active',\n btnBorder: '--btn-border',\n btnForeground: '--btn-fg',\n\n sidebar: '--sidebar',\n sidebarForeground: '--sidebar-foreground',\n sidebarPrimary: '--sidebar-primary',\n sidebarPrimaryForeground: '--sidebar-primary-foreground',\n sidebarAccent: '--sidebar-accent',\n sidebarAccentForeground: '--sidebar-accent-foreground',\n sidebarBorder: '--sidebar-border',\n sidebarRing: '--sidebar-ring',\n\n chart1: '--chart-1',\n chart2: '--chart-2',\n chart3: '--chart-3',\n chart4: '--chart-4',\n chart5: '--chart-5',\n\n fontSans: '--font-sans',\n fontMono: '--font-mono',\n};\n\nconst buildCssVars = (token?: ThemeToken): React.CSSProperties => {\n if (!token) {\n return {};\n }\n\n const vars: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(token)) {\n if (value === undefined) {\n continue;\n }\n const cssVar = tokenToCssVar[key as keyof ThemeToken];\n if (cssVar) {\n vars[cssVar] = value;\n }\n }\n\n return vars as React.CSSProperties;\n};\n\ninterface Props {\n theme?: ThemeConfig;\n locale?: Locale;\n getContainer?: () => HTMLElement;\n componentSize?: ComponentSize;\n direction?: 'ltr' | 'rtl';\n defaultProps?: DefaultProps;\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n}\n\nconst Config = ({\n theme = {},\n locale = {},\n getContainer,\n componentSize,\n direction,\n defaultProps,\n className,\n style,\n children,\n}: Props) => {\n const parent = useConfig();\n const [wrapperEl, setWrapperEl] = useState<HTMLDivElement | null>(null);\n\n // Only a Config with no Config ancestor of its own registers for the\n // imperative stack (Modal.*/Toast.*) to inherit — see registry.tsx.\n const isRoot = !parent.isConfigured;\n\n // `theme` is typically a fresh inline object literal at the call site\n // (and `parent.theme` inherits the same problem from its own Config\n // ancestor), so depending on them directly meant mergedTheme/contextValue\n // got a new identity on every render regardless of whether the actual\n // values changed — every useConfig() consumer in the tree re-rendered on\n // every render of whatever's above this Config, which for the common\n // app-root usage is everything. Depend on a serialized key instead so\n // the memo only invalidates when the content actually changes; these\n // are small, string/boolean-only objects, so JSON.stringify is cheap.\n const themeKey = JSON.stringify(theme);\n const parentThemeKey = JSON.stringify(parent.theme);\n const localeKey = JSON.stringify(locale);\n const parentLocaleKey = JSON.stringify(parent.locale);\n\n const mergedTheme = useMemo<ThemeConfig>(\n () => ({\n ...parent.theme,\n ...theme,\n token: {\n ...parent.theme.token,\n ...theme.token,\n },\n darkToken: {\n ...parent.theme.darkToken,\n ...theme.darkToken,\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentThemeKey, themeKey],\n );\n\n const mergedLocale = useMemo<Locale>(\n () => ({\n ...parent.locale,\n ...locale,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentLocaleKey, localeKey],\n );\n\n const systemPrefersDark = useSystemPrefersDark();\n const isDarkActive =\n mergedTheme.dark === 'system'\n ? systemPrefersDark\n : mergedTheme.dark === 'dark';\n\n // `darkToken` overrides individual keys of `token` (not a full swap)\n // only while dark mode is actually active.\n const cssVars = useMemo(\n () =>\n buildCssVars(\n isDarkActive\n ? { ...mergedTheme.token, ...mergedTheme.darkToken }\n : mergedTheme.token,\n ),\n [mergedTheme.token, mergedTheme.darkToken, isDarkActive],\n );\n\n const hasCssVars = Object.keys(cssVars).length > 0;\n const hasDarkMode = mergedTheme.dark !== undefined;\n const needsWrapper =\n hasCssVars || hasDarkMode || !!className || !!style || !!direction;\n\n // Resolution order: an explicit `getContainer` prop on this Config wins;\n // otherwise, if this Config renders its own themed wrapper below, that's\n // the default (portaled content appended into it still inherits its CSS\n // custom properties and matches the `.dark` selector — `display:\n // contents` only removes the element from the layout box tree, not from\n // the DOM/cascade); otherwise defer to the parent Config's resolution,\n // so an inner Config that only sets e.g. `locale` doesn't shadow an\n // outer one's theme container.\n const resolvedGetContainer = useCallback((): HTMLElement | undefined => {\n if (getContainer) {\n return getContainer();\n }\n if (needsWrapper && wrapperEl) {\n return wrapperEl;\n }\n return parent.getContainer();\n }, [getContainer, needsWrapper, wrapperEl, parent]);\n\n const resolvedComponentSize = componentSize ?? parent.componentSize;\n const resolvedDirection = direction ?? parent.direction;\n\n const defaultPropsKey = JSON.stringify(defaultProps);\n const parentDefaultPropsKey = JSON.stringify(parent.defaultProps);\n\n const mergedDefaultProps = useMemo<DefaultProps | undefined>(\n () =>\n parent.defaultProps || defaultProps\n ? { ...parent.defaultProps, ...defaultProps }\n : undefined,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentDefaultPropsKey, defaultPropsKey],\n );\n\n const contextValue = useMemo(\n () => ({\n theme: mergedTheme,\n locale: mergedLocale,\n getContainer: resolvedGetContainer,\n componentSize: resolvedComponentSize,\n direction: resolvedDirection,\n defaultProps: mergedDefaultProps,\n isConfigured: true,\n }),\n [\n mergedTheme,\n mergedLocale,\n resolvedGetContainer,\n resolvedComponentSize,\n resolvedDirection,\n mergedDefaultProps,\n ],\n );\n\n useEffect(() => {\n if (!isRoot) {\n return;\n }\n return registerRootConfig(contextValue);\n }, [isRoot, contextValue]);\n\n const content = (\n <Context.Provider value={contextValue}>\n {needsWrapper ? (\n <div\n ref={setWrapperEl}\n dir={direction}\n className={cn(isDarkActive && 'dark', className)}\n style={{\n ...(hasCssVars ? cssVars : undefined),\n ...style,\n // Keeps this element out of the layout box tree — so it\n // doesn't intercept flex/grid child relationships or break\n // height chains like min-h-screen — while custom properties\n // and the `dark` class selector still cascade to children\n // normally (inheritance and box generation are separate\n // mechanisms in CSS). Note this means className/style\n // shouldn't be used here for anything that needs a real box\n // (padding, background, borders); Config's className/style are\n // meant for theming hooks (the `dark` class, arbitrary\n // selector hooks, custom properties), not layout — spread\n // after the caller's `style` so it can't be overridden into\n // something that reintroduces the layout-breaking wrapper this\n // was added to fix in the first place.\n display: 'contents',\n }}\n >\n {children}\n </div>\n ) : (\n children\n )}\n </Context.Provider>\n );\n\n // DirectionProvider is Radix's own mechanism (@radix-ui/react-direction)\n // for propagating `dir` to every primitive that calls its useDirection()\n // internally — Select, Menu (Dropdown/Menu), Accordion (Collapse),\n // RadioGroup, ScrollArea all pick up RTL automatically this way, no\n // per-component wiring needed. Only wraps when this Config's own\n // `direction` prop is set (not the merely-inherited resolved value) —\n // context already cascades on its own, so re-wrapping at every nested\n // Config that doesn't override direction would just be redundant.\n // Components with hardcoded LTR-only layout logic that Radix's Direction\n // context doesn't reach (Popover placement, Splitter, Sider, Drawer)\n // aren't covered by this and would need dedicated follow-up work.\n return direction ? (\n <DirectionProvider dir={direction}>{content}</DirectionProvider>\n ) : (\n content\n );\n};\n\nexport default Config;\nexport { useConfig, DEFAULT_LOCALE };\nexport type {\n Props,\n ComponentSize,\n DefaultProps,\n Locale,\n ThemeConfig,\n ThemeToken,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../../src/providers/config/config.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { DirectionProvider } from '@radix-ui/react-direction';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { Context, DEFAULT_LOCALE, useConfig } from './context';\nimport { resolveDark, useSystemPrefersDark } from './dark-mode';\nimport { registerRootConfig } from './registry';\nimport type {\n ComponentSize,\n DefaultProps,\n Locale,\n ThemeConfig,\n ThemeToken,\n} from './types';\n\nconst tokenToCssVar: Record<keyof ThemeToken, string> = {\n colorPrimary: '--primary',\n colorPrimaryForeground: '--primary-foreground',\n colorBackground: '--background',\n colorForeground: '--foreground',\n colorCard: '--card',\n colorCardForeground: '--card-foreground',\n colorPopover: '--popover',\n colorPopoverForeground: '--popover-foreground',\n colorSecondary: '--secondary',\n colorSecondaryForeground: '--secondary-foreground',\n colorMuted: '--muted',\n colorMutedForeground: '--muted-foreground',\n colorAccent: '--accent',\n colorAccentForeground: '--accent-foreground',\n colorDestructive: '--destructive',\n colorBorder: '--border',\n colorInput: '--input',\n colorRing: '--ring',\n borderRadius: '--radius',\n\n // Not `--font-sans`/`--font-mono`: those are `@theme inline` entries, so\n // Tailwind substitutes their value into each utility rather than referencing\n // them, and a wrapper-level override never applied. `globals.css` reads these\n // hooks ahead of the app's own font variable instead (#343).\n fontSans: '--ui-font-sans',\n fontMono: '--ui-font-mono',\n};\n\nconst buildCssVars = (token?: ThemeToken): React.CSSProperties => {\n if (!token) {\n return {};\n }\n\n const vars: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(token)) {\n if (value === undefined) {\n continue;\n }\n const cssVar = tokenToCssVar[key as keyof ThemeToken];\n if (cssVar) {\n vars[cssVar] = value;\n }\n }\n\n return vars as React.CSSProperties;\n};\n\ninterface Props {\n theme?: ThemeConfig;\n locale?: Locale;\n getContainer?: () => HTMLElement;\n componentSize?: ComponentSize;\n direction?: 'ltr' | 'rtl';\n defaultProps?: DefaultProps;\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n}\n\nconst Config = ({\n theme = {},\n locale = {},\n getContainer,\n componentSize,\n direction,\n defaultProps,\n className,\n style,\n children,\n}: Props) => {\n const parent = useConfig();\n const [wrapperEl, setWrapperEl] = useState<HTMLDivElement | null>(null);\n\n // Only a Config with no Config ancestor of its own registers for the\n // imperative stack (Modal.*/Toast.*) to inherit — see registry.tsx.\n const isRoot = !parent.isConfigured;\n\n // `theme` is typically a fresh inline object literal at the call site\n // (and `parent.theme` inherits the same problem from its own Config\n // ancestor), so depending on them directly meant mergedTheme/contextValue\n // got a new identity on every render regardless of whether the actual\n // values changed — every useConfig() consumer in the tree re-rendered on\n // every render of whatever's above this Config, which for the common\n // app-root usage is everything. Depend on a serialized key instead so\n // the memo only invalidates when the content actually changes; these\n // are small, string/boolean-only objects, so JSON.stringify is cheap.\n const themeKey = JSON.stringify(theme);\n const parentThemeKey = JSON.stringify(parent.theme);\n const localeKey = JSON.stringify(locale);\n const parentLocaleKey = JSON.stringify(parent.locale);\n\n const mergedTheme = useMemo<ThemeConfig>(\n () => ({\n ...parent.theme,\n ...theme,\n token: {\n ...parent.theme.token,\n ...theme.token,\n },\n darkToken: {\n ...parent.theme.darkToken,\n ...theme.darkToken,\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentThemeKey, themeKey],\n );\n\n const mergedLocale = useMemo<Locale>(\n () => ({\n ...parent.locale,\n ...locale,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentLocaleKey, localeKey],\n );\n\n const systemPrefersDark = useSystemPrefersDark();\n const isDarkActive = resolveDark(mergedTheme.dark, systemPrefersDark);\n\n // `darkToken` overrides individual keys of `token` (not a full swap)\n // only while dark mode is actually active.\n const cssVars = useMemo(\n () =>\n buildCssVars(\n isDarkActive\n ? { ...mergedTheme.token, ...mergedTheme.darkToken }\n : mergedTheme.token,\n ),\n [mergedTheme.token, mergedTheme.darkToken, isDarkActive],\n );\n\n const hasCssVars = Object.keys(cssVars).length > 0;\n const hasDarkMode = mergedTheme.dark !== undefined;\n const needsWrapper =\n hasCssVars || hasDarkMode || !!className || !!style || !!direction;\n\n // Resolution order: an explicit `getContainer` prop on this Config wins;\n // otherwise, if this Config renders its own themed wrapper below, that's\n // the default (portaled content appended into it still inherits its CSS\n // custom properties and matches the `.dark` selector — `display:\n // contents` only removes the element from the layout box tree, not from\n // the DOM/cascade); otherwise defer to the parent Config's resolution,\n // so an inner Config that only sets e.g. `locale` doesn't shadow an\n // outer one's theme container.\n const resolvedGetContainer = useCallback((): HTMLElement | undefined => {\n if (getContainer) {\n return getContainer();\n }\n if (needsWrapper && wrapperEl) {\n return wrapperEl;\n }\n return parent.getContainer();\n }, [getContainer, needsWrapper, wrapperEl, parent]);\n\n const resolvedComponentSize = componentSize ?? parent.componentSize;\n const resolvedDirection = direction ?? parent.direction;\n\n const defaultPropsKey = JSON.stringify(defaultProps);\n const parentDefaultPropsKey = JSON.stringify(parent.defaultProps);\n\n const mergedDefaultProps = useMemo<DefaultProps | undefined>(\n () =>\n parent.defaultProps || defaultProps\n ? { ...parent.defaultProps, ...defaultProps }\n : undefined,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentDefaultPropsKey, defaultPropsKey],\n );\n\n const contextValue = useMemo(\n () => ({\n theme: mergedTheme,\n locale: mergedLocale,\n getContainer: resolvedGetContainer,\n componentSize: resolvedComponentSize,\n direction: resolvedDirection,\n defaultProps: mergedDefaultProps,\n isConfigured: true,\n }),\n [\n mergedTheme,\n mergedLocale,\n resolvedGetContainer,\n resolvedComponentSize,\n resolvedDirection,\n mergedDefaultProps,\n ],\n );\n\n useEffect(() => {\n if (!isRoot) {\n return;\n }\n return registerRootConfig(contextValue);\n }, [isRoot, contextValue]);\n\n const content = (\n <Context.Provider value={contextValue}>\n {needsWrapper ? (\n <div\n ref={setWrapperEl}\n dir={direction}\n className={cn(isDarkActive && 'dark', className)}\n style={{\n ...(hasCssVars ? cssVars : undefined),\n ...style,\n // Keeps this element out of the layout box tree — so it\n // doesn't intercept flex/grid child relationships or break\n // height chains like min-h-screen — while custom properties\n // and the `dark` class selector still cascade to children\n // normally (inheritance and box generation are separate\n // mechanisms in CSS). Note this means className/style\n // shouldn't be used here for anything that needs a real box\n // (padding, background, borders); Config's className/style are\n // meant for theming hooks (the `dark` class, arbitrary\n // selector hooks, custom properties), not layout — spread\n // after the caller's `style` so it can't be overridden into\n // something that reintroduces the layout-breaking wrapper this\n // was added to fix in the first place.\n display: 'contents',\n }}\n >\n {children}\n </div>\n ) : (\n children\n )}\n </Context.Provider>\n );\n\n // DirectionProvider is Radix's own mechanism (@radix-ui/react-direction)\n // for propagating `dir` to every primitive that calls its useDirection()\n // internally — Select, Menu (Dropdown/Menu), Accordion (Collapse),\n // RadioGroup, ScrollArea all pick up RTL automatically this way, no\n // per-component wiring needed. Only wraps when this Config's own\n // `direction` prop is set (not the merely-inherited resolved value) —\n // context already cascades on its own, so re-wrapping at every nested\n // Config that doesn't override direction would just be redundant.\n // Components with hardcoded LTR-only layout logic that Radix's Direction\n // context doesn't reach (Popover placement, Splitter, Sider, Drawer)\n // aren't covered by this and would need dedicated follow-up work.\n return direction ? (\n <DirectionProvider dir={direction}>{content}</DirectionProvider>\n ) : (\n content\n );\n};\n\nexport default Config;\nexport { useConfig, DEFAULT_LOCALE };\nexport type {\n Props,\n ComponentSize,\n DefaultProps,\n Locale,\n ThemeConfig,\n ThemeToken,\n};\n"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { useConfig } from "./context.mjs";
3
+ import { useSyncExternalStore } from "react";
4
+ //#region src/providers/config/dark-mode.ts
5
+ const DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
6
+ const subscribeToSystemColorScheme = (callback) => {
7
+ if (typeof window === "undefined") return () => {};
8
+ const mql = window.matchMedia(DARK_MEDIA_QUERY);
9
+ mql.addEventListener("change", callback);
10
+ return () => mql.removeEventListener("change", callback);
11
+ };
12
+ const getSystemPrefersDark = () => typeof window !== "undefined" && window.matchMedia(DARK_MEDIA_QUERY).matches;
13
+ const getServerSnapshot = () => false;
14
+ const useSystemPrefersDark = () => useSyncExternalStore(subscribeToSystemColorScheme, getSystemPrefersDark, getServerSnapshot);
15
+ /**
16
+ * Turns the three-valued `theme.dark` setting into the boolean that decides
17
+ * whether dark treatments actually apply. Kept as a pure function so `Config`
18
+ * can resolve the theme it is about to publish (its merged value isn't in
19
+ * context yet) while consumers resolve the one already in context, without the
20
+ * two drifting apart.
21
+ */
22
+ const resolveDark = (dark, systemPrefersDark) => dark === "system" ? systemPrefersDark : dark === "dark";
23
+ /**
24
+ * Whether dark mode is active for the nearest `Config`, with `'system'`
25
+ * resolved against `prefers-color-scheme`.
26
+ *
27
+ * Internal to the package: components that render *CSS* can just use Tailwind's
28
+ * `dark:` variants against the `.dark` class `Config` puts on its wrapper. This
29
+ * exists for the ones that have to hand a light/dark choice to a third party in
30
+ * JS instead — `CodeEditor` picking a CodeMirror theme extension.
31
+ */
32
+ const useIsDarkActive = () => {
33
+ const { theme } = useConfig();
34
+ return resolveDark(theme.dark, useSystemPrefersDark());
35
+ };
36
+ //#endregion
37
+ export { resolveDark, useIsDarkActive, useSystemPrefersDark };
38
+
39
+ //# sourceMappingURL=dark-mode.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dark-mode.mjs","names":[],"sources":["../../../src/providers/config/dark-mode.ts"],"sourcesContent":["'use client';\n\nimport { useSyncExternalStore } from 'react';\n\nimport { useConfig } from './context';\nimport type { ThemeConfig } from './types';\n\nconst DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';\n\nconst subscribeToSystemColorScheme = (callback: () => void) => {\n if (typeof window === 'undefined') {\n return () => {};\n }\n\n const mql = window.matchMedia(DARK_MEDIA_QUERY);\n mql.addEventListener('change', callback);\n return () => mql.removeEventListener('change', callback);\n};\n\nconst getSystemPrefersDark = () =>\n typeof window !== 'undefined' && window.matchMedia(DARK_MEDIA_QUERY).matches;\n\n// No `window` during SSR, so there's no way to know the visitor's actual\n// preference before hydration — assume light (the conservative default)\n// and let useSyncExternalStore correct it client-side once matchMedia is\n// available, same flash-of-incorrect-guess tradeoff already accepted for\n// Sider's breakpoint prop elsewhere in this library.\nconst getServerSnapshot = () => false;\n\nexport const useSystemPrefersDark = () =>\n useSyncExternalStore(\n subscribeToSystemColorScheme,\n getSystemPrefersDark,\n getServerSnapshot,\n );\n\n/**\n * Turns the three-valued `theme.dark` setting into the boolean that decides\n * whether dark treatments actually apply. Kept as a pure function so `Config`\n * can resolve the theme it is about to publish (its merged value isn't in\n * context yet) while consumers resolve the one already in context, without the\n * two drifting apart.\n */\nexport const resolveDark = (\n dark: ThemeConfig['dark'],\n systemPrefersDark: boolean,\n) => (dark === 'system' ? systemPrefersDark : dark === 'dark');\n\n/**\n * Whether dark mode is active for the nearest `Config`, with `'system'`\n * resolved against `prefers-color-scheme`.\n *\n * Internal to the package: components that render *CSS* can just use Tailwind's\n * `dark:` variants against the `.dark` class `Config` puts on its wrapper. This\n * exists for the ones that have to hand a light/dark choice to a third party in\n * JS instead — `CodeEditor` picking a CodeMirror theme extension.\n */\nexport const useIsDarkActive = () => {\n const { theme } = useConfig();\n\n return resolveDark(theme.dark, useSystemPrefersDark());\n};\n"],"mappings":";;;;AAOA,MAAM,mBAAmB;AAEzB,MAAM,gCAAgC,aAAyB;CAC7D,IAAI,OAAO,WAAW,aACpB,aAAa,CAAC;CAGhB,MAAM,MAAM,OAAO,WAAW,gBAAgB;CAC9C,IAAI,iBAAiB,UAAU,QAAQ;CACvC,aAAa,IAAI,oBAAoB,UAAU,QAAQ;AACzD;AAEA,MAAM,6BACJ,OAAO,WAAW,eAAe,OAAO,WAAW,gBAAgB,CAAC,CAAC;AAOvE,MAAM,0BAA0B;AAEhC,MAAa,6BACX,qBACE,8BACA,sBACA,iBACF;;;;;;;;AASF,MAAa,eACX,MACA,sBACI,SAAS,WAAW,oBAAoB,SAAS;;;;;;;;;;AAWvD,MAAa,wBAAwB;CACnC,MAAM,EAAE,UAAU,UAAU;CAE5B,OAAO,YAAY,MAAM,MAAM,qBAAqB,CAAC;AACvD"}
@@ -1,4 +1,29 @@
1
1
  //#region src/providers/config/types.d.ts
2
+ /**
3
+ * Theme overrides applied as CSS custom properties on `Config`'s wrapper
4
+ * element, inherited by everything inside it.
5
+ *
6
+ * Every token here is verified to actually reach a component. That is worth
7
+ * stating because it did not use to be true: 19 of the previous 40 tokens were
8
+ * inert (#343). Three groups were dropped in 8.0 —
9
+ *
10
+ * - `btnBackground`/`btnBackgroundHover`/`btnBackgroundActive`/`btnBorder`/
11
+ * `btnForeground` — `globals.css` declares `--btn-*` on the button element
12
+ * itself (keyed off `data-color`), and an element's own declaration always
13
+ * beats an inherited one, so a wrapper value could never win. Re-theming a
14
+ * button is done through `colorPrimary`/`colorDestructive` for the semantic
15
+ * colours, or by overriding `--preset-color` in CSS for the preset hues.
16
+ * - `sidebar*` and `chart*` — no component in the library reads them, and the
17
+ * matching utilities (`bg-sidebar`, `bg-chart-1`) are never generated into
18
+ * the published stylesheet, so nothing consumed them. The underlying
19
+ * `--sidebar-*`/`--chart-*` custom properties are still declared in
20
+ * `globals.css` and remain overridable in plain CSS; only the inert token
21
+ * API is gone. They should come back alongside an actual Sidebar/Chart
22
+ * component.
23
+ *
24
+ * `fontSans`/`fontMono` were inert for a different reason and were fixed
25
+ * rather than removed — see below.
26
+ */
2
27
  interface ThemeToken {
3
28
  colorPrimary?: string;
4
29
  colorPrimaryForeground?: string;
@@ -19,24 +44,6 @@ interface ThemeToken {
19
44
  colorInput?: string;
20
45
  colorRing?: string;
21
46
  borderRadius?: string;
22
- btnBackground?: string;
23
- btnBackgroundHover?: string;
24
- btnBackgroundActive?: string;
25
- btnBorder?: string;
26
- btnForeground?: string;
27
- sidebar?: string;
28
- sidebarForeground?: string;
29
- sidebarPrimary?: string;
30
- sidebarPrimaryForeground?: string;
31
- sidebarAccent?: string;
32
- sidebarAccentForeground?: string;
33
- sidebarBorder?: string;
34
- sidebarRing?: string;
35
- chart1?: string;
36
- chart2?: string;
37
- chart3?: string;
38
- chart4?: string;
39
- chart5?: string;
40
47
  fontSans?: string;
41
48
  fontMono?: string;
42
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/providers/config/types.ts"],"mappings":";UAAiB;EAEf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EASA;EAIA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EAGA;EACA;;UAGe;EACf,QAAQ;EAKR,YAAY;EASZ;;UAQe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;KAMU;KAYA,eAAe,eAAe;UAEzB;EACf,OAAO;EACP,QAAQ;EACR;EAUA,oBAAoB;EACpB,gBAAgB;EAChB,eAAe;EAKf"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/providers/config/types.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;UAyBiB;EAEf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EASA;EAMA;EACA;;UAGe;EACf,QAAQ;EAKR,YAAY;EASZ;;UAQe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;KAMU;KAYA,eAAe,eAAe;UAEzB;EACf,OAAO;EACP,QAAQ;EACR;EAUA,oBAAoB;EACpB,gBAAgB;EAChB,eAAe;EAKf"}
package/dist/style.css CHANGED
@@ -2,13 +2,28 @@
2
2
  @layer properties;
3
3
  @layer theme {
4
4
  :root, :host {
5
- --font-sans: var(--font-geist-sans);
6
- --font-mono: var(--font-geist-mono);
5
+ --font-sans: var(--ui-font-sans, var(--font-geist-sans));
6
+ --font-mono: var(--ui-font-mono, var(--font-geist-mono));
7
7
  --color-red-400: oklch(70.4% .191 22.216);
8
+ --color-red-500: oklch(63.7% .237 25.331);
9
+ --color-orange-500: oklch(70.5% .213 47.604);
10
+ --color-orange-600: oklch(64.6% .222 41.116);
11
+ --color-amber-500: oklch(76.9% .188 70.08);
8
12
  --color-yellow-400: oklch(85.2% .199 91.936);
13
+ --color-yellow-500: oklch(79.5% .184 86.047);
14
+ --color-yellow-600: oklch(68.1% .162 75.834);
15
+ --color-lime-500: oklch(76.8% .233 130.85);
9
16
  --color-green-400: oklch(79.2% .209 151.711);
17
+ --color-green-500: oklch(72.3% .219 149.579);
18
+ --color-green-600: oklch(62.7% .194 149.214);
19
+ --color-cyan-500: oklch(71.5% .143 215.221);
10
20
  --color-blue-400: oklch(70.7% .165 254.624);
21
+ --color-blue-500: oklch(62.3% .214 259.815);
11
22
  --color-blue-600: oklch(54.6% .245 262.881);
23
+ --color-indigo-500: oklch(58.5% .233 277.117);
24
+ --color-purple-500: oklch(62.7% .265 303.9);
25
+ --color-fuchsia-500: oklch(66.7% .295 322.15);
26
+ --color-pink-500: oklch(65.6% .241 354.308);
12
27
  --color-gray-100: oklch(96.7% .003 264.542);
13
28
  --color-gray-200: oklch(92.8% .006 264.531);
14
29
  --color-gray-300: oklch(87.2% .01 258.338);
@@ -88,7 +103,56 @@
88
103
  }
89
104
  }
90
105
  @layer components {
106
+ :is([data-slot="button"], [data-slot="badge"])[data-color="blue"] {
107
+ --preset-color: var(--color-blue-500);
108
+ }
109
+ :is([data-slot="button"], [data-slot="badge"])[data-color="purple"] {
110
+ --preset-color: var(--color-purple-500);
111
+ }
112
+ :is([data-slot="button"], [data-slot="badge"])[data-color="cyan"] {
113
+ --preset-color: var(--color-cyan-500);
114
+ }
115
+ :is([data-slot="button"], [data-slot="badge"])[data-color="green"] {
116
+ --preset-color: var(--color-green-500);
117
+ }
118
+ :is([data-slot="button"], [data-slot="badge"])[data-color="fuchsia"] {
119
+ --preset-color: var(--color-fuchsia-500);
120
+ }
121
+ :is([data-slot="button"], [data-slot="badge"])[data-color="pink"] {
122
+ --preset-color: var(--color-pink-500);
123
+ }
124
+ :is([data-slot="button"], [data-slot="badge"])[data-color="red"] {
125
+ --preset-color: var(--color-red-500);
126
+ }
127
+ :is([data-slot="button"], [data-slot="badge"])[data-color="orange"] {
128
+ --preset-color: var(--color-orange-500);
129
+ }
130
+ :is([data-slot="button"], [data-slot="badge"])[data-color="yellow"] {
131
+ --preset-color: var(--color-yellow-500);
132
+ }
133
+ :is([data-slot="button"], [data-slot="badge"])[data-color="indigo"] {
134
+ --preset-color: var(--color-indigo-500);
135
+ }
136
+ :is([data-slot="button"], [data-slot="badge"])[data-color="lime"] {
137
+ --preset-color: var(--color-lime-500);
138
+ }
139
+ :is([data-slot="button"], [data-slot="badge"])[data-color="amber"] {
140
+ --preset-color: var(--color-amber-500);
141
+ }
142
+ :is([data-slot="button"], [data-slot="badge"])[data-color="magenta"] {
143
+ --preset-color: var(--color-fuchsia-500);
144
+ }
145
+ :is([data-slot="button"], [data-slot="badge"])[data-color="geekblue"] {
146
+ --preset-color: var(--color-indigo-500);
147
+ }
148
+ :is([data-slot="button"], [data-slot="badge"])[data-color="gold"] {
149
+ --preset-color: var(--color-amber-500);
150
+ }
151
+ :is([data-slot="button"], [data-slot="badge"])[data-color="volcano"] {
152
+ --preset-color: var(--color-orange-600);
153
+ }
91
154
  [data-slot="button"][data-color] {
155
+ --btn-bg: var(--preset-color);
92
156
  --btn-fg: white;
93
157
  --btn-border: var(--btn-bg);
94
158
  --btn-bg-hover: var(--btn-bg);
@@ -100,7 +164,7 @@
100
164
  --btn-bg-active: color-mix(in oklch, var(--btn-bg), black 22%);
101
165
  }
102
166
  }
103
- [data-slot="button"][data-color="yellow"], [data-slot="button"][data-color="lime"], [data-slot="button"][data-color="gold"] {
167
+ [data-slot="button"][data-color="yellow"], [data-slot="button"][data-color="lime"], [data-slot="button"][data-color="amber"], [data-slot="button"][data-color="gold"] {
104
168
  --btn-fg: black;
105
169
  }
106
170
  [data-slot="button"][data-color="default"] {
@@ -114,46 +178,8 @@
114
178
  [data-slot="button"][data-color="danger"] {
115
179
  --btn-bg: var(--destructive);
116
180
  }
117
- [data-slot="button"][data-color="blue"] {
118
- --btn-bg: #3b82f6;
119
- }
120
- [data-slot="button"][data-color="purple"] {
121
- --btn-bg: #a855f7;
122
- }
123
- [data-slot="button"][data-color="cyan"] {
124
- --btn-bg: #06b6d4;
125
- }
126
- [data-slot="button"][data-color="green"] {
127
- --btn-bg: #22c55e;
128
- }
129
- [data-slot="button"][data-color="magenta"] {
130
- --btn-bg: #d946ef;
131
- }
132
- [data-slot="button"][data-color="pink"] {
133
- --btn-bg: #ec4899;
134
- }
135
- [data-slot="button"][data-color="red"] {
136
- --btn-bg: #ef4444;
137
- }
138
- [data-slot="button"][data-color="orange"] {
139
- --btn-bg: #f97316;
140
- }
141
- [data-slot="button"][data-color="yellow"] {
142
- --btn-bg: #eab308;
143
- }
144
- [data-slot="button"][data-color="volcano"] {
145
- --btn-bg: #ea580c;
146
- }
147
- [data-slot="button"][data-color="geekblue"] {
148
- --btn-bg: #6366f1;
149
- }
150
- [data-slot="button"][data-color="lime"] {
151
- --btn-bg: #84cc16;
152
- }
153
- [data-slot="button"][data-color="gold"] {
154
- --btn-bg: #f59e0b;
155
- }
156
181
  [data-slot="badge"][data-color] {
182
+ --tag-bg: var(--preset-color);
157
183
  --tag-fg: var(--tag-bg);
158
184
  --tag-tint: var(--tag-bg);
159
185
  @supports (color: color-mix(in lab, red, red)) {
@@ -175,57 +201,18 @@
175
201
  --tag-bg: var(--destructive);
176
202
  }
177
203
  [data-slot="badge"][data-color="success"] {
178
- --tag-bg: oklch(72.3% .219 149.579);
179
- --tag-fg: oklch(62.7% .194 149.214);
204
+ --tag-bg: var(--color-green-500);
205
+ --tag-fg: var(--color-green-600);
180
206
  }
181
207
  [data-slot="badge"][data-color="warning"] {
182
- --tag-bg: oklch(79.5% .184 86.047);
183
- --tag-fg: oklch(68.1% .162 75.834);
208
+ --tag-bg: var(--color-yellow-500);
209
+ --tag-fg: var(--color-yellow-600);
184
210
  }
185
211
  :is(.dark, [data-theme="dark"]) [data-slot="badge"][data-color="success"] {
186
- --tag-fg: oklch(79.2% .209 151.711);
212
+ --tag-fg: var(--color-green-400);
187
213
  }
188
214
  :is(.dark, [data-theme="dark"]) [data-slot="badge"][data-color="warning"] {
189
- --tag-fg: oklch(85.2% .199 91.936);
190
- }
191
- [data-slot="badge"][data-color="blue"] {
192
- --tag-bg: #3b82f6;
193
- }
194
- [data-slot="badge"][data-color="purple"] {
195
- --tag-bg: #a855f7;
196
- }
197
- [data-slot="badge"][data-color="cyan"] {
198
- --tag-bg: #06b6d4;
199
- }
200
- [data-slot="badge"][data-color="green"] {
201
- --tag-bg: #22c55e;
202
- }
203
- [data-slot="badge"][data-color="magenta"] {
204
- --tag-bg: #d946ef;
205
- }
206
- [data-slot="badge"][data-color="pink"] {
207
- --tag-bg: #ec4899;
208
- }
209
- [data-slot="badge"][data-color="red"] {
210
- --tag-bg: #ef4444;
211
- }
212
- [data-slot="badge"][data-color="orange"] {
213
- --tag-bg: #f97316;
214
- }
215
- [data-slot="badge"][data-color="yellow"] {
216
- --tag-bg: #eab308;
217
- }
218
- [data-slot="badge"][data-color="volcano"] {
219
- --tag-bg: #ea580c;
220
- }
221
- [data-slot="badge"][data-color="geekblue"] {
222
- --tag-bg: #6366f1;
223
- }
224
- [data-slot="badge"][data-color="lime"] {
225
- --tag-bg: #84cc16;
226
- }
227
- [data-slot="badge"][data-color="gold"] {
228
- --tag-bg: #f59e0b;
215
+ --tag-fg: var(--color-yellow-400);
229
216
  }
230
217
  [data-slot="col"] {
231
218
  flex-basis: calc(var(--col-span-xs) / 24 * 100%);
@@ -1192,6 +1179,9 @@
1192
1179
  .bg-card {
1193
1180
  background-color: var(--card);
1194
1181
  }
1182
+ .bg-chart-1 {
1183
+ background-color: var(--chart-1);
1184
+ }
1195
1185
  .bg-destructive {
1196
1186
  background-color: var(--destructive);
1197
1187
  }
@@ -1219,6 +1209,9 @@
1219
1209
  .bg-secondary {
1220
1210
  background-color: var(--secondary);
1221
1211
  }
1212
+ .bg-sidebar {
1213
+ background-color: var(--sidebar);
1214
+ }
1222
1215
  .bg-transparent {
1223
1216
  background-color: transparent;
1224
1217
  }
@@ -1346,7 +1339,7 @@
1346
1339
  text-align: start;
1347
1340
  }
1348
1341
  .font-mono {
1349
- font-family: var(--font-geist-mono);
1342
+ font-family: var(--ui-font-mono, var(--font-geist-mono));
1350
1343
  }
1351
1344
  .text-2xl {
1352
1345
  font-size: var(--text-2xl);
@@ -2722,7 +2715,7 @@
2722
2715
  padding-block: calc(var(--spacing) * 0.5);
2723
2716
  }
2724
2717
  .\[\&_\.tiptap_code\]\:font-mono .tiptap code {
2725
- font-family: var(--font-geist-mono);
2718
+ font-family: var(--ui-font-mono, var(--font-geist-mono));
2726
2719
  }
2727
2720
  .\[\&_\.tiptap_code\]\:text-\[0\.85em\] .tiptap code {
2728
2721
  font-size: 0.85em;
@@ -2833,7 +2826,7 @@
2833
2826
  padding: calc(var(--spacing) * 3);
2834
2827
  }
2835
2828
  .\[\&_\.tiptap_pre\]\:font-mono .tiptap pre {
2836
- font-family: var(--font-geist-mono);
2829
+ font-family: var(--ui-font-mono, var(--font-geist-mono));
2837
2830
  }
2838
2831
  .\[\&_\.tiptap_pre\]\:text-sm .tiptap pre {
2839
2832
  font-size: var(--text-sm);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbpark/ui-kit",
3
- "version": "7.1.0",
3
+ "version": "8.0.0",
4
4
  "description": "Modern React UI component library built with TypeScript, Tailwind CSS, and Radix UI. Featuring atoms, molecules, organisms and layout templates for building beautiful interfaces.",
5
5
  "keywords": [
6
6
  "react",
@@ -54,6 +54,11 @@
54
54
  "import": "./dist/Button.mjs",
55
55
  "default": "./dist/Button.mjs"
56
56
  },
57
+ "./CodeEditor": {
58
+ "types": "./dist/CodeEditor.d.mts",
59
+ "import": "./dist/CodeEditor.mjs",
60
+ "default": "./dist/CodeEditor.mjs"
61
+ },
57
62
  "./Tag": {
58
63
  "types": "./dist/Tag.d.mts",
59
64
  "import": "./dist/Tag.mjs",
@@ -113,6 +118,8 @@
113
118
  "postpublish": "node scripts/restore-package.mjs"
114
119
  },
115
120
  "devDependencies": {
121
+ "@codemirror/lang-javascript": "^6.2.5",
122
+ "@codemirror/merge": "^6.12.2",
116
123
  "@repo/eslint-config": "workspace:*",
117
124
  "@repo/typescript-config": "workspace:*",
118
125
  "@tailwindcss/postcss": "^4.3.3",
@@ -120,6 +127,9 @@
120
127
  "@types/node": "^26.2.0",
121
128
  "@types/react": "19.2.18",
122
129
  "@types/react-dom": "19.2.4",
130
+ "@uiw/codemirror-theme-vscode": "^4.25.11",
131
+ "@uiw/react-codemirror": "^4.25.11",
132
+ "codemirror": "^6.0.0",
123
133
  "eslint": "^9.39.5",
124
134
  "postcss": "^8.5.26",
125
135
  "rollup-plugin-postcss": "^4.0.2",
@@ -162,7 +172,29 @@
162
172
  "vaul": "^1.1.2"
163
173
  },
164
174
  "peerDependencies": {
175
+ "@codemirror/lang-javascript": "^6.2.5",
176
+ "@codemirror/merge": "^6.12.2",
177
+ "@uiw/codemirror-theme-vscode": "^4.25.11",
178
+ "@uiw/react-codemirror": "^4.25.11",
179
+ "codemirror": "^6.0.0",
165
180
  "react": "^19.0.0",
166
181
  "react-dom": "^19.0.0"
182
+ },
183
+ "peerDependenciesMeta": {
184
+ "@codemirror/lang-javascript": {
185
+ "optional": true
186
+ },
187
+ "@codemirror/merge": {
188
+ "optional": true
189
+ },
190
+ "@uiw/codemirror-theme-vscode": {
191
+ "optional": true
192
+ },
193
+ "@uiw/react-codemirror": {
194
+ "optional": true
195
+ },
196
+ "codemirror": {
197
+ "optional": true
198
+ }
167
199
  }
168
200
  }