@carbonid1/design-system 5.1.0 → 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 (60) 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/src/Badge/Badge.tsx +0 -53
  41. package/src/Button/Button.tsx +0 -67
  42. package/src/Button/Button.types.ts +0 -11
  43. package/src/ContextMenu/ContextMenu.tsx +0 -159
  44. package/src/Kbd/Kbd.tsx +0 -56
  45. package/src/Kbd/Kbd.types.ts +0 -10
  46. package/src/ProgressRing/ProgressRing.consts.ts +0 -4
  47. package/src/ProgressRing/ProgressRing.tsx +0 -68
  48. package/src/Select/Select.test.tsx +0 -129
  49. package/src/Select/Select.tsx +0 -156
  50. package/src/Select/Select.types.ts +0 -30
  51. package/src/Slider/Slider.test.tsx +0 -29
  52. package/src/Slider/Slider.tsx +0 -53
  53. package/src/ThemeCycler/ThemeCycler.tsx +0 -37
  54. package/src/ThemeProvider/ThemeProvider.tsx +0 -18
  55. package/src/Toaster/Toaster.tsx +0 -7
  56. package/src/Tooltip/Tooltip.tsx +0 -107
  57. package/src/helpers/cn/cn.ts +0 -6
  58. package/src/helpers/getModKey/getModKey.test.ts +0 -23
  59. package/src/helpers/getModKey/getModKey.ts +0 -2
  60. package/src/index.ts +0 -36
@@ -0,0 +1,21 @@
1
+ export { Badge, badgeVariants } from './Badge/Badge';
2
+ export type { BadgeProps } from './Badge/Badge.types';
3
+ export { Button, buttonVariants } from './Button/Button';
4
+ export type { ButtonProps } from './Button/Button.types';
5
+ export { Kbd, kbdVariants } from './Kbd/Kbd';
6
+ export type { KbdProps } from './Kbd/Kbd.types';
7
+ export { ProgressRing } from './ProgressRing/ProgressRing';
8
+ export { Slider } from './Slider/Slider';
9
+ export { ContextMenu } from './ContextMenu/ContextMenu';
10
+ export { Select } from './Select/Select';
11
+ export type { SelectOption, SelectGroup, SelectOptionState, SelectProps, } from './Select/Select.types';
12
+ export { Tooltip } from './Tooltip/Tooltip';
13
+ export { ThemeProvider } from './ThemeProvider/ThemeProvider';
14
+ export { ThemeCycler } from './ThemeCycler/ThemeCycler';
15
+ export { Toaster } from './Toaster/Toaster';
16
+ export { toast } from 'sonner';
17
+ export { useTheme } from 'next-themes';
18
+ export { cn } from './helpers/cn/cn';
19
+ export { getModKey } from './helpers/getModKey/getModKey';
20
+ export { cva } from 'class-variance-authority';
21
+ export type { VariantProps } from 'class-variance-authority';
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ export { Badge, badgeVariants } from './Badge/Badge';
2
+ export { Button, buttonVariants } from './Button/Button';
3
+ export { Kbd, kbdVariants } from './Kbd/Kbd';
4
+ export { ProgressRing } from './ProgressRing/ProgressRing';
5
+ export { Slider } from './Slider/Slider';
6
+ export { ContextMenu } from './ContextMenu/ContextMenu';
7
+ export { Select } from './Select/Select';
8
+ export { Tooltip } from './Tooltip/Tooltip';
9
+ export { ThemeProvider } from './ThemeProvider/ThemeProvider';
10
+ export { ThemeCycler } from './ThemeCycler/ThemeCycler';
11
+ export { Toaster } from './Toaster/Toaster';
12
+ export { toast } from 'sonner';
13
+ export { useTheme } from 'next-themes';
14
+ export { cn } from './helpers/cn/cn';
15
+ export { getModKey } from './helpers/getModKey/getModKey';
16
+ export { cva } from 'class-variance-authority';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carbonid1/design-system",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Shared React UI primitives + design tokens (themes, postcss config)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,13 +14,16 @@
14
14
  "**/*.css"
15
15
  ],
