@carbonid1/design-system 5.1.0 → 5.2.1

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 (62) 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/themes/dashboard.css +4 -2
  41. package/themes/reader.css +4 -2
  42. package/src/Badge/Badge.tsx +0 -53
  43. package/src/Button/Button.tsx +0 -67
  44. package/src/Button/Button.types.ts +0 -11
  45. package/src/ContextMenu/ContextMenu.tsx +0 -159
  46. package/src/Kbd/Kbd.tsx +0 -56
  47. package/src/Kbd/Kbd.types.ts +0 -10
  48. package/src/ProgressRing/ProgressRing.consts.ts +0 -4
  49. package/src/ProgressRing/ProgressRing.tsx +0 -68
  50. package/src/Select/Select.test.tsx +0 -129
  51. package/src/Select/Select.tsx +0 -156
  52. package/src/Select/Select.types.ts +0 -30
  53. package/src/Slider/Slider.test.tsx +0 -29
  54. package/src/Slider/Slider.tsx +0 -53
  55. package/src/ThemeCycler/ThemeCycler.tsx +0 -37
  56. package/src/ThemeProvider/ThemeProvider.tsx +0 -18
  57. package/src/Toaster/Toaster.tsx +0 -7
  58. package/src/Tooltip/Tooltip.tsx +0 -107
  59. package/src/helpers/cn/cn.ts +0 -6
  60. package/src/helpers/getModKey/getModKey.test.ts +0 -23
  61. package/src/helpers/getModKey/getModKey.ts +0 -2
  62. package/src/index.ts +0 -36
