@carbonid1/design-system 5.0.3 → 5.2.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.
Files changed (64) hide show
  1. package/dist/Badge/Badge.d.ts +6 -0
  2. package/dist/Badge/Badge.js +23 -0
  3. package/{src/Badge/Badge.types.ts → dist/Badge/Badge.types.d.ts} +8 -11
  4. package/dist/Badge/Badge.types.js +1 -0
  5. package/dist/Button/Button.d.ts +7 -0
  6. package/dist/Button/Button.js +34 -0
  7. package/dist/Button/Button.types.d.ts +8 -0
  8. package/dist/Button/Button.types.js +1 -0
  9. package/dist/ContextMenu/ContextMenu.d.ts +56 -0
  10. package/dist/ContextMenu/ContextMenu.js +40 -0
  11. package/dist/Kbd/Kbd.d.ts +6 -0
  12. package/dist/Kbd/Kbd.js +38 -0
  13. package/dist/Kbd/Kbd.types.d.ts +8 -0
  14. package/dist/Kbd/Kbd.types.js +1 -0
  15. package/dist/ProgressRing/ProgressRing.consts.d.ts +4 -0
  16. package/dist/ProgressRing/ProgressRing.consts.js +4 -0
  17. package/dist/ProgressRing/ProgressRing.d.ts +10 -0
  18. package/dist/ProgressRing/ProgressRing.js +12 -0
  19. package/dist/Select/Select.d.ts +2 -0
  20. package/dist/Select/Select.js +85 -0
  21. package/dist/Select/Select.types.d.ts +26 -0
  22. package/dist/Select/Select.types.js +1 -0
  23. package/dist/Slider/Slider.d.ts +13 -0
  24. package/dist/Slider/Slider.js +5 -0
  25. package/dist/ThemeCycler/ThemeCycler.d.ts +1 -0
  26. package/dist/ThemeCycler/ThemeCycler.js +30 -0
  27. package/dist/ThemeProvider/ThemeProvider.d.ts +5 -0
  28. package/dist/ThemeProvider/ThemeProvider.js +4 -0
  29. package/dist/Toaster/Toaster.d.ts +2 -0
  30. package/dist/Toaster/Toaster.js +4 -0
  31. package/dist/Tooltip/Tooltip.d.ts +15 -0
  32. package/dist/Tooltip/Tooltip.js +59 -0
  33. package/dist/helpers/cn/cn.d.ts +2 -0
  34. package/dist/helpers/cn/cn.js +5 -0
  35. package/dist/helpers/getModKey/getModKey.d.ts +1 -0
  36. package/dist/helpers/getModKey/getModKey.js +1 -0
  37. package/dist/index.d.ts +21 -0
  38. package/dist/index.js +16 -0
  39. package/package.json +17 -35
  40. package/skills/design-system/SKILL.md +17 -0
  41. package/skills/design-system/references/theming.md +53 -14
  42. package/themes/dashboard.css +22 -4
  43. package/themes/reader.css +51 -6
  44. package/src/Badge/Badge.tsx +0 -53
  45. package/src/Button/Button.tsx +0 -67
  46. package/src/Button/Button.types.ts +0 -11
  47. package/src/ContextMenu/ContextMenu.tsx +0 -159
  48. package/src/Kbd/Kbd.tsx +0 -56
  49. package/src/Kbd/Kbd.types.ts +0 -10
  50. package/src/ProgressRing/ProgressRing.consts.ts +0 -4
  51. package/src/ProgressRing/ProgressRing.tsx +0 -68
  52. package/src/Select/Select.test.tsx +0 -129
  53. package/src/Select/Select.tsx +0 -156
  54. package/src/Select/Select.types.ts +0 -30
  55. package/src/Slider/Slider.test.tsx +0 -29
  56. package/src/Slider/Slider.tsx +0 -53
  57. package/src/ThemeCycler/ThemeCycler.tsx +0 -37
  58. package/src/ThemeProvider/ThemeProvider.tsx +0 -18
  59. package/src/Toaster/Toaster.tsx +0 -7
  60. package/src/Tooltip/Tooltip.tsx +0 -107
  61. package/src/helpers/cn/cn.ts +0 -6
  62. package/src/helpers/getModKey/getModKey.test.ts +0 -23
  63. package/src/helpers/getModKey/getModKey.ts +0 -2
  64. package/src/index.ts +0 -36