16
16
  "exports": {
17
- ".": "./src/index.ts",
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
18
21
  "./themes/reader": "./themes/reader.css",
19
22
  "./themes/dashboard": "./themes/dashboard.css",
20
23
  "./postcss": "./postcss.mjs"
21
24
  },
22
25
  "files": [
23
- "src",
26
+ "dist",
24
27
  "themes",
25
28
  "postcss.mjs",
26
29
  "skills",
@@ -28,43 +31,26 @@
28
31
  ],
29
32
  "dependencies": {
30
33
  "@tailwindcss/postcss": "^4.2.0",
34
+ "class-variance-authority": "^0.7.1",
35
+ "clsx": "^2.1.1",
36
+ "lucide-react": "^1.8.0",
31
37
  "next-themes": "^0.4.6",
38
+ "react-hotkeys-hook": "^5.2.4",
32
39
  "sonner": "^2.0.7",
40
+ "tailwind-merge": "^3.5.0",
33
41
  "tailwindcss": "^4.2.0"
34
42
  },
35
43
  "peerDependencies": {
36
- "@base-ui/react": "^1.0.0-alpha",
37
- "class-variance-authority": "^0.7.0",
38
- "clsx": "^2.0.0",
39
- "lucide-react": ">=0.400.0",
44
+ "@base-ui/react": "^1.4.0",
40
45
  "postcss": "^8.0.0",
41
46
  "react": "^19.0.0",
42
- "react-dom": "^19.0.0",
43
- "react-hotkeys-hook": "^5.0.0",
44
- "tailwind-merge": "^3.0.0"
45
- },
46
- "peerDependenciesMeta": {
47
- "@base-ui/react": {
48
- "optional": true
49
- },
50
- "class-variance-authority": {
51
- "optional": true
52
- },
53
- "clsx": {
54
- "optional": true
55
- },
56
- "lucide-react": {
57
- "optional": true
58
- },
59
- "tailwind-merge": {
60
- "optional": true
61
- }
47
+ "react-dom": "^19.0.0"
62
48
  },
63
49
  "publishConfig": {
64
50
  "access": "public"
65
51
  },
66
52
  "devDependencies": {
67
- "@base-ui/react": "^1.4.0",
53
+ "@base-ui/react": "^1.4.1",
68
54
  "@storybook/addon-docs": "^10.3.5",
69
55
  "@storybook/addon-themes": "^10.3.5",
70
56
  "@storybook/addon-vitest": "^10.3.5",
@@ -76,24 +62,20 @@
76
62
  "@types/react-dom": "^19.2.3",
77
63
  "@vitejs/plugin-react": "^6.0.1",
78
64
  "@vitest/browser-playwright": "^4.1.4",
79
- "class-variance-authority": "^0.7.1",
80
- "clsx": "^2.1.1",
81
65
  "jsdom": "^29.0.2",
82
- "lucide-react": "^1.8.0",
83
66
  "next-themes": "^0.4.6",
84
67
  "playwright": "^1.59.1",
85
- "react": "^19.2.5",
86
- "react-dom": "^19.2.5",
87
- "react-hotkeys-hook": "^5.2.4",
68
+ "react": "^19.2.6",
69
+ "react-dom": "^19.2.6",
88
70
  "sonner": "^2.0.7",
89
71
  "storybook": "^10.3.5",
90
- "tailwind-merge": "^3.5.0",
91
72
  "tailwindcss": "^4.2.2",
92
73
  "vite": "^8.0.8",
93
74
  "vitest": "^4.1.4",
94
75
  "@carbonid1/tsconfig": "0.3.0"
95
76
  },