@@ -1,129 +0,0 @@
1
- import { fireEvent, render, screen } from '@testing-library/react'
2
- import { StrictMode } from 'react'
3
- import { describe, expect, it, vi } from 'vitest'
4
- import { Select } from './Select'
5
- import type { SelectGroup } from './Select.types'
6
-
7
- const options = [
8
- { value: 'a', label: 'Alpha' },
9
- { value: 'b', label: 'Bravo' },
10
- { value: 'c', label: 'Charlie' },
11
- ]
12
-
13
- const groups: SelectGroup[] = [
14
- {
15
- label: 'Letters',
16
- options: [
17
- { value: 'a', label: 'Alpha' },
18
- { value: 'b', label: 'Bravo' },
19
- ],
20
- },
21
- {
22
- label: 'Numbers',
23
- options: [
24
- { value: '1', label: 'One' },
25
- { value: '2', label: 'Two' },
26
- ],
27
- },
28
- ]
29
-
30
- const renderWith = (ui: React.ReactElement) => render(<StrictMode>{ui}</StrictMode>)
31
-
32
- describe('Select', () => {
33
- it('renders selected label and chevron in button', () => {
34
- renderWith(<Select options={options} value="b" onChange={vi.fn()} />)
35
-
36
- const button = screen.getByRole('button', { name: /bravo/i })
37
- expect(button).toBeInTheDocument()
38
- expect(button.querySelector('svg')).toBeInTheDocument()
39
- })
40
-
41
- it('opens menu on click showing all options', () => {
42
- renderWith(<Select options={options} value="a" onChange={vi.fn()} />)
43
-
44
- fireEvent.click(screen.getByRole('button', { name: /alpha/i }))
45
-
46
- expect(screen.getByRole('listbox')).toBeInTheDocument()
47
- expect(screen.getByRole('option', { name: /alpha/i })).toBeInTheDocument()
48
- expect(screen.getByRole('option', { name: /bravo/i })).toBeInTheDocument()
49
- expect(screen.getByRole('option', { name: /charlie/i })).toBeInTheDocument()
50
- })
51
-
52
- it('calls onChange and closes menu when an option is selected', () => {
53
- const onChange = vi.fn()
54
- renderWith(<Select options={options} value="a" onChange={onChange} />)
55
-
56
- fireEvent.click(screen.getByRole('button', { name: /alpha/i }))
57
- fireEvent.click(screen.getByRole('option', { name: /charlie/i }))
58
-
59
- expect(onChange).toHaveBeenCalledWith('c')
60
- expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
61
- })
62
-
63
- it('closes on Escape key', () => {
64
- renderWith(<Select options={options} value="a" onChange={vi.fn()} />)
65
-
66
- const button = screen.getByRole('button', { name: /alpha/i })
67
- fireEvent.click(button)
68
- expect(screen.getByRole('listbox')).toBeInTheDocument()
69
-
70
- fireEvent.keyDown(button, { key: 'Escape' })
71
- expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
72
- })
73
-
74
- it('closes on click outside', () => {
75
- renderWith(<Select options={options} value="a" onChange={vi.fn()} />)
76
-
77
- fireEvent.click(screen.getByRole('button', { name: /alpha/i }))
78
- expect(screen.getByRole('listbox')).toBeInTheDocument()
79
-
80
- fireEvent.mouseDown(document.body)
81
- expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
82
- })
83
-
84
- it('navigates with arrow keys and selects with Enter', () => {
85
- const onChange = vi.fn()
86
- renderWith(<Select options={options} value="a" onChange={onChange} />)
87
-
88
- const button = screen.getByRole('button', { name: /alpha/i })
89
- fireEvent.click(button)
90
-
91
- fireEvent.keyDown(button, { key: 'ArrowDown' })
92
- fireEvent.keyDown(button, { key: 'ArrowDown' })
93
- fireEvent.keyDown(button, { key: 'Enter' })
94
-
95
- expect(onChange).toHaveBeenCalledWith('b')
96
- })
97
-
98
- it('renders groups with headers', () => {
99
- renderWith(<Select groups={groups} value="a" onChange={vi.fn()} />)
100
-
101
- fireEvent.click(screen.getByRole('button', { name: /alpha/i }))
102
-
103
- expect(screen.getByText('Letters')).toBeInTheDocument()
104
- expect(screen.getByText('Numbers')).toBeInTheDocument()
105
- expect(screen.getByRole('option', { name: /one/i })).toBeInTheDocument()
106
- })
107
-
108
- it('uses renderOption for custom item rendering', () => {
109
- renderWith(
110
- <Select
111
- options={options}
112
- value="a"
113
- onChange={vi.fn()}
114
- renderOption={option => <span data-testid="custom">{option.label.toUpperCase()}</span>}
115
- />,
116
- )
117
-
118
- fireEvent.click(screen.getByRole('button', { name: /alpha/i }))
119
-
120
- expect(screen.getAllByTestId('custom')).toHaveLength(3)
121
- expect(screen.getByText('BRAVO')).toBeInTheDocument()
122
- })
123
-
124
- it('shows placeholder when value does not match any option', () => {
125
- renderWith(<Select options={options} value="x" onChange={vi.fn()} placeholder="Pick one" />)
126
-
127
- expect(screen.getByRole('button', { name: /pick one/i })).toBeInTheDocument()
128
- })
129
- })
@@ -1,156 +0,0 @@
1
- 'use client'
2
-
3
- import { ChevronDown } from 'lucide-react'
4
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
5
- import type { SelectOption, SelectProps } from './Select.types'
6
-
7
- export const Select = ({
8
- value,
9
- onChange,
10
- options,
11
- groups,
12
- placeholder,
13
- id,
14
- className,
15
- menuClassName,
16
- renderOption,
17
- onOpenChange,
18
- 'aria-label': ariaLabel,
19
- }: SelectProps) => {
20
- const [open, setOpen] = useState(false)
21
- const [highlightedIndex, setHighlightedIndex] = useState(-1)
22
- const containerRef = useRef<HTMLDivElement>(null)
23
-
24
- const changeOpen = useCallback(
25
- (next: boolean) => {
26
- setOpen(next)
27
- onOpenChange?.(next)
28
- },
29
- [onOpenChange],
30
- )
31
-
32
- // Build flat items list with group metadata
33
- const { items, groupStartIndices } = useMemo(() => {
34
- const flat: SelectOption[] = [...(options ?? [])]
35
- const starts: { index: number; label: string }[] = []
36
-
37
- for (const group of groups ?? []) {
38
- starts.push({ index: flat.length, label: group.label })
39
- flat.push(...group.options)
40
- }
41
-
42
- return { items: flat, groupStartIndices: starts }
43
- }, [options, groups])
44
-
45
- const selectedOption = items.find(o => o.value === value)
46
- const displayText = selectedOption?.label ?? placeholder ?? value
47
-
48
- // Click outside to close
49
- useEffect(() => {
50
- const handleClickOutside = (e: MouseEvent) => {
51
- if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
52
- changeOpen(false)
53
- }
54
- }
55
- document.addEventListener('mousedown', handleClickOutside)
56
- return () => document.removeEventListener('mousedown', handleClickOutside)
57
- }, [changeOpen])
58
-
59
- const selectItem = (itemValue: string) => {
60
- onChange(itemValue)
61
- changeOpen(false)
62
- setHighlightedIndex(-1)
63
- }
64
-
65
- const handleKeyDown = (e: React.KeyboardEvent) => {
66
- if (e.key === 'Escape') {
67
- changeOpen(false)
68
- setHighlightedIndex(-1)
69
- return
70
- }
71
-
72
- if (!open) {
73
- if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {
74
- e.preventDefault()
75
- changeOpen(true)
76
- setHighlightedIndex(0)
77
- }
78
- return
79
- }
80
-
81
- if (e.key === 'ArrowDown') {
82
- e.preventDefault()
83
- setHighlightedIndex(prev => (prev + 1) % items.length)
84
- } else if (e.key === 'ArrowUp') {
85
- e.preventDefault()
86
- setHighlightedIndex(prev => (prev - 1 + items.length) % items.length)
87
- } else if (e.key === 'Enter') {
88
- e.preventDefault()
89
- const highlighted = items[highlightedIndex]
90
- if (highlighted) {
91
- selectItem(highlighted.value)
92
- }
93
- }
94
- }
95
-
96
- const itemClassName = (index: number, itemValue: string) => {
97
- const base = 'px-3 py-2.5 cursor-pointer'
98
- const highlight = index === highlightedIndex ? 'bg-primary-muted' : 'hover:bg-primary-muted'
99
- const selected = itemValue === value ? 'font-medium' : ''
100
- return `${base} ${highlight} ${selected}`
101
- }
102
-
103
- return (
104
- <div className="relative" ref={containerRef} onKeyDown={handleKeyDown}>
105
- <button
106
- type="button"
107
- id={id}
108
- className={`flex items-center justify-between gap-2 ${className ?? ''}`}
109
- aria-expanded={open}
110
- aria-haspopup="listbox"
111
- aria-label={ariaLabel}
112
- onClick={() => changeOpen(!open)}
113
- >
114
- <span className="truncate">{displayText}</span>
115
- <ChevronDown
116
- className={`size-4 shrink-0 transition-transform ${open ? 'rotate-180' : ''}`}
117
- />
118
- </button>
119
-
120
- {open && (
121
- <ul
122
- role="listbox"
123
- 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 ?? ''}`}
124
- >
125
- {items.map((item, index) => {
126
- const groupHeader = groupStartIndices.find(g => g.index === index)
127
-
128
- return (
129
- <li key={item.value} role="presentation">
130
- {groupHeader && (
131
- <div className="text-muted-foreground px-3 pt-2 pb-1 text-xs font-medium uppercase">
132
- {groupHeader.label}
133
- </div>
134
- )}
135
- <div
136
- role="option"
137
- aria-selected={value === item.value}
138
- className={itemClassName(index, item.value)}
139
- onClick={() => selectItem(item.value)}
140
- onMouseEnter={() => setHighlightedIndex(index)}
141
- >
142
- {renderOption
143
- ? renderOption(item, {
144
- highlighted: index === highlightedIndex,
145
- selected: value === item.value,
146
- })
147
- : item.label}
148
- </div>
149
- </li>
150
- )
151
- })}
152
- </ul>
153
- )}
154
- </div>
155
- )
156
- }
@@ -1,30 +0,0 @@
1
- import type { ReactNode } from 'react'
2
-
3
- export type SelectOption = {
4
- value: string
5
- label: string
6
- }
7
-
8
- export type SelectGroup = {
9
- label: string
10
- options: SelectOption[]
11
- }
12
-
13
- export type SelectOptionState = {
14
- highlighted: boolean
15
- selected: boolean
16
- }
17
-
18
- export type SelectProps = {
19
- value: string
20
- onChange: (value: string) => void
21
- options?: SelectOption[]
22
- groups?: SelectGroup[]
23
- placeholder?: string
24
- id?: string
25
- className?: string
26
- menuClassName?: string
27
- renderOption?: (option: SelectOption, state: SelectOptionState) => ReactNode
28
- onOpenChange?: (open: boolean) => void
29
- 'aria-label'?: string
30
- }
@@ -1,29 +0,0 @@
1
- import { cleanup, render, screen } from '@testing-library/react'
2
- import { afterEach, describe, expect, it, vi } from 'vitest'
3
- import { Slider } from './Slider'
4
-
5
- afterEach(cleanup)
6
-
7
- describe('Slider', () => {
8
- it('renders a slider with the correct value', () => {
9
- render(<Slider value={50} onChange={() => {}} min={0} max={100} aria-label="Volume" />)
10
- const slider = screen.getByRole('slider')
11
- expect(slider).toHaveAttribute('aria-valuenow', '50')
12
- })
13
-
14
- it('calls onCommit when interaction ends', () => {
15
- const onCommit = vi.fn()
16
- render(
17
- <Slider
18
- value={50}
19
- onChange={() => {}}
20
- onCommit={onCommit}
21
- min={0}
22
- max={100}
23
- aria-label="Volume"
24
- />,
25
- )
26
- // Just verify it renders without error — slider drag testing is unreliable in jsdom
27
- expect(screen.getByRole('slider')).toBeInTheDocument()
28
- })
29
- })
@@ -1,53 +0,0 @@
1
- 'use client'
2
-
3
- import { cn } from '../helpers/cn/cn'
4
- import { Slider as SliderPrimitive } from '@base-ui/react/slider'
5
-
6
- type SliderProps = {
7
- value: number
8
- onChange: (value: number) => void
9
- onCommit?: (value: number) => void
10
- min: number
11
- max: number
12
- step?: number
13
- disabled?: boolean
14
- 'aria-label'?: string
15
- className?: string
16
- }
17
-
18
- export const Slider = ({
19
- value,
20
- onChange,
21
- onCommit,
22
- min,
23
- max,
24
- step = 1,
25
- disabled,
26
- 'aria-label': ariaLabel,
27
- className,
28
- }: SliderProps) => (
29
- <SliderPrimitive.Root
30
- value={value}
31
- onValueChange={v => onChange(v as number)}
32
- onValueCommitted={v => onCommit?.(v as number)}
33
- min={min}
34
- max={max}
35
- step={step}
36
- disabled={disabled}
37
- aria-label={ariaLabel}
38
- className={cn('relative flex w-full touch-none items-center py-2', className)}
39
- >
40
- <SliderPrimitive.Control className="relative flex h-1.5 w-full items-center">
41
- <SliderPrimitive.Track className="bg-input h-full w-full overflow-hidden rounded-full">
42
- <SliderPrimitive.Indicator className="bg-primary h-full rounded-full" />
43
- </SliderPrimitive.Track>
44
- <SliderPrimitive.Thumb
45
- className={cn(
46
- 'bg-primary border-background block size-4 rounded-full border-2 shadow-sm',
47
- 'focus-visible:ring-ring/50 focus-visible:ring-3 focus-visible:outline-hidden',
48
- disabled && 'pointer-events-none opacity-50',
49
- )}
50
- />
51
- </SliderPrimitive.Control>
52
- </SliderPrimitive.Root>
53
- )
@@ -1,37 +0,0 @@
1
- 'use client'
2
-
3
- import { useTheme } from 'next-themes'
4
- import { useCallback, useSyncExternalStore } from 'react'
5
- import { useHotkeys } from 'react-hotkeys-hook'
6
- import { toast } from 'sonner'
7
-
8
- const THEME_CYCLE = ['system', 'light', 'dark'] as const
9
-
10
- const THEME_LABELS: Record<string, string> = {
11
- system: 'System',
12
- light: 'Light',
13
- dark: 'Dark',
14
- }
15
-
16
- const emptySubscribe = () => () => {}
17
- const getSnapshot = () => true
18
- const getServerSnapshot = () => false
19
-
20
- export const ThemeCycler = () => {
21
- const { theme, setTheme } = useTheme()
22
- const mounted = useSyncExternalStore(emptySubscribe, getSnapshot, getServerSnapshot)
23
-
24
- const cycleTheme = useCallback(() => {
25
- if (!mounted) return
26
- const current = theme ?? 'system'
27
- const currentIndex = THEME_CYCLE.indexOf(current as (typeof THEME_CYCLE)[number])
28
- const nextIndex = (currentIndex + 1) % THEME_CYCLE.length
29
- const next = THEME_CYCLE[nextIndex] ?? 'system'
30
- setTheme(next)
31
- toast(`Theme: ${THEME_LABELS[next]}`, { duration: 2000 })
32
- }, [theme, setTheme, mounted])
33
-
34
- useHotkeys('shift+t', cycleTheme, { preventDefault: true })
35
-
36
- return null
37
- }
@@ -1,18 +0,0 @@
1
- 'use client'
2
-
3
- import { ThemeProvider as NextThemesProvider } from 'next-themes'
4
-
5
- type Props = {
6
- children: React.ReactNode
7
- }
8
-
9
- export const ThemeProvider = ({ children }: Props) => (
10
- <NextThemesProvider
11
- attribute="class"
12
- defaultTheme="system"
13
- enableSystem
14
- disableTransitionOnChange
15
- >
16
- {children}
17
- </NextThemesProvider>
18
- )
@@ -1,7 +0,0 @@
1
- 'use client'
2
-
3
- import { Toaster as Sonner, type ToasterProps } from 'sonner'
4
-
5
- export const Toaster = (props: ToasterProps) => (
6
- <Sonner theme="system" position="bottom-right" {...props} />
7
- )
@@ -1,107 +0,0 @@
1
- 'use client'
2
-
3
- import { Kbd } from '../Kbd/Kbd'
4
- import { type ReactElement, cloneElement, useCallback, useEffect, useRef, useState } from 'react'
5
-
6
- type TooltipProps = {
7
- label: string
8
- shortcut?: string | string[]
9
- position?: 'top' | 'bottom'
10
- delay?: number
11
- maxWidth?: number
12
- disabled?: boolean
13
- className?: string
14
- children: ReactElement<{ 'aria-label'?: string }>
15
- }
16
-
17
- const DEFAULT_DELAY = 200
18
-
19
- export const Tooltip = ({
20
- label,
21
- shortcut,
22
- position = 'top',
23
- delay = DEFAULT_DELAY,
24
- maxWidth,
25
- disabled,
26
- className,
27
- children,
28
- }: TooltipProps) => {
29
- const [visible, setVisible] = useState(false)
30
- const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
31
- const wrapperRef = useRef<HTMLDivElement>(null)
32
-
33
- const show = useCallback(() => {
34
- if (disabled) return
35
- timeoutRef.current = setTimeout(() => setVisible(true), delay)
36
- }, [disabled, delay])
37
-
38
- const hide = useCallback(() => {
39
- if (timeoutRef.current) {
40
- clearTimeout(timeoutRef.current)
41
- timeoutRef.current = null
42
- }
43
- setVisible(false)
44
- }, [])
45
-
46
- useEffect(() => {
47
- if (disabled && timeoutRef.current) {
48
- clearTimeout(timeoutRef.current)
49
- timeoutRef.current = null
50
- }
51
- }, [disabled])
52
-
53
- useEffect(() => {
54
- return () => {
55
- if (timeoutRef.current) clearTimeout(timeoutRef.current)
56
- }
57
- }, [])
58
-
59
- useEffect(() => {
60
- if (!visible) return
61
- let lastCheck = 0
62
- const handlePointerMove = (e: PointerEvent) => {
63
- if (e.timeStamp - lastCheck < 100) return
64
- lastCheck = e.timeStamp
65
- const rect = wrapperRef.current?.getBoundingClientRect()
66
- if (!rect) return
67
- const inside =
68
- e.clientX >= rect.left &&
69
- e.clientX <= rect.right &&
70
- e.clientY >= rect.top &&
71
- e.clientY <= rect.bottom
72
- if (!inside) hide()
73
- }
74
- document.addEventListener('pointermove', handlePointerMove, { passive: true })
75
- return () => document.removeEventListener('pointermove', handlePointerMove)
76
- }, [visible, hide])
77
-
78
- const positionClasses = position === 'top' ? 'bottom-full mb-2' : 'top-full mt-2'
79
-
80
- return (
81
- <div
82
- ref={wrapperRef}
83
- className={`relative inline-flex${className ? ` ${className}` : ''}`}
84
- onMouseEnter={show}
85
- onMouseLeave={hide}
86
- onPointerDown={hide}
87
- onFocus={show}
88
- onBlur={hide}
89
- >
90
- {cloneElement(children, {
91
- 'aria-label': children.props['aria-label'] ?? label,
92
- })}
93
- {visible && !disabled && (
94
- <div
95
- role="tooltip"
96
- style={maxWidth ? { maxWidth } : undefined}
97
- 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}`}
98
- >
99
- {label}
100
- {shortcut && (
101
- <Kbd keys={shortcut} size="sm" className="bg-background/15 border-transparent" />
102
- )}
103
- </div>
104
- )}
105
- </div>
106
- )
107
- }
@@ -1,6 +0,0 @@
1
- import { clsx, type ClassValue } from 'clsx'
2
- import { twMerge } from 'tailwind-merge'
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }
@@ -1,23 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest'
2
- import { getModKey } from './getModKey'
3
-
4
- describe('getModKey', () => {
5
- afterEach(() => {
6
- vi.unstubAllGlobals()
7
- })
8
-
9
- it('returns "Cmd" on Mac', () => {
10
- vi.stubGlobal('navigator', { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' })
11
- expect(getModKey()).toBe('Cmd')
12
- })
13
-
14
- it('returns "Ctrl" on Windows', () => {
15
- vi.stubGlobal('navigator', { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' })
16
- expect(getModKey()).toBe('Ctrl')
17
- })
18
-
19
- it('returns "Ctrl" when navigator is undefined', () => {
20
- vi.stubGlobal('navigator', undefined)
21
- expect(getModKey()).toBe('Ctrl')
22
- })
23
- })
@@ -1,2 +0,0 @@
1
- export const getModKey = (): string =>
2
- typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.userAgent) ? 'Cmd' : 'Ctrl'
package/src/index.ts DELETED
@@ -1,36 +0,0 @@
1
- export { Badge, badgeVariants } from './Badge/Badge'
2
- export type { BadgeProps } from './Badge/Badge.types'
3
-
4
- export { Button, buttonVariants } from './Button/Button'
5
- export type { ButtonProps } from './Button/Button.types'
6
-
7
- export { Kbd, kbdVariants } from './Kbd/Kbd'
8
- export type { KbdProps } from './Kbd/Kbd.types'
9
-
10
- export { ProgressRing } from './ProgressRing/ProgressRing'
11
-
12
- export { Slider } from './Slider/Slider'
13
-
14
- export { ContextMenu } from './ContextMenu/ContextMenu'
15
-
16
- export { Select } from './Select/Select'
17
- export type {
18
- SelectOption,
19
- SelectGroup,
20
- SelectOptionState,
21
- SelectProps,
22
- } from './Select/Select.types'
23
-
24
- export { Tooltip } from './Tooltip/Tooltip'
25
-
26
- export { ThemeProvider } from './ThemeProvider/ThemeProvider'
27
-
28
- export { ThemeCycler } from './ThemeCycler/ThemeCycler'
29
-
30
- export { Toaster } from './Toaster/Toaster'
31
- export { toast } from 'sonner'
32
-
33
- export { useTheme } from 'next-themes'
34
-
35
- export { cn } from './helpers/cn/cn'
36
- export { getModKey } from './helpers/getModKey/getModKey'