@@ -0,0 +1,6 @@
1
+ import type { BadgeProps } from './Badge.types';
2
+ declare const badgeVariants: (props?: ({
3
+ variant?: "default" | "primary" | "success" | "attention" | "destructive" | "highlight" | null | undefined;
4
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
5
+ declare const Badge: ({ className, variant, onRemove, removeLabel, children, ...props }: BadgeProps) => import("react/jsx-runtime").JSX.Element;
6
+ export { Badge, badgeVariants };
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../helpers/cn/cn';
4
+ import { cva } from 'class-variance-authority';
5
+ import { X } from 'lucide-react';
6
+ const badgeVariants = cva('inline-flex max-w-full min-w-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium', {
7
+ variants: {
8
+ variant: {
9
+ default: 'bg-muted text-muted-foreground',
10
+ primary: 'bg-primary-border/30 text-primary',
11
+ success: 'bg-success/15 text-success-foreground',
12
+ attention: 'bg-attention-muted text-attention-foreground',
13
+ destructive: 'bg-destructive/15 text-destructive',
14
+ highlight: 'bg-highlight-muted text-highlight-foreground',
15
+ },
16
+ },
17
+ defaultVariants: { variant: 'default' },
18
+ });
19
+ const Badge = ({ className, variant, onRemove, removeLabel, children, ...props }) => {
20
+ const resolvedRemoveLabel = removeLabel ?? (typeof children === 'string' ? `Remove ${children}` : 'Remove');
21
+ return (_jsxs("span", { className: cn(badgeVariants({ variant }), className), ...props, children: [onRemove ? _jsx("span", { className: "truncate", children: children }) : children, onRemove && (_jsx("button", { type: "button", "aria-label": resolvedRemoveLabel, onClick: onRemove, className: "-me-0.5 inline-flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-full opacity-70 hover:opacity-100 focus-visible:ring-2 focus-visible:ring-current focus-visible:ring-offset-1 focus-visible:ring-offset-transparent focus-visible:outline-none", children: _jsx(X, { className: "size-3", "aria-hidden": "true" }) }))] }));
22
+ };
23
+ export { Badge, badgeVariants };
@@ -1,17 +1,14 @@
1
- import type { HTMLAttributes } from 'react'
2
- import type { VariantProps } from 'class-variance-authority'
3
- import type { badgeVariants } from './Badge'
4
-
1
+ import type { HTMLAttributes } from 'react';
2
+ import type { VariantProps } from 'class-variance-authority';
3
+ import type { badgeVariants } from './Badge';
5
4
  /**
6
5
  * `variant` communicates emphasis, not meaning — always pair it with
7
6
  * descriptive text content so the badge isn't relying on color alone.
8
7
  */
9
- type BadgeProps = HTMLAttributes<HTMLSpanElement> &
10
- VariantProps<typeof badgeVariants> & {
8
+ type BadgeProps = HTMLAttributes<HTMLSpanElement> & VariantProps<typeof badgeVariants> & {
11
9
  /** When set, renders an inline dismiss button after the label. */
12
- onRemove?: () => void
10
+ onRemove?: () => void;
13
11
  /** A11y label for the dismiss button. Defaults to `Remove ${children}` when children is a string, otherwise `Remove`. */
14
- removeLabel?: string
15
- }
16
-
17
- export type { BadgeProps }
12
+ removeLabel?: string;
13
+ };
14
+ export type { BadgeProps };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { ButtonProps } from './Button.types';
2
+ declare const buttonVariants: (props?: ({
3
+ variant?: "primary" | "attention" | "destructive" | "link" | "ghost" | "outline" | "subtle" | "danger" | null | undefined;
4
+ size?: "default" | "small" | "large" | "icon" | "smallIcon" | "largeIcon" | null | undefined;
5
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ declare const Button: ({ className, variant, size, fullWidth, loading, disabled, children, ...props }: ButtonProps) => import("react/jsx-runtime").JSX.Element;
7
+ export { Button, buttonVariants };
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../helpers/cn/cn';
4
+ import { Button as ButtonPrimitive } from '@base-ui/react/button';
5
+ import { cva } from 'class-variance-authority';
6
+ import { Loader2 } from 'lucide-react';
7
+ const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center gap-2 rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-hidden select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
8
+ variants: {
9
+ variant: {
10
+ ghost: 'hover:bg-accent aria-expanded:bg-accent dark:hover:bg-accent/50',
11
+ primary: 'bg-primary text-primary-foreground font-medium hover:bg-primary/90',
12
+ outline: 'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
13
+ destructive: 'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40',
14
+ attention: 'text-attention-foreground hover:bg-attention-muted',
15
+ subtle: 'text-muted-foreground hover:text-primary',
16
+ danger: 'text-muted-foreground hover:text-destructive',
17
+ link: 'text-primary underline-offset-4 hover:underline',
18
+ },
19
+ size: {
20
+ small: 'h-7 gap-1 px-2.5 text-[0.8rem]',
21
+ default: 'h-8 gap-1.5 px-2.5',
22
+ large: 'h-9 gap-1.5 px-3',
23
+ icon: 'size-8 rounded-full',
24
+ smallIcon: 'size-6 rounded-full',
25
+ largeIcon: 'size-12 rounded-full',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: 'ghost',
30
+ size: 'default',
31
+ },
32
+ });
33
+ const Button = ({ className, variant, size, fullWidth, loading, disabled, children, ...props }) => (_jsxs(ButtonPrimitive, { "data-slot": "button", className: cn(buttonVariants({ variant, size }), fullWidth && 'w-full justify-start', className), disabled: disabled ?? loading, "aria-busy": loading ?? undefined, ...props, children: [loading && _jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }), children] }));
34
+ export { Button, buttonVariants };
@@ -0,0 +1,8 @@
1
+ import type { Button as ButtonPrimitive } from '@base-ui/react/button';
2
+ import type { VariantProps } from 'class-variance-authority';
3
+ import type { buttonVariants } from './Button';
4
+ type ButtonProps = ButtonPrimitive.Props & VariantProps<typeof buttonVariants> & {
5
+ fullWidth?: boolean;
6
+ loading?: boolean;
7
+ };
8
+ export type { ButtonProps };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu';
2
+ import type { ComponentPropsWithoutRef, Ref } from 'react';
3
+ type BasePopupProps = ComponentPropsWithoutRef<typeof BaseContextMenu.Popup>;
4
+ type BasePositionerProps = ComponentPropsWithoutRef<typeof BaseContextMenu.Positioner>;
5
+ type BaseItemProps = ComponentPropsWithoutRef<typeof BaseContextMenu.Item>;
6
+ type BaseSeparatorProps = ComponentPropsWithoutRef<typeof BaseContextMenu.Separator>;
7
+ type BaseGroupLabelProps = ComponentPropsWithoutRef<typeof BaseContextMenu.GroupLabel>;
8
+ type BaseSubmenuTriggerProps = ComponentPropsWithoutRef<typeof BaseContextMenu.SubmenuTrigger>;
9
+ type BaseCheckboxItemProps = ComponentPropsWithoutRef<typeof BaseContextMenu.CheckboxItem>;
10
+ type BaseRadioItemProps = ComponentPropsWithoutRef<typeof BaseContextMenu.RadioItem>;
11
+ type ItemVariant = 'default' | 'destructive';
12
+ type PositionerProps = BasePositionerProps & {
13
+ ref?: Ref<HTMLDivElement>;
14
+ };
15
+ type PopupProps = BasePopupProps & {
16
+ ref?: Ref<HTMLDivElement>;
17
+ };
18
+ type SeparatorProps = BaseSeparatorProps & {
19
+ ref?: Ref<HTMLDivElement>;
20
+ };
21
+ type GroupLabelProps = BaseGroupLabelProps & {
22
+ ref?: Ref<HTMLDivElement>;
23
+ };
24
+ type ItemProps = BaseItemProps & {
25
+ ref?: Ref<HTMLDivElement>;
26
+ variant?: ItemVariant;
27
+ };
28
+ type CheckboxItemProps = BaseCheckboxItemProps & {
29
+ ref?: Ref<HTMLDivElement>;
30
+ };
31
+ type RadioItemProps = BaseRadioItemProps & {
32
+ ref?: Ref<HTMLDivElement>;
33
+ };
34
+ type SubmenuTriggerProps = BaseSubmenuTriggerProps & {
35
+ ref?: Ref<HTMLDivElement>;
36
+ };
37
+ export declare const ContextMenu: {
38
+ Root: typeof BaseContextMenu.Root;
39
+ Trigger: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react/context-menu").ContextMenuTriggerProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
40
+ Portal: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react/menu").MenuPortalProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
41
+ Backdrop: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react/menu").MenuBackdropProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
42
+ Positioner: ({ className, sideOffset, ...props }: PositionerProps) => import("react/jsx-runtime").JSX.Element;
43
+ Popup: ({ className, ...props }: PopupProps) => import("react/jsx-runtime").JSX.Element;
44
+ Arrow: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react/menu").MenuArrowProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
45
+ Group: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react/menu").MenuGroupProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
46
+ GroupLabel: ({ className, ...props }: GroupLabelProps) => import("react/jsx-runtime").JSX.Element;
47
+ Separator: ({ className, ...props }: SeparatorProps) => import("react/jsx-runtime").JSX.Element;
48
+ Item: ({ className, variant, ...props }: ItemProps) => import("react/jsx-runtime").JSX.Element;
49
+ LinkItem: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react/menu").MenuLinkItemProps, "ref"> & import("react").RefAttributes<Element>>;
50
+ CheckboxItem: ({ className, children, ...props }: CheckboxItemProps) => import("react/jsx-runtime").JSX.Element;
51
+ RadioGroup: import("react").NamedExoticComponent<Omit<import("@base-ui/react/menu").MenuRadioGroupProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
52
+ RadioItem: ({ className, children, ...props }: RadioItemProps) => import("react/jsx-runtime").JSX.Element;
53
+ SubmenuRoot: typeof BaseContextMenu.SubmenuRoot;
54
+ SubmenuTrigger: ({ className, children, ...props }: SubmenuTriggerProps) => import("react/jsx-runtime").JSX.Element;
55
+ };
56
+ export {};
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu';
4
+ import { ChevronRight } from 'lucide-react';
5
+ import { cn } from '../helpers/cn/cn';
6
+ const popupClasses = 'bg-popover text-popover-foreground border-border z-50 min-w-45 origin-[var(--transform-origin)] rounded-lg border py-1 shadow-lg outline-hidden transition-[transform,opacity] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0';
7
+ const itemBaseClasses = 'relative flex h-7 cursor-default items-center gap-1.5 px-2.5 text-[0.8rem] outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4 data-[disabled]:pointer-events-none data-[disabled]:opacity-50';
8
+ const itemVariantClasses = {
9
+ default: 'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground',
10
+ destructive: 'text-destructive data-[highlighted]:bg-destructive/10 data-[highlighted]:text-destructive',
11
+ };
12
+ const Positioner = ({ className, sideOffset = 4, ...props }) => (_jsx(BaseContextMenu.Positioner, { sideOffset: sideOffset, className: cn('z-50 outline-hidden', className), ...props }));
13
+ const Popup = ({ className, ...props }) => (_jsx(BaseContextMenu.Popup, { className: cn(popupClasses, className), ...props }));
14
+ const Item = ({ className, variant = 'default', ...props }) => (_jsx(BaseContextMenu.Item, { className: cn(itemBaseClasses, itemVariantClasses[variant], className), ...props }));
15
+ const Separator = ({ className, ...props }) => (_jsx(BaseContextMenu.Separator, { className: cn('bg-border my-1 h-px', className), ...props }));
16
+ const GroupLabel = ({ className, ...props }) => (_jsx(BaseContextMenu.GroupLabel, { className: cn('text-muted-foreground px-2.5 py-1 text-xs', className), ...props }));
17
+ const CheckboxItem = ({ className, children, ...props }) => (_jsxs(BaseContextMenu.CheckboxItem, { className: cn(itemBaseClasses, itemVariantClasses.default, 'pl-7', className), ...props, children: [_jsx("span", { "aria-hidden": true, className: "absolute left-2 flex size-3.5 items-center justify-center", children: _jsx(BaseContextMenu.CheckboxItemIndicator, { className: "size-3.5", children: _jsx(CheckIcon, {}) }) }), children] }));
18
+ const RadioItem = ({ className, children, ...props }) => (_jsxs(BaseContextMenu.RadioItem, { className: cn(itemBaseClasses, itemVariantClasses.default, 'pl-7', className), ...props, children: [_jsx("span", { "aria-hidden": true, className: "absolute left-2 flex size-3.5 items-center justify-center", children: _jsx(BaseContextMenu.RadioItemIndicator, { className: "size-3.5", children: _jsx(DotIcon, {}) }) }), children] }));
19
+ const SubmenuTrigger = ({ className, children, ...props }) => (_jsxs(BaseContextMenu.SubmenuTrigger, { className: cn(itemBaseClasses, itemVariantClasses.default, 'data-popup-open:bg-accent data-popup-open:text-accent-foreground pr-2', className), ...props, children: [children, _jsx(ChevronRight, { className: "ml-auto size-4 opacity-60", "aria-hidden": true })] }));
20
+ const CheckIcon = () => (_jsx("svg", { viewBox: "0 0 14 14", fill: "none", className: "size-3.5", children: _jsx("path", { d: "M3 7.5 6 10.5 11 4.5", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) }));
21
+ const DotIcon = () => (_jsx("svg", { viewBox: "0 0 14 14", className: "size-3.5", children: _jsx("circle", { cx: "7", cy: "7", r: "3", fill: "currentColor" }) }));
22
+ export const ContextMenu = {
23
+ Root: BaseContextMenu.Root,
24
+ Trigger: BaseContextMenu.Trigger,
25
+ Portal: BaseContextMenu.Portal,
26
+ Backdrop: BaseContextMenu.Backdrop,
27
+ Positioner,
28
+ Popup,
29
+ Arrow: BaseContextMenu.Arrow,
30
+ Group: BaseContextMenu.Group,
31
+ GroupLabel,
32
+ Separator,
33
+ Item,
34
+ LinkItem: BaseContextMenu.LinkItem,
35
+ CheckboxItem,
36
+ RadioGroup: BaseContextMenu.RadioGroup,
37
+ RadioItem,
38
+ SubmenuRoot: BaseContextMenu.SubmenuRoot,
39
+ SubmenuTrigger,
40
+ };
@@ -0,0 +1,6 @@
1
+ import type { KbdProps } from './Kbd.types';
2
+ declare const kbdVariants: (props?: ({
3
+ size?: "default" | "sm" | null | undefined;
4
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
5
+ declare const Kbd: ({ keys, size, className }: KbdProps) => import("react/jsx-runtime").JSX.Element;
6
+ export { Kbd, kbdVariants };
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { getModKey } from '../helpers/getModKey/getModKey';
4
+ import { cn } from '../helpers/cn/cn';
5
+ import { cva } from 'class-variance-authority';
6
+ const MOD_SYMBOLS = { Cmd: '⌘', Ctrl: 'Ctrl' };
7
+ const KEY_SYMBOLS = {
8
+ shift: '⇧',
9
+ alt: '⌥',
10
+ enter: '↵',
11
+ backspace: '⌫',
12
+ escape: 'Esc',
13
+ space: '␣',
14
+ left: '←',
15
+ right: '→',
16
+ up: '↑',
17
+ down: '↓',
18
+ };
19
+ const resolveKey = (key) => {
20
+ const lower = key.toLowerCase();
21
+ if (lower === 'mod')
22
+ return MOD_SYMBOLS[getModKey()] ?? 'Ctrl';
23
+ return KEY_SYMBOLS[lower] ?? key;
24
+ };
25
+ const kbdVariants = cva('inline-flex items-center justify-center rounded-sm border font-mono leading-none select-none', {
26
+ variants: {
27
+ size: {
28
+ sm: 'min-w-4 px-1 py-0.5 text-[10px] border-foreground/10 bg-foreground/10',
29
+ default: 'min-w-5 px-1.5 py-1 text-[11px] border-border bg-muted',
30
+ },
31
+ },
32
+ defaultVariants: { size: 'default' },
33
+ });
34
+ const Kbd = ({ keys, size, className }) => {
35
+ const keyList = Array.isArray(keys) ? keys : [keys];
36
+ return (_jsx("span", { className: "inline-flex items-center gap-0.5", role: "presentation", children: keyList.map((key, i) => (_jsx("kbd", { className: cn(kbdVariants({ size }), className), children: resolveKey(key) }, i))) }));
37
+ };
38
+ export { Kbd, kbdVariants };
@@ -0,0 +1,8 @@
1
+ import type { VariantProps } from 'class-variance-authority';
2
+ import type { kbdVariants } from './Kbd';
3
+ type KbdProps = {
4
+ /** Single key or array of keys for combos. 'mod' auto-resolves to Cmd/Ctrl. */
5
+ keys: string | string[];
6
+ className?: string;
7
+ } & VariantProps<typeof kbdVariants>;
8
+ export type { KbdProps };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare const RING_SIZE = 16;
2
+ export declare const STROKE_WIDTH = 2;
3
+ export declare const RADIUS: number;
4
+ export declare const CIRCUMFERENCE: number;
@@ -0,0 +1,4 @@
1
+ export const RING_SIZE = 16;
2
+ export const STROKE_WIDTH = 2;
3
+ export const RADIUS = (RING_SIZE - STROKE_WIDTH) / 2;
4
+ export const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
@@ -0,0 +1,10 @@
1
+ type ProgressRingProps = {
2
+ progress: number;
3
+ colorClass: string;
4
+ label: string;
5
+ animate?: boolean;
6
+ pendingStyle?: 'none' | 'dashed';
7
+ testId?: string;
8
+ };
9
+ export declare const ProgressRing: ({ progress, colorClass, label, animate, pendingStyle, testId, }: ProgressRingProps) => import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CIRCUMFERENCE, RADIUS, RING_SIZE, STROKE_WIDTH } from './ProgressRing.consts';
3
+ const DASH_SEGMENT = CIRCUMFERENCE / 8;
4
+ export const ProgressRing = ({ progress, colorClass, label, animate = false, pendingStyle = 'none', testId, }) => {
5
+ const MIN_VISIBLE = 0.08;
6
+ const visibleProgress = animate ? Math.max(progress, MIN_VISIBLE) : progress;
7
+ const dashoffset = CIRCUMFERENCE * (1 - visibleProgress);
8
+ const shouldSpin = animate && progress < 0.15;
9
+ const isDashed = !animate && pendingStyle === 'dashed';
10
+ const center = RING_SIZE / 2;
11
+ return (_jsxs("svg", { width: RING_SIZE, height: RING_SIZE, viewBox: `0 0 ${RING_SIZE} ${RING_SIZE}`, role: "img", "aria-label": label, className: animate && !shouldSpin ? 'animate-buffer-pulse motion-reduce:animate-none' : '', children: [_jsx("circle", { cx: center, cy: center, r: RADIUS, fill: "none", stroke: "currentColor", strokeWidth: STROKE_WIDTH, className: `text-border ${isDashed ? 'animate-ring-drift motion-reduce:animate-none' : ''}`, strokeDasharray: isDashed ? `${DASH_SEGMENT * 0.6} ${DASH_SEGMENT * 0.4}` : undefined, style: isDashed ? { transformOrigin: `${center}px ${center}px` } : undefined }), _jsx("circle", { "data-testid": testId, cx: center, cy: center, r: RADIUS, fill: "none", stroke: "currentColor", strokeWidth: STROKE_WIDTH, strokeDasharray: CIRCUMFERENCE, strokeDashoffset: dashoffset, strokeLinecap: "round", className: `${colorClass} transition-[stroke-dashoffset,color] duration-500 ${shouldSpin ? 'animate-ring-spin motion-reduce:animate-none' : ''}`, style: { transformOrigin: `${center}px ${center}px` } })] }));
12
+ };
@@ -0,0 +1,2 @@
1
+ import type { SelectProps } from './Select.types';
2
+ export declare const Select: ({ value, onChange, options, groups, placeholder, id, className, menuClassName, renderOption, onOpenChange, "aria-label": ariaLabel, }: SelectProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronDown } from 'lucide-react';
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
+ export const Select = ({ value, onChange, options, groups, placeholder, id, className, menuClassName, renderOption, onOpenChange, 'aria-label': ariaLabel, }) => {
6
+ const [open, setOpen] = useState(false);
7
+ const [highlightedIndex, setHighlightedIndex] = useState(-1);
8
+ const containerRef = useRef(null);
9
+ const changeOpen = useCallback((next) => {
10
+ setOpen(next);
11
+ onOpenChange?.(next);
12
+ }, [onOpenChange]);
13
+ // Build flat items list with group metadata
14
+ const { items, groupStartIndices } = useMemo(() => {
15
+ const flat = [...(options ?? [])];
16
+ const starts = [];
17
+ for (const group of groups ?? []) {
18
+ starts.push({ index: flat.length, label: group.label });
19
+ flat.push(...group.options);
20
+ }
21
+ return { items: flat, groupStartIndices: starts };
22
+ }, [options, groups]);
23
+ const selectedOption = items.find(o => o.value === value);
24
+ const displayText = selectedOption?.label ?? placeholder ?? value;
25
+ // Click outside to close
26
+ useEffect(() => {
27
+ const handleClickOutside = (e) => {
28
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
29
+ changeOpen(false);
30
+ }
31
+ };
32
+ document.addEventListener('mousedown', handleClickOutside);
33
+ return () => document.removeEventListener('mousedown', handleClickOutside);
34
+ }, [changeOpen]);
35
+ const selectItem = (itemValue) => {
36
+ onChange(itemValue);
37
+ changeOpen(false);
38
+ setHighlightedIndex(-1);
39
+ };
40
+ const handleKeyDown = (e) => {
41
+ if (e.key === 'Escape') {
42
+ changeOpen(false);
43
+ setHighlightedIndex(-1);
44
+ return;
45
+ }
46
+ if (!open) {
47
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {
48
+ e.preventDefault();
49
+ changeOpen(true);
50
+ setHighlightedIndex(0);
51
+ }
52
+ return;
53
+ }
54
+ if (e.key === 'ArrowDown') {
55
+ e.preventDefault();
56
+ setHighlightedIndex(prev => (prev + 1) % items.length);
57
+ }
58
+ else if (e.key === 'ArrowUp') {
59
+ e.preventDefault();
60
+ setHighlightedIndex(prev => (prev - 1 + items.length) % items.length);
61
+ }
62
+ else if (e.key === 'Enter') {
63
+ e.preventDefault();
64
+ const highlighted = items[highlightedIndex];
65
+ if (highlighted) {
66
+ selectItem(highlighted.value);
67
+ }
68
+ }
69
+ };
70
+ const itemClassName = (index, itemValue) => {
71
+ const base = 'px-3 py-2.5 cursor-pointer';
72
+ const highlight = index === highlightedIndex ? 'bg-primary-muted' : 'hover:bg-primary-muted';
73
+ const selected = itemValue === value ? 'font-medium' : '';
74
+ return `${base} ${highlight} ${selected}`;
75
+ };
76
+ return (_jsxs("div", { className: "relative", ref: containerRef, onKeyDown: handleKeyDown, children: [_jsxs("button", { type: "button", id: id, className: `flex items-center justify-between gap-2 ${className ?? ''}`, "aria-expanded": open, "aria-haspopup": "listbox", "aria-label": ariaLabel, onClick: () => changeOpen(!open), children: [_jsx("span", { className: "truncate", children: displayText }), _jsx(ChevronDown, { className: `size-4 shrink-0 transition-transform ${open ? 'rotate-180' : ''}` })] }), open && (_jsx("ul", { role: "listbox", className: `border-border bg-background absolute z-50 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-lg border shadow-lg ${menuClassName ?? ''}`, children: items.map((item, index) => {
77
+ const groupHeader = groupStartIndices.find(g => g.index === index);
78
+ return (_jsxs("li", { role: "presentation", children: [groupHeader && (_jsx("div", { className: "text-muted-foreground px-3 pt-2 pb-1 text-xs font-medium uppercase", children: groupHeader.label })), _jsx("div", { role: "option", "aria-selected": value === item.value, className: itemClassName(index, item.value), onClick: () => selectItem(item.value), onMouseEnter: () => setHighlightedIndex(index), children: renderOption
79
+ ? renderOption(item, {
80
+ highlighted: index === highlightedIndex,
81
+ selected: value === item.value,
82
+ })
83
+ : item.label })] }, item.value));
84
+ }) }))] }));
85
+ };
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react';
2
+ export type SelectOption = {
3
+ value: string;
4
+ label: string;
5
+ };
6
+ export type SelectGroup = {
7
+ label: string;
8
+ options: SelectOption[];
9
+ };
10
+ export type SelectOptionState = {
11
+ highlighted: boolean;
12
+ selected: boolean;
13
+ };
14
+ export type SelectProps = {
15
+ value: string;
16
+ onChange: (value: string) => void;
17
+ options?: SelectOption[];
18
+ groups?: SelectGroup[];
19
+ placeholder?: string;
20
+ id?: string;
21
+ className?: string;
22
+ menuClassName?: string;
23
+ renderOption?: (option: SelectOption, state: SelectOptionState) => ReactNode;
24
+ onOpenChange?: (open: boolean) => void;
25
+ 'aria-label'?: string;
26
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ type SliderProps = {
2
+ value: number;
3
+ onChange: (value: number) => void;
4
+ onCommit?: (value: number) => void;
5
+ min: number;
6
+ max: number;
7
+ step?: number;
8
+ disabled?: boolean;
9
+ 'aria-label'?: string;
10
+ className?: string;
11
+ };
12
+ export declare const Slider: ({ value, onChange, onCommit, min, max, step, disabled, "aria-label": ariaLabel, className, }: SliderProps) => import("react/jsx-runtime").JSX.Element;
13
+ export {};
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../helpers/cn/cn';
4
+ import { Slider as SliderPrimitive } from '@base-ui/react/slider';
5
+ export const Slider = ({ value, onChange, onCommit, min, max, step = 1, disabled, 'aria-label': ariaLabel, className, }) => (_jsx(SliderPrimitive.Root, { value: value, onValueChange: v => onChange(v), onValueCommitted: v => onCommit?.(v), min: min, max: max, step: step, disabled: disabled, "aria-label": ariaLabel, className: cn('relative flex w-full touch-none items-center py-2', className), children: _jsxs(SliderPrimitive.Control, { className: "relative flex h-1.5 w-full items-center", children: [_jsx(SliderPrimitive.Track, { className: "bg-input h-full w-full overflow-hidden rounded-full", children: _jsx(SliderPrimitive.Indicator, { className: "bg-primary h-full rounded-full" }) }), _jsx(SliderPrimitive.Thumb, { className: cn('bg-primary border-background block size-4 rounded-full border-2 shadow-sm', 'focus-visible:ring-ring/50 focus-visible:ring-3 focus-visible:outline-hidden', disabled && 'pointer-events-none opacity-50') })] }) }));
@@ -0,0 +1 @@
1
+ export declare const ThemeCycler: () => null;
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { useTheme } from 'next-themes';
3
+ import { useCallback, useSyncExternalStore } from 'react';
4
+ import { useHotkeys } from 'react-hotkeys-hook';
5
+ import { toast } from 'sonner';
6
+ const THEME_CYCLE = ['system', 'light', 'dark'];
7
+ const THEME_LABELS = {
8
+ system: 'System',
9
+ light: 'Light',
10
+ dark: 'Dark',
11
+ };
12
+ const emptySubscribe = () => () => { };
13
+ const getSnapshot = () => true;
14
+ const getServerSnapshot = () => false;
15
+ export const ThemeCycler = () => {
16
+ const { theme, setTheme } = useTheme();
17
+ const mounted = useSyncExternalStore(emptySubscribe, getSnapshot, getServerSnapshot);
18
+ const cycleTheme = useCallback(() => {
19
+ if (!mounted)
20
+ return;
21
+ const current = theme ?? 'system';
22
+ const currentIndex = THEME_CYCLE.indexOf(current);
23
+ const nextIndex = (currentIndex + 1) % THEME_CYCLE.length;
24
+ const next = THEME_CYCLE[nextIndex] ?? 'system';
25
+ setTheme(next);
26
+ toast(`Theme: ${THEME_LABELS[next]}`, { duration: 2000 });
27
+ }, [theme, setTheme, mounted]);
28
+ useHotkeys('shift+t', cycleTheme, { preventDefault: true });
29
+ return null;
30
+ };
@@ -0,0 +1,5 @@
1
+ type Props = {
2
+ children: React.ReactNode;
3
+ };
4
+ export declare const ThemeProvider: ({ children }: Props) => import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,4 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ThemeProvider as NextThemesProvider } from 'next-themes';
4
+ export const ThemeProvider = ({ children }) => (_jsx(NextThemesProvider, { attribute: "class", defaultTheme: "system", enableSystem: true, disableTransitionOnChange: true, children: children }));
@@ -0,0 +1,2 @@
1
+ import { type ToasterProps } from 'sonner';
2
+ export declare const Toaster: (props: ToasterProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Toaster as Sonner } from 'sonner';
4
+ export const Toaster = (props) => (_jsx(Sonner, { theme: "system", position: "bottom-right", ...props }));
@@ -0,0 +1,15 @@
1
+ import { type ReactElement } from 'react';
2
+ type TooltipProps = {
3
+ label: string;
4
+ shortcut?: string | string[];
5
+ position?: 'top' | 'bottom';
6
+ delay?: number;
7
+ maxWidth?: number;
8
+ disabled?: boolean;
9
+ className?: string;
10
+ children: ReactElement<{
11
+ 'aria-label'?: string;
12
+ }>;
13
+ };
14
+ export declare const Tooltip: ({ label, shortcut, position, delay, maxWidth, disabled, className, children, }: TooltipProps) => import("react/jsx-runtime").JSX.Element;
15
+ export {};
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Kbd } from '../Kbd/Kbd';
4
+ import { cloneElement, useCallback, useEffect, useRef, useState } from 'react';
5
+ const DEFAULT_DELAY = 200;
6
+ export const Tooltip = ({ label, shortcut, position = 'top', delay = DEFAULT_DELAY, maxWidth, disabled, className, children, }) => {
7
+ const [visible, setVisible] = useState(false);
8
+ const timeoutRef = useRef(null);
9
+ const wrapperRef = useRef(null);
10
+ const show = useCallback(() => {
11
+ if (disabled)
12
+ return;
13
+ timeoutRef.current = setTimeout(() => setVisible(true), delay);
14
+ }, [disabled, delay]);
15
+ const hide = useCallback(() => {
16
+ if (timeoutRef.current) {
17
+ clearTimeout(timeoutRef.current);
18
+ timeoutRef.current = null;
19
+ }
20
+ setVisible(false);
21
+ }, []);
22
+ useEffect(() => {
23
+ if (disabled && timeoutRef.current) {
24
+ clearTimeout(timeoutRef.current);
25
+ timeoutRef.current = null;
26
+ }
27
+ }, [disabled]);
28
+ useEffect(() => {
29
+ return () => {
30
+ if (timeoutRef.current)
31
+ clearTimeout(timeoutRef.current);
32
+ };
33
+ }, []);
34
+ useEffect(() => {
35
+ if (!visible)
36
+ return;
37
+ let lastCheck = 0;
38
+ const handlePointerMove = (e) => {
39
+ if (e.timeStamp - lastCheck < 100)
40
+ return;
41
+ lastCheck = e.timeStamp;
42
+ const rect = wrapperRef.current?.getBoundingClientRect();
43
+ if (!rect)
44
+ return;
45
+ const inside = e.clientX >= rect.left &&
46
+ e.clientX <= rect.right &&
47
+ e.clientY >= rect.top &&
48
+ e.clientY <= rect.bottom;
49
+ if (!inside)
50
+ hide();
51
+ };
52
+ document.addEventListener('pointermove', handlePointerMove, { passive: true });
53
+ return () => document.removeEventListener('pointermove', handlePointerMove);
54
+ }, [visible, hide]);
55
+ const positionClasses = position === 'top' ? 'bottom-full mb-2' : 'top-full mt-2';
56
+ return (_jsxs("div", { ref: wrapperRef, className: `relative inline-flex${className ? ` ${className}` : ''}`, onMouseEnter: show, onMouseLeave: hide, onPointerDown: hide, onFocus: show, onBlur: hide, children: [cloneElement(children, {
57
+ 'aria-label': children.props['aria-label'] ?? label,
58
+ }), visible && !disabled && (_jsxs("div", { role: "tooltip", style: maxWidth ? { maxWidth } : undefined, className: `absolute left-1/2 z-50 -translate-x-1/2 ${maxWidth ? 'w-max whitespace-normal' : 'whitespace-nowrap'} bg-foreground text-background pointer-events-none flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs shadow-lg ${positionClasses}`, children: [label, shortcut && (_jsx(Kbd, { keys: shortcut, size: "sm", className: "bg-background/15 border-transparent" }))] }))] }));
59
+ };
@@ -0,0 +1,2 @@
1
+ import { type ClassValue } from 'clsx';
2
+ export declare function cn(...inputs: ClassValue[]): string;
@@ -0,0 +1,5 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -0,0 +1 @@
1
+ export declare const getModKey: () => string;
@@ -0,0 +1 @@
1
+ export const getModKey = () => typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.userAgent) ? 'Cmd' : 'Ctrl';