96
77
  "scripts": {
78
+ "build": "tsc",
97
79
  "storybook": "storybook dev -p 6006",
98
80
  "build-storybook": "storybook build",
99
81
  "test": "vitest --run",
@@ -1,53 +0,0 @@
1
- 'use client'
2
-
3
- import { cn } from '../helpers/cn/cn'
4
- import { cva } from 'class-variance-authority'
5
- import { X } from 'lucide-react'
6
- import type { BadgeProps } from './Badge.types'
7
-
8
- const badgeVariants = cva(
9
- 'inline-flex max-w-full min-w-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',
10
- {
11
- variants: {
12
- variant: {
13
- default: 'bg-muted text-muted-foreground',
14
- primary: 'bg-primary-border/30 text-primary',
15
- success: 'bg-success/15 text-success-foreground',
16
- attention: 'bg-attention-muted text-attention-foreground',
17
- destructive: 'bg-destructive/15 text-destructive',
18
- highlight: 'bg-highlight-muted text-highlight-foreground',
19
- },
20
- },
21
- defaultVariants: { variant: 'default' },
22
- },
23
- )
24
-
25
- const Badge = ({
26
- className,
27
- variant,
28
- onRemove,
29
- removeLabel,
30
- children,
31
- ...props
32
- }: BadgeProps) => {
33
- const resolvedRemoveLabel =
34
- removeLabel ?? (typeof children === 'string' ? `Remove ${children}` : 'Remove')
35
-
36
- return (
37
- <span className={cn(badgeVariants({ variant }), className)} {...props}>
38
- {onRemove ? <span className="truncate">{children}</span> : children}
39
- {onRemove && (
40
- <button
41
- type="button"
42
- aria-label={resolvedRemoveLabel}
43
- onClick={onRemove}
44
- 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"
45
- >
46
- <X className="size-3" aria-hidden="true" />
47
- </button>
48
- )}
49
- </span>
50
- )
51
- }
52
-
53
- export { Badge, badgeVariants }
@@ -1,67 +0,0 @@
1
- 'use client'
2
-
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
- import type { ButtonProps } from './Button.types'
8
-
9
- const buttonVariants = cva(
10
- "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",
11
- {
12
- variants: {
13
- variant: {
14
- ghost: 'hover:bg-accent aria-expanded:bg-accent dark:hover:bg-accent/50',
15
- primary: 'bg-primary text-primary-foreground font-medium hover:bg-primary/90',
16
- outline:
17
- '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',
18
- destructive:
19
- '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',
20
- attention: 'text-attention-foreground hover:bg-attention-muted',
21
- subtle: 'text-muted-foreground hover:text-primary',
22
- danger: 'text-muted-foreground hover:text-destructive',
23
- link: 'text-primary underline-offset-4 hover:underline',
24
- },
25
- size: {
26
- small: 'h-7 gap-1 px-2.5 text-[0.8rem]',
27
- default: 'h-8 gap-1.5 px-2.5',
28
- large: 'h-9 gap-1.5 px-3',
29
- icon: 'size-8 rounded-full',
30
- smallIcon: 'size-6 rounded-full',
31
- largeIcon: 'size-12 rounded-full',
32
- },
33
- },
34
- defaultVariants: {
35
- variant: 'ghost',
36
- size: 'default',
37
- },
38
- },
39
- )
40
-
41
- const Button = ({
42
- className,
43
- variant,
44
- size,
45
- fullWidth,
46
- loading,
47
- disabled,
48
- children,
49
- ...props
50
- }: ButtonProps) => (
51
- <ButtonPrimitive
52
- data-slot="button"
53
- className={cn(
54
- buttonVariants({ variant, size }),
55
- fullWidth && 'w-full justify-start',
56
- className,
57
- )}
58
- disabled={disabled ?? loading}
59
- aria-busy={loading ?? undefined}
60
- {...props}
61
- >
62
- {loading && <Loader2 className="size-4 animate-spin" aria-hidden="true" />}
63
- {children}
64
- </ButtonPrimitive>
65
- )
66
-
67
- export { Button, buttonVariants }
@@ -1,11 +0,0 @@
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
-
5
- type ButtonProps = ButtonPrimitive.Props &
6
- VariantProps<typeof buttonVariants> & {
7
- fullWidth?: boolean
8
- loading?: boolean
9
- }
10
-
11
- export type { ButtonProps }
@@ -1,159 +0,0 @@
1
- 'use client'
2
-
3
- import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu'
4
- import { ChevronRight } from 'lucide-react'
5
- import type { ComponentPropsWithoutRef, Ref } from 'react'
6
- import { cn } from '../helpers/cn/cn'
7
-
8
- type BasePopupProps = ComponentPropsWithoutRef<typeof BaseContextMenu.Popup>
9
- type BasePositionerProps = ComponentPropsWithoutRef<typeof BaseContextMenu.Positioner>
10
- type BaseItemProps = ComponentPropsWithoutRef<typeof BaseContextMenu.Item>
11
- type BaseSeparatorProps = ComponentPropsWithoutRef<typeof BaseContextMenu.Separator>
12
- type BaseGroupLabelProps = ComponentPropsWithoutRef<typeof BaseContextMenu.GroupLabel>
13
- type BaseSubmenuTriggerProps = ComponentPropsWithoutRef<typeof BaseContextMenu.SubmenuTrigger>
14
- type BaseCheckboxItemProps = ComponentPropsWithoutRef<typeof BaseContextMenu.CheckboxItem>
15
- type BaseRadioItemProps = ComponentPropsWithoutRef<typeof BaseContextMenu.RadioItem>
16
-
17
- type ItemVariant = 'default' | 'destructive'
18
-
19
- type PositionerProps = BasePositionerProps & { ref?: Ref<HTMLDivElement> }
20
- type PopupProps = BasePopupProps & { ref?: Ref<HTMLDivElement> }
21
- type SeparatorProps = BaseSeparatorProps & { ref?: Ref<HTMLDivElement> }
22
- type GroupLabelProps = BaseGroupLabelProps & { ref?: Ref<HTMLDivElement> }
23
- type ItemProps = BaseItemProps & { ref?: Ref<HTMLDivElement>; variant?: ItemVariant }
24
- type CheckboxItemProps = BaseCheckboxItemProps & { ref?: Ref<HTMLDivElement> }
25
- type RadioItemProps = BaseRadioItemProps & { ref?: Ref<HTMLDivElement> }
26
- type SubmenuTriggerProps = BaseSubmenuTriggerProps & { ref?: Ref<HTMLDivElement> }
27
-
28
- const popupClasses =
29
- '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'
30
-
31
- const itemBaseClasses =
32
- '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'
33
-
34
- const itemVariantClasses: Record<ItemVariant, string> = {
35
- default:
36
- 'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground',
37
- destructive:
38
- 'text-destructive data-[highlighted]:bg-destructive/10 data-[highlighted]:text-destructive',
39
- }
40
-
41
- const Positioner = ({ className, sideOffset = 4, ...props }: PositionerProps) => (
42
- <BaseContextMenu.Positioner
43
- sideOffset={sideOffset}
44
- className={cn('z-50 outline-hidden', className)}
45
- {...props}
46
- />
47
- )
48
-
49
- const Popup = ({ className, ...props }: PopupProps) => (
50
- <BaseContextMenu.Popup className={cn(popupClasses, className)} {...props} />
51
- )
52
-
53
- const Item = ({ className, variant = 'default', ...props }: ItemProps) => (
54
- <BaseContextMenu.Item
55
- className={cn(itemBaseClasses, itemVariantClasses[variant], className)}
56
- {...props}
57
- />
58
- )
59
-
60
- const Separator = ({ className, ...props }: SeparatorProps) => (
61
- <BaseContextMenu.Separator
62
- className={cn('bg-border my-1 h-px', className)}
63
- {...props}
64
- />
65
- )
66
-
67
- const GroupLabel = ({ className, ...props }: GroupLabelProps) => (
68
- <BaseContextMenu.GroupLabel
69
- className={cn('text-muted-foreground px-2.5 py-1 text-xs', className)}
70
- {...props}
71
- />
72
- )
73
-
74
- const CheckboxItem = ({ className, children, ...props }: CheckboxItemProps) => (
75
- <BaseContextMenu.CheckboxItem
76
- className={cn(itemBaseClasses, itemVariantClasses.default, 'pl-7', className)}
77
- {...props}
78
- >
79
- <span
80
- aria-hidden
81
- className="absolute left-2 flex size-3.5 items-center justify-center"
82
- >
83
- <BaseContextMenu.CheckboxItemIndicator className="size-3.5">
84
- <CheckIcon />
85
- </BaseContextMenu.CheckboxItemIndicator>
86
- </span>
87
- {children}
88
- </BaseContextMenu.CheckboxItem>
89
- )
90
-
91
- const RadioItem = ({ className, children, ...props }: RadioItemProps) => (
92
- <BaseContextMenu.RadioItem
93
- className={cn(itemBaseClasses, itemVariantClasses.default, 'pl-7', className)}
94
- {...props}
95
- >
96
- <span
97
- aria-hidden
98
- className="absolute left-2 flex size-3.5 items-center justify-center"
99
- >
100
- <BaseContextMenu.RadioItemIndicator className="size-3.5">
101
- <DotIcon />
102
- </BaseContextMenu.RadioItemIndicator>
103
- </span>
104
- {children}
105
- </BaseContextMenu.RadioItem>
106
- )
107
-
108
- const SubmenuTrigger = ({ className, children, ...props }: SubmenuTriggerProps) => (
109
- <BaseContextMenu.SubmenuTrigger
110
- className={cn(
111
- itemBaseClasses,
112
- itemVariantClasses.default,
113
- 'data-popup-open:bg-accent data-popup-open:text-accent-foreground pr-2',
114
- className,
115
- )}
116
- {...props}
117
- >
118
- {children}
119
- <ChevronRight className="ml-auto size-4 opacity-60" aria-hidden />
120
- </BaseContextMenu.SubmenuTrigger>
121
- )
122
-
123
- const CheckIcon = () => (
124
- <svg viewBox="0 0 14 14" fill="none" className="size-3.5">
125
- <path
126
- d="M3 7.5 6 10.5 11 4.5"
127
- stroke="currentColor"
128
- strokeWidth="1.75"
129
- strokeLinecap="round"
130
- strokeLinejoin="round"
131
- />
132
- </svg>
133
- )
134
-
135
- const DotIcon = () => (
136
- <svg viewBox="0 0 14 14" className="size-3.5">
137
- <circle cx="7" cy="7" r="3" fill="currentColor" />
138
- </svg>
139
- )
140
-
141
- export const ContextMenu = {
142
- Root: BaseContextMenu.Root,
143
- Trigger: BaseContextMenu.Trigger,
144
- Portal: BaseContextMenu.Portal,
145
- Backdrop: BaseContextMenu.Backdrop,
146
- Positioner,
147
- Popup,
148
- Arrow: BaseContextMenu.Arrow,
149
- Group: BaseContextMenu.Group,
150
- GroupLabel,
151
- Separator,
152
- Item,
153
- LinkItem: BaseContextMenu.LinkItem,
154
- CheckboxItem,
155
- RadioGroup: BaseContextMenu.RadioGroup,
156
- RadioItem,
157
- SubmenuRoot: BaseContextMenu.SubmenuRoot,
158
- SubmenuTrigger,
159
- }
package/src/Kbd/Kbd.tsx DELETED
@@ -1,56 +0,0 @@
1
- 'use client'
2
-
3
- import { getModKey } from '../helpers/getModKey/getModKey'
4
- import { cn } from '../helpers/cn/cn'
5
- import { cva } from 'class-variance-authority'
6
- import type { KbdProps } from './Kbd.types'
7
-
8
- const MOD_SYMBOLS: Record<string, string> = { Cmd: '⌘', Ctrl: 'Ctrl' }
9
-
10
- const KEY_SYMBOLS: Record<string, string> = {
11
- shift: '⇧',
12
- alt: '⌥',
13
- enter: '↵',
14
- backspace: '⌫',
15
- escape: 'Esc',
16
- space: '␣',
17
- left: '←',
18
- right: '→',
19
- up: '↑',
20
- down: '↓',
21
- }
22
-
23
- const resolveKey = (key: string): string => {
24
- const lower = key.toLowerCase()
25
- if (lower === 'mod') return MOD_SYMBOLS[getModKey()] ?? 'Ctrl'
26
- return KEY_SYMBOLS[lower] ?? key
27
- }
28
-
29
- const kbdVariants = cva(
30
- 'inline-flex items-center justify-center rounded-sm border font-mono leading-none select-none',
31
- {
32
- variants: {
33
- size: {
34
- sm: 'min-w-4 px-1 py-0.5 text-[10px] border-foreground/10 bg-foreground/10',
35
- default: 'min-w-5 px-1.5 py-1 text-[11px] border-border bg-muted',
36
- },
37
- },
38
- defaultVariants: { size: 'default' },
39
- },
40
- )
41
-
42
- const Kbd = ({ keys, size, className }: KbdProps) => {
43
- const keyList = Array.isArray(keys) ? keys : [keys]
44
-
45
- return (
46
- <span className="inline-flex items-center gap-0.5" role="presentation">
47
- {keyList.map((key, i) => (
48
- <kbd key={i} className={cn(kbdVariants({ size }), className)}>
49
- {resolveKey(key)}
50
- </kbd>
51
- ))}
52
- </span>
53
- )
54
- }
55
-
56
- export { Kbd, kbdVariants }
@@ -1,10 +0,0 @@
1
- import type { VariantProps } from 'class-variance-authority'
2
- import type { kbdVariants } from './Kbd'
3
-
4
- type KbdProps = {
5
- /** Single key or array of keys for combos. 'mod' auto-resolves to Cmd/Ctrl. */
6
- keys: string | string[]
7
- className?: string
8
- } & VariantProps<typeof kbdVariants>
9
-
10
- export type { KbdProps }
@@ -1,4 +0,0 @@
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
@@ -1,68 +0,0 @@
1
- import { CIRCUMFERENCE, RADIUS, RING_SIZE, STROKE_WIDTH } from './ProgressRing.consts'
2
-
3
- type ProgressRingProps = {
4
- progress: number
5
- colorClass: string
6
- label: string
7
- animate?: boolean
8
- pendingStyle?: 'none' | 'dashed'
9
- testId?: string
10
- }
11
-
12
- const DASH_SEGMENT = CIRCUMFERENCE / 8
13
-
14
- export const ProgressRing = ({
15
- progress,
16
- colorClass,
17
- label,
18
- animate = false,
19
- pendingStyle = 'none',
20
- testId,
21
- }: ProgressRingProps) => {
22
- const MIN_VISIBLE = 0.08
23
- const visibleProgress = animate ? Math.max(progress, MIN_VISIBLE) : progress
24
- const dashoffset = CIRCUMFERENCE * (1 - visibleProgress)
25
-
26
- const shouldSpin = animate && progress < 0.15
27
- const isDashed = !animate && pendingStyle === 'dashed'
28
- const center = RING_SIZE / 2
29
-
30
- return (
31
- <svg
32
- width={RING_SIZE}
33
- height={RING_SIZE}
34
- viewBox={`0 0 ${RING_SIZE} ${RING_SIZE}`}
35
- role="img"
36
- aria-label={label}
37
- className={animate && !shouldSpin ? 'animate-buffer-pulse motion-reduce:animate-none' : ''}
38
- >
39
- {/* Track */}
40
- <circle
41
- cx={center}
42
- cy={center}
43
- r={RADIUS}
44
- fill="none"
45
- stroke="currentColor"
46
- strokeWidth={STROKE_WIDTH}
47
- className={`text-border ${isDashed ? 'animate-ring-drift motion-reduce:animate-none' : ''}`}
48
- strokeDasharray={isDashed ? `${DASH_SEGMENT * 0.6} ${DASH_SEGMENT * 0.4}` : undefined}
49
- style={isDashed ? { transformOrigin: `${center}px ${center}px` } : undefined}
50
- />
51
- {/* Fill */}
52
- <circle
53
- data-testid={testId}
54
- cx={center}
55
- cy={center}
56
- r={RADIUS}
57
- fill="none"
58
- stroke="currentColor"
59
- strokeWidth={STROKE_WIDTH}
60
- strokeDasharray={CIRCUMFERENCE}
61
- strokeDashoffset={dashoffset}
62
- strokeLinecap="round"
63
- className={`${colorClass} transition-[stroke-dashoffset,color] duration-500 ${shouldSpin ? 'animate-ring-spin motion-reduce:animate-none' : ''}`}
64
- style={{ transformOrigin: `${center}px ${center}px` }}
65
- />
66
- </svg>
67
- )
68
- }