@toolpath/ui 0.1.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/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/index.d.ts +1174 -0
- package/dist/index.js +5934 -0
- package/package.json +68 -0
- package/src/attribution/action.tsx +13 -0
- package/src/attribution/at.tsx +28 -0
- package/src/attribution/attribution.tsx +16 -0
- package/src/attribution/by.tsx +21 -0
- package/src/attribution/format-time.ts +48 -0
- package/src/attribution/index.ts +20 -0
- package/src/badge/badge.tsx +38 -0
- package/src/badge/index.ts +1 -0
- package/src/breadcrumbs/breadcrumbs-context.tsx +26 -0
- package/src/breadcrumbs/breadcrumbs.tsx +24 -0
- package/src/breadcrumbs/index.ts +12 -0
- package/src/breadcrumbs/item.tsx +85 -0
- package/src/button/animated-ellipsis.tsx +44 -0
- package/src/button/button.tsx +168 -0
- package/src/button/index.ts +1 -0
- package/src/callout/callout.tsx +202 -0
- package/src/callout/index.ts +2 -0
- package/src/callout/use-dismissed-callouts.ts +78 -0
- package/src/card/card.tsx +19 -0
- package/src/card/index.ts +2 -0
- package/src/checkbox/checkbox.tsx +76 -0
- package/src/checkbox/index.ts +1 -0
- package/src/combobox/button.tsx +99 -0
- package/src/combobox/combobox-context.tsx +42 -0
- package/src/combobox/combobox.tsx +60 -0
- package/src/combobox/empty.tsx +21 -0
- package/src/combobox/group-label.tsx +20 -0
- package/src/combobox/group.tsx +11 -0
- package/src/combobox/icon.tsx +11 -0
- package/src/combobox/index.tsx +64 -0
- package/src/combobox/input.tsx +77 -0
- package/src/combobox/item-indicator.tsx +16 -0
- package/src/combobox/item.tsx +58 -0
- package/src/combobox/list.tsx +19 -0
- package/src/combobox/popover.tsx +62 -0
- package/src/combobox/trigger.tsx +33 -0
- package/src/combobox/value.tsx +12 -0
- package/src/common/index.ts +1 -0
- package/src/common/svg.tsx +17 -0
- package/src/dialog/actions.tsx +17 -0
- package/src/dialog/confirm-button.tsx +25 -0
- package/src/dialog/dialog-context.tsx +21 -0
- package/src/dialog/dialog.tsx +37 -0
- package/src/dialog/dismiss-button.tsx +29 -0
- package/src/dialog/index.ts +37 -0
- package/src/dialog/message.tsx +20 -0
- package/src/dialog/provider.tsx +102 -0
- package/src/dialog/title.tsx +20 -0
- package/src/editable-cell/editable-cell.tsx +307 -0
- package/src/editable-cell/index.ts +7 -0
- package/src/editable-cell/use-editable-grid.tsx +296 -0
- package/src/field/field.tsx +22 -0
- package/src/field/index.ts +2 -0
- package/src/helpers/cn.ts +6 -0
- package/src/helpers/index.ts +1 -0
- package/src/icon-button/icon-button.tsx +126 -0
- package/src/icon-button/index.ts +2 -0
- package/src/index.ts +32 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.tsx +276 -0
- package/src/link/index.ts +4 -0
- package/src/link/link-context.tsx +33 -0
- package/src/link/link.tsx +24 -0
- package/src/menu/divider.tsx +17 -0
- package/src/menu/index.ts +40 -0
- package/src/menu/item.tsx +149 -0
- package/src/menu/menu-context.tsx +24 -0
- package/src/menu/menu.tsx +14 -0
- package/src/menu/popover.tsx +69 -0
- package/src/menu/submenu-trigger.tsx +48 -0
- package/src/menu/submenu.tsx +20 -0
- package/src/menu/trigger.tsx +43 -0
- package/src/notification/index.ts +16 -0
- package/src/notification/list.tsx +123 -0
- package/src/notification/provider.tsx +75 -0
- package/src/page-header/index.ts +2 -0
- package/src/page-header/page-header.tsx +14 -0
- package/src/pagination/index.ts +1 -0
- package/src/pagination/pagination.tsx +136 -0
- package/src/panels/collapsed.tsx +32 -0
- package/src/panels/group.tsx +27 -0
- package/src/panels/index.ts +57 -0
- package/src/panels/layout-context.ts +5 -0
- package/src/panels/orientation-context.tsx +5 -0
- package/src/panels/panel.tsx +95 -0
- package/src/panels/separator.tsx +44 -0
- package/src/pin/index.ts +1 -0
- package/src/pin/pin.tsx +99 -0
- package/src/progress-bar/index.ts +1 -0
- package/src/progress-bar/progress-bar.tsx +48 -0
- package/src/quantity-input/index.ts +1 -0
- package/src/quantity-input/quantity-input.tsx +158 -0
- package/src/scroll-area/index.ts +1 -0
- package/src/scroll-area/scroll-area.tsx +82 -0
- package/src/slider/index.ts +1 -0
- package/src/slider/slider.tsx +75 -0
- package/src/star/index.ts +1 -0
- package/src/star/star.tsx +99 -0
- package/src/switch/index.ts +1 -0
- package/src/switch/switch.tsx +33 -0
- package/src/table/body.tsx +15 -0
- package/src/table/cell-context.tsx +48 -0
- package/src/table/cell.tsx +83 -0
- package/src/table/group-empty-state.tsx +28 -0
- package/src/table/group-header.tsx +62 -0
- package/src/table/header-cell.tsx +158 -0
- package/src/table/header-row.tsx +28 -0
- package/src/table/index.ts +56 -0
- package/src/table/is-group-empty-state.ts +17 -0
- package/src/table/is-group-header.ts +5 -0
- package/src/table/row-context.tsx +61 -0
- package/src/table/row-disabled-context.tsx +7 -0
- package/src/table/row.tsx +98 -0
- package/src/table/table-context.tsx +127 -0
- package/src/table/table-empty.tsx +57 -0
- package/src/table/table.tsx +547 -0
- package/src/table/tree-icon.tsx +147 -0
- package/src/table/use-checkbox.ts +200 -0
- package/src/table/use-column-layout.ts +117 -0
- package/src/table/use-groups.ts +134 -0
- package/src/table/use-keyboard-navigation.ts +274 -0
- package/src/table/use-sort-functions.ts +90 -0
- package/src/tabs/index.ts +20 -0
- package/src/tabs/list.tsx +29 -0
- package/src/tabs/tab.tsx +141 -0
- package/src/tabs/tabs-context.tsx +38 -0
- package/src/tabs/tabs.tsx +27 -0
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.tsx +88 -0
- package/src/toast/index.ts +19 -0
- package/src/toast/list.tsx +133 -0
- package/src/toast/provider.tsx +36 -0
- package/src/toggle/index.ts +17 -0
- package/src/toggle/indicator.tsx +27 -0
- package/src/toggle/item.tsx +54 -0
- package/src/toggle/toggle-context.tsx +68 -0
- package/src/toggle/toggle.tsx +124 -0
- package/src/toggle/use-indicator.tsx +69 -0
- package/src/tooltip/index.ts +1 -0
- package/src/tooltip/tooltip.tsx +122 -0
- package/theme.css +70 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React, { ChangeEvent, MouseEvent, Ref } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
import { MinusIcon, PlusIcon } from '@phosphor-icons/react'
|
|
4
|
+
|
|
5
|
+
export type QuantityInputProps = {
|
|
6
|
+
value: number
|
|
7
|
+
onValueChange: (value: number) => void
|
|
8
|
+
min?: number
|
|
9
|
+
max?: number
|
|
10
|
+
step?: number
|
|
11
|
+
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
12
|
+
variant?: 'default' | 'muted'
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
className?: string
|
|
15
|
+
id?: string
|
|
16
|
+
ref?: Ref<HTMLInputElement>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const QuantityInput = ({
|
|
20
|
+
value,
|
|
21
|
+
onValueChange,
|
|
22
|
+
min = 0,
|
|
23
|
+
max = Infinity,
|
|
24
|
+
step = 1,
|
|
25
|
+
size = 'md',
|
|
26
|
+
variant = 'default',
|
|
27
|
+
disabled = false,
|
|
28
|
+
className,
|
|
29
|
+
id,
|
|
30
|
+
ref,
|
|
31
|
+
}: QuantityInputProps) => {
|
|
32
|
+
const handleDecrement = (e: MouseEvent) => {
|
|
33
|
+
e.preventDefault()
|
|
34
|
+
const next = value - step
|
|
35
|
+
if (next >= min) {
|
|
36
|
+
onValueChange(next)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handleIncrement = (e: MouseEvent) => {
|
|
41
|
+
e.preventDefault()
|
|
42
|
+
const next = value + step
|
|
43
|
+
if (next <= max) {
|
|
44
|
+
onValueChange(next)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
49
|
+
const raw = e.target.value
|
|
50
|
+
if (raw === '') {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
const parsed = Number(raw)
|
|
54
|
+
if (!isNaN(parsed)) {
|
|
55
|
+
onValueChange(Math.min(max, Math.max(min, parsed)))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
60
|
+
if (e.key === 'ArrowUp') {
|
|
61
|
+
e.preventDefault()
|
|
62
|
+
const next = value + step
|
|
63
|
+
if (next <= max) {
|
|
64
|
+
onValueChange(next)
|
|
65
|
+
}
|
|
66
|
+
} else if (e.key === 'ArrowDown') {
|
|
67
|
+
e.preventDefault()
|
|
68
|
+
const next = value - step
|
|
69
|
+
if (next >= min) {
|
|
70
|
+
onValueChange(next)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const handleBlur = () => {
|
|
76
|
+
onValueChange(Math.min(max, Math.max(min, value)))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const atMin = value <= min
|
|
80
|
+
const atMax = value >= max
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
className={cn(
|
|
85
|
+
'inline-flex items-stretch overflow-hidden font-medium',
|
|
86
|
+
'focus-within:ring-2 ring-info/50 dark:ring-info/75',
|
|
87
|
+
{
|
|
88
|
+
'border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 focus-within:border-info dark:focus-within:border-info':
|
|
89
|
+
variant === 'default',
|
|
90
|
+
'bg-gray-50 dark:bg-zinc-800': variant === 'muted',
|
|
91
|
+
'h-5 rounded text-xs': size === 'sm',
|
|
92
|
+
'h-6 rounded text-xs': size === 'md',
|
|
93
|
+
'h-7 rounded text-sm': size === 'lg',
|
|
94
|
+
'h-9 rounded-md text-lg': size === 'xl',
|
|
95
|
+
'opacity-50 pointer-events-none': disabled,
|
|
96
|
+
},
|
|
97
|
+
className,
|
|
98
|
+
)}
|
|
99
|
+
data-disabled={disabled || undefined}
|
|
100
|
+
>
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
tabIndex={-1}
|
|
104
|
+
disabled={disabled || atMin}
|
|
105
|
+
onClick={handleDecrement}
|
|
106
|
+
className={cn(
|
|
107
|
+
'flex shrink-0 items-center justify-center bg-gray-200 dark:bg-zinc-800 transition-colors cursor-pointer',
|
|
108
|
+
'hover:bg-gray-300 dark:hover:bg-zinc-900 active:bg-gray-400 dark:active:bg-zinc-900 disabled:opacity-40 disabled:pointer-events-none',
|
|
109
|
+
{
|
|
110
|
+
'w-5': size === 'sm',
|
|
111
|
+
'w-6': size === 'md',
|
|
112
|
+
'w-7': size === 'lg',
|
|
113
|
+
'w-9': size === 'xl',
|
|
114
|
+
},
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
<MinusIcon className="size-3.5 text-white dark:text-zinc-100" />
|
|
118
|
+
</button>
|
|
119
|
+
<input
|
|
120
|
+
ref={ref}
|
|
121
|
+
id={id}
|
|
122
|
+
data-testid={id}
|
|
123
|
+
type="text"
|
|
124
|
+
inputMode="numeric"
|
|
125
|
+
value={value}
|
|
126
|
+
onChange={handleChange}
|
|
127
|
+
onKeyDown={handleKeyDown}
|
|
128
|
+
onBlur={handleBlur}
|
|
129
|
+
disabled={disabled}
|
|
130
|
+
className={cn(
|
|
131
|
+
'min-w-0 flex-1 bg-transparent text-center text-gray-500 dark:text-zinc-100 outline-none',
|
|
132
|
+
{
|
|
133
|
+
'px-1': size === 'sm' || size === 'md',
|
|
134
|
+
'px-2': size === 'lg' || size === 'xl',
|
|
135
|
+
},
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
tabIndex={-1}
|
|
141
|
+
disabled={disabled || atMax}
|
|
142
|
+
onClick={handleIncrement}
|
|
143
|
+
className={cn(
|
|
144
|
+
'flex shrink-0 items-center justify-center bg-gray-200 dark:bg-zinc-800 transition-colors cursor-pointer',
|
|
145
|
+
'hover:bg-gray-300 dark:hover:bg-zinc-900 active:bg-gray-400 dark:active:bg-zinc-900 disabled:opacity-40 disabled:pointer-events-none',
|
|
146
|
+
{
|
|
147
|
+
'w-5': size === 'sm',
|
|
148
|
+
'w-6': size === 'md',
|
|
149
|
+
'w-7': size === 'lg',
|
|
150
|
+
'w-9': size === 'xl',
|
|
151
|
+
},
|
|
152
|
+
)}
|
|
153
|
+
>
|
|
154
|
+
<PlusIcon className="size-3.5 text-white dark:text-zinc-100" />
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './scroll-area'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React, { ReactNode, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import { ScrollArea as BaseScrollArea } from '@base-ui/react'
|
|
3
|
+
import { cn } from '../helpers'
|
|
4
|
+
|
|
5
|
+
export interface ScrollAreaState {
|
|
6
|
+
hasOverflow: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ScrollAreaSize = 'sm' | 'md'
|
|
10
|
+
|
|
11
|
+
export interface ScrollAreaProps {
|
|
12
|
+
children: ReactNode | ((state: ScrollAreaState) => ReactNode)
|
|
13
|
+
className?: string
|
|
14
|
+
size?: ScrollAreaSize
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ScrollArea = ({ children, className, size = 'md' }: ScrollAreaProps) => {
|
|
18
|
+
const viewportRef = useRef<HTMLDivElement>(null)
|
|
19
|
+
const [hasOverflow, setHasOverflow] = useState(false)
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const viewport = viewportRef.current
|
|
23
|
+
if (!viewport) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const checkOverflow = () => {
|
|
28
|
+
setHasOverflow(viewport.scrollHeight > viewport.clientHeight)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
checkOverflow()
|
|
32
|
+
|
|
33
|
+
const resizeObserver = new ResizeObserver(checkOverflow)
|
|
34
|
+
resizeObserver.observe(viewport)
|
|
35
|
+
|
|
36
|
+
const mutationObserver = new MutationObserver(checkOverflow)
|
|
37
|
+
mutationObserver.observe(viewport, { childList: true, subtree: true })
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
resizeObserver.disconnect()
|
|
41
|
+
mutationObserver.disconnect()
|
|
42
|
+
}
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
const state: ScrollAreaState = { hasOverflow }
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<BaseScrollArea.Root className={cn('flex-1 min-h-0 w-full', className)}>
|
|
49
|
+
<BaseScrollArea.Viewport
|
|
50
|
+
ref={viewportRef}
|
|
51
|
+
tabIndex={-1}
|
|
52
|
+
data-viewport
|
|
53
|
+
className="h-full w-full overscroll-contain outline-none"
|
|
54
|
+
>
|
|
55
|
+
{typeof children === 'function' ? children(state) : children}
|
|
56
|
+
</BaseScrollArea.Viewport>
|
|
57
|
+
<BaseScrollArea.Scrollbar
|
|
58
|
+
data-scrollbar
|
|
59
|
+
className={cn(
|
|
60
|
+
'relative flex rounded bg-gray-100 dark:bg-zinc-700 opacity-0 pointer-events-none transition-opacity',
|
|
61
|
+
"before:absolute before:content-['']",
|
|
62
|
+
'data-[orientation=vertical]:m-2 data-[orientation=vertical]:before:h-full data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:left-1/2 data-[orientation=vertical]:before:-translate-x-1/2',
|
|
63
|
+
'data-[orientation=horizontal]:m-2 data-[orientation=horizontal]:before:h-5 data-[orientation=horizontal]:before:w-full data-[orientation=horizontal]:before:left-0 data-[orientation=horizontal]:before:right-0 data-[orientation=horizontal]:before:-bottom-2',
|
|
64
|
+
// Reveal each scrollbar only when its orientation has overflow.
|
|
65
|
+
'data-[orientation=vertical]:data-[has-overflow-y]:opacity-100',
|
|
66
|
+
'data-[orientation=horizontal]:data-[has-overflow-x]:opacity-100',
|
|
67
|
+
'data-[hovering]:pointer-events-auto data-[hovering]:opacity-100 data-[hovering]:delay-0',
|
|
68
|
+
'data-[scrolling]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling]:duration-0',
|
|
69
|
+
{
|
|
70
|
+
'data-[orientation=vertical]:w-1 data-[orientation=horizontal]:h-1': size === 'sm',
|
|
71
|
+
'data-[orientation=vertical]:w-2 data-[orientation=horizontal]:h-2': size === 'md',
|
|
72
|
+
},
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
<BaseScrollArea.Thumb
|
|
76
|
+
data-thumb
|
|
77
|
+
className="w-full rounded bg-gray-500/50 dark:bg-zinc-400/50"
|
|
78
|
+
/>
|
|
79
|
+
</BaseScrollArea.Scrollbar>
|
|
80
|
+
</BaseScrollArea.Root>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './slider'
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { debounce } from 'lodash-es'
|
|
2
|
+
import React, { useEffect, useMemo } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
Slider as BaseSlider,
|
|
5
|
+
SliderRoot,
|
|
6
|
+
SliderRootProps,
|
|
7
|
+
SliderThumbProps,
|
|
8
|
+
} from '@base-ui/react/slider'
|
|
9
|
+
import { cn } from '../helpers'
|
|
10
|
+
|
|
11
|
+
export type SliderProps = Omit<SliderRootProps, 'onValueChange'> & {
|
|
12
|
+
variant?: 'default'
|
|
13
|
+
onFocus?: SliderThumbProps['onFocus'] | undefined
|
|
14
|
+
onBlur?: SliderThumbProps['onBlur'] | undefined
|
|
15
|
+
onValueChange?: (value: number, eventDetails: SliderRoot.ChangeEventDetails) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Slider = ({
|
|
19
|
+
variant = 'default',
|
|
20
|
+
onFocus,
|
|
21
|
+
onBlur,
|
|
22
|
+
onValueChange,
|
|
23
|
+
className = '',
|
|
24
|
+
...props
|
|
25
|
+
}: SliderProps) => {
|
|
26
|
+
const debouncedOnValueChange = useMemo(
|
|
27
|
+
() =>
|
|
28
|
+
debounce((value, eventDetails) => {
|
|
29
|
+
if (typeof onValueChange === 'function') {
|
|
30
|
+
onValueChange(Array.isArray(value) ? value[0] : value, eventDetails)
|
|
31
|
+
}
|
|
32
|
+
}, 10),
|
|
33
|
+
[onValueChange],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
return () => debouncedOnValueChange.cancel()
|
|
38
|
+
}, [debouncedOnValueChange])
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<BaseSlider.Root
|
|
42
|
+
thumbAlignment="edge"
|
|
43
|
+
className={cn('w-full data-[dragging]:cursor-ew-resize hover:cursor-ew-resize', className)}
|
|
44
|
+
onValueChange={debouncedOnValueChange}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<BaseSlider.Control
|
|
48
|
+
data-control
|
|
49
|
+
className="flex w-full touch-none items-center select-none outline-none"
|
|
50
|
+
>
|
|
51
|
+
<BaseSlider.Track className="h-2 w-full rounded-full bg-gray-100 dark:bg-zinc-700 select-none hover:cursor-ew-resize">
|
|
52
|
+
<BaseSlider.Indicator
|
|
53
|
+
data-indicator
|
|
54
|
+
className={cn('rounded-full bg-gray-600 dark:bg-zinc-400 select-none', {
|
|
55
|
+
'bg-gray-200 dark:bg-zinc-700': props.disabled && variant === 'default',
|
|
56
|
+
})}
|
|
57
|
+
/>
|
|
58
|
+
<BaseSlider.Thumb
|
|
59
|
+
data-thumb
|
|
60
|
+
onBlur={onBlur}
|
|
61
|
+
onFocus={onFocus}
|
|
62
|
+
className={cn(
|
|
63
|
+
'size-4 appearance-none rounded-full bg-white dark:bg-zinc-200 shadow-lg ring-1 ring-gray-200 dark:ring-zinc-800 outline-none',
|
|
64
|
+
'has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-info/60 dark:has-[:focus-visible]:ring-info',
|
|
65
|
+
{
|
|
66
|
+
'cursor-ew-resize hover:ring-offset-2 transition ease-in-out': !props.disabled,
|
|
67
|
+
},
|
|
68
|
+
{ 'cursor-not-allowed bg-gray-100': props.disabled },
|
|
69
|
+
)}
|
|
70
|
+
/>
|
|
71
|
+
</BaseSlider.Track>
|
|
72
|
+
</BaseSlider.Control>
|
|
73
|
+
</BaseSlider.Root>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './star'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, { useRef } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
import { StarIcon } from '@phosphor-icons/react'
|
|
4
|
+
|
|
5
|
+
export interface StarChangeEvent {
|
|
6
|
+
shiftKey: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type StarProps = {
|
|
10
|
+
checked: boolean
|
|
11
|
+
onChange: (checked: boolean, event: StarChangeEvent) => void
|
|
12
|
+
name: string
|
|
13
|
+
className?: string
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
size?: 'sm' | 'md' | 'lg'
|
|
16
|
+
tabIndex?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Star = ({
|
|
20
|
+
checked,
|
|
21
|
+
onChange,
|
|
22
|
+
name,
|
|
23
|
+
className,
|
|
24
|
+
disabled = false,
|
|
25
|
+
size = 'md',
|
|
26
|
+
tabIndex = 0,
|
|
27
|
+
}: StarProps) => {
|
|
28
|
+
const shiftKeyRef = useRef(false)
|
|
29
|
+
|
|
30
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
31
|
+
e.stopPropagation()
|
|
32
|
+
if (disabled) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
shiftKeyRef.current = e.shiftKey
|
|
36
|
+
onChange(!checked, { shiftKey: shiftKeyRef.current })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
40
|
+
if (disabled) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
if (e.key === ' ' || e.key === 'Enter') {
|
|
44
|
+
e.preventDefault()
|
|
45
|
+
shiftKeyRef.current = e.shiftKey
|
|
46
|
+
onChange(!checked, { shiftKey: shiftKeyRef.current })
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const sizeClasses = {
|
|
51
|
+
sm: 'size-3',
|
|
52
|
+
md: 'size-4',
|
|
53
|
+
lg: 'size-5',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
role="checkbox"
|
|
60
|
+
aria-checked={checked}
|
|
61
|
+
name={name}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
tabIndex={tabIndex}
|
|
64
|
+
onClick={handleClick}
|
|
65
|
+
onKeyDown={handleKeyDown}
|
|
66
|
+
className={cn(
|
|
67
|
+
'group grid cursor-pointer outline-none',
|
|
68
|
+
'focus-visible:ring-2 ring-info/75 ring-offset-1 rounded-sm',
|
|
69
|
+
'transition-colors duration-150',
|
|
70
|
+
{
|
|
71
|
+
'cursor-not-allowed opacity-50': disabled,
|
|
72
|
+
},
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
<StarIcon
|
|
77
|
+
weight="fill"
|
|
78
|
+
className={cn(
|
|
79
|
+
sizeClasses[size],
|
|
80
|
+
'col-start-1 row-start-1 transition-opacity duration-150 pointer-event-none',
|
|
81
|
+
{
|
|
82
|
+
'text-warning': checked,
|
|
83
|
+
'text-transparent': !checked,
|
|
84
|
+
'group-hover:text-warning': !disabled && !checked,
|
|
85
|
+
},
|
|
86
|
+
)}
|
|
87
|
+
/>
|
|
88
|
+
<StarIcon
|
|
89
|
+
weight="regular"
|
|
90
|
+
className={cn(sizeClasses[size], 'col-start-1 row-start-1 pointer-event-none', {
|
|
91
|
+
'text-[#F0D360]': checked,
|
|
92
|
+
'text-gray-200 group-hover:text-gray-200 dark:text-zinc-500 group-hover:dark:text-warning-darken/75':
|
|
93
|
+
!checked && !disabled,
|
|
94
|
+
'text-gray-200 dark:text-zinc-800': !checked && disabled,
|
|
95
|
+
})}
|
|
96
|
+
/>
|
|
97
|
+
</button>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './switch'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Switch as BaseSwitch, SwitchRootProps } from '@base-ui/react'
|
|
3
|
+
import { cn } from '../helpers'
|
|
4
|
+
|
|
5
|
+
export type SwitchProps = SwitchRootProps
|
|
6
|
+
|
|
7
|
+
export const Switch = ({
|
|
8
|
+
checked,
|
|
9
|
+
onCheckedChange,
|
|
10
|
+
disabled = false,
|
|
11
|
+
className = '',
|
|
12
|
+
...props
|
|
13
|
+
}: SwitchProps) => {
|
|
14
|
+
return (
|
|
15
|
+
<BaseSwitch.Root
|
|
16
|
+
checked={checked}
|
|
17
|
+
onCheckedChange={onCheckedChange}
|
|
18
|
+
disabled={disabled}
|
|
19
|
+
className={cn(
|
|
20
|
+
'relative inline-flex h-6 w-11 shrink-0 items-center rounded-full bg-gray-200 dark:bg-zinc-700 transition-colors',
|
|
21
|
+
'focus:outline-none focus:ring-2 focus:ring-info/50 focus:ring-offset-1 ring-offset-white dark:ring-offset-zinc-800 dark:focus:ring-info/75',
|
|
22
|
+
'data-[checked]:bg-info dark:data-[checked]:bg-info/75 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-60',
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
<BaseSwitch.Thumb
|
|
28
|
+
data-thumb
|
|
29
|
+
className="inline-block h-4 w-4 bg-white dark:bg-zinc-100 translate-x-1 rounded-full transition-transform data-[checked]:translate-x-6"
|
|
30
|
+
/>
|
|
31
|
+
</BaseSwitch.Root>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React, { ReactNode, RefObject } from 'react'
|
|
2
|
+
import { useKeyboardNavigation } from './use-keyboard-navigation'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
indexes: Array<[number, number | string]>
|
|
6
|
+
children: ReactNode
|
|
7
|
+
containerRef?: RefObject<HTMLElement | null>
|
|
8
|
+
data?: Array<any>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Body = ({ indexes, children, containerRef, data }: Props) => {
|
|
12
|
+
useKeyboardNavigation(indexes, containerRef, data)
|
|
13
|
+
|
|
14
|
+
return <>{children}</>
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { FC, ReactElement, createContext, useCallback, useContext, useMemo } from 'react'
|
|
2
|
+
import { useRow } from './row-context'
|
|
3
|
+
import { useTable } from './table-context'
|
|
4
|
+
|
|
5
|
+
export interface CellContextInterface {
|
|
6
|
+
index: number
|
|
7
|
+
isEditing: boolean
|
|
8
|
+
toggleIsEditing: () => void
|
|
9
|
+
isHovered: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const CellContext = createContext<CellContextInterface>({} as CellContextInterface)
|
|
13
|
+
|
|
14
|
+
interface ProviderProps {
|
|
15
|
+
index: number
|
|
16
|
+
isHovered: boolean
|
|
17
|
+
children: ReactElement
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CellProvider: FC<ProviderProps> = ({ index, isHovered, children }) => {
|
|
21
|
+
const { select, editingCell, setEditingCell } = useTable()
|
|
22
|
+
const { item, index: rowIndex } = useRow()
|
|
23
|
+
const isEditing =
|
|
24
|
+
editingCell !== null &&
|
|
25
|
+
editingCell[0] === rowIndex &&
|
|
26
|
+
editingCell[1] === item.id &&
|
|
27
|
+
editingCell[2] === index
|
|
28
|
+
|
|
29
|
+
const toggleIsEditing = useCallback(() => {
|
|
30
|
+
setEditingCell((prev) => (prev ? null : [rowIndex, item.id, index]))
|
|
31
|
+
}, [setEditingCell, rowIndex, item?.id, index])
|
|
32
|
+
|
|
33
|
+
const value = useMemo(
|
|
34
|
+
() => ({
|
|
35
|
+
index,
|
|
36
|
+
isEditing: select ? isEditing : false,
|
|
37
|
+
isHovered,
|
|
38
|
+
toggleIsEditing,
|
|
39
|
+
}),
|
|
40
|
+
[index, select, isEditing, isHovered, toggleIsEditing],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return <CellContext.Provider value={value}>{children}</CellContext.Provider>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const useCell = () => useContext(CellContext)
|
|
47
|
+
const CellConsumer = CellContext.Consumer
|
|
48
|
+
export { useCell, CellProvider, CellContext, CellConsumer }
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, { memo, useState } from 'react'
|
|
2
|
+
import { Cell as BaseCell } from '@table-library/react-table-library/table'
|
|
3
|
+
import { CellTree } from '@table-library/react-table-library/tree'
|
|
4
|
+
import { CellProps as BaseCellProps } from '@table-library/react-table-library/types/table'
|
|
5
|
+
import { cn } from '../helpers'
|
|
6
|
+
import { CellConsumer, CellProvider } from './cell-context'
|
|
7
|
+
import { useRow } from './row-context'
|
|
8
|
+
import { useRowDisabled } from './row-disabled-context'
|
|
9
|
+
import { useTable } from './table-context'
|
|
10
|
+
|
|
11
|
+
const Divider = () => {
|
|
12
|
+
const { isSelected, isHovered } = useRow()
|
|
13
|
+
return (
|
|
14
|
+
<div className="absolute top-0 right-0 w-1 h-full py-1 shrink-0">
|
|
15
|
+
<div
|
|
16
|
+
className={cn('w-1 h-full border-r border-gray-50 dark:border-zinc-800', {
|
|
17
|
+
'border-gray-100 dark:border-zinc-800': isSelected || isHovered,
|
|
18
|
+
})}
|
|
19
|
+
></div>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CellProps extends BaseCellProps {
|
|
25
|
+
divider?: boolean
|
|
26
|
+
tree?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const Cell = memo(function Cell({
|
|
30
|
+
children,
|
|
31
|
+
className,
|
|
32
|
+
divider = true,
|
|
33
|
+
tree = false,
|
|
34
|
+
...props
|
|
35
|
+
}: CellProps) {
|
|
36
|
+
const { columns, select, editingCell, density } = useTable()
|
|
37
|
+
const { isSelected: isRowSelected, isHovered: isRowHovered, item, index: rowIndex } = useRow()
|
|
38
|
+
const isRowDisabled = useRowDisabled()
|
|
39
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
40
|
+
const isLastColumn = props.index === columns
|
|
41
|
+
const cellIndex = props.index - (select ? 1 : 0)
|
|
42
|
+
const nextCellIsEditing =
|
|
43
|
+
editingCell !== null &&
|
|
44
|
+
editingCell[0] === rowIndex &&
|
|
45
|
+
editingCell[1] === item.id &&
|
|
46
|
+
editingCell[2] === cellIndex + 1
|
|
47
|
+
const Wrapper = tree ? CellTree : BaseCell
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Wrapper
|
|
51
|
+
item={tree ? item : undefined}
|
|
52
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
53
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
54
|
+
className="border-b border-gray-50 dark:border-zinc-800 relative"
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<CellProvider isHovered={isHovered} index={cellIndex}>
|
|
58
|
+
<CellConsumer>
|
|
59
|
+
{({ isEditing }) => {
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<div
|
|
63
|
+
className={cn(
|
|
64
|
+
'flex flex-row items-center text-xs font-medium px-1.5 flex-1',
|
|
65
|
+
density === 'roomy' ? 'h-11' : 'h-8',
|
|
66
|
+
{
|
|
67
|
+
'bg-gray-50 dark:bg-zinc-800': isRowSelected || isRowHovered,
|
|
68
|
+
'text-gray-300 dark:text-zinc-500': isRowDisabled,
|
|
69
|
+
},
|
|
70
|
+
className,
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
{children}
|
|
74
|
+
</div>
|
|
75
|
+
{divider && !isLastColumn && !isEditing && !nextCellIsEditing && <Divider />}
|
|
76
|
+
</>
|
|
77
|
+
)
|
|
78
|
+
}}
|
|
79
|
+
</CellConsumer>
|
|
80
|
+
</CellProvider>
|
|
81
|
+
</Wrapper>
|
|
82
|
+
)
|
|
83
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
import { getRowHeight } from './table'
|
|
4
|
+
import { useTable } from './table-context'
|
|
5
|
+
|
|
6
|
+
interface GroupEmptyStateProps {
|
|
7
|
+
children: ReactNode
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const GroupEmptyState = ({ children, className }: GroupEmptyStateProps) => {
|
|
12
|
+
const { columns, select, density } = useTable()
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className={cn(
|
|
17
|
+
'w-full bg-white dark:bg-zinc-950 flex flex-row items-center justify-center px-2 text-xs text-gray-300 dark:text-zinc-200 font-normal border-b border-gray-100 dark:border-zinc-800',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
style={{
|
|
21
|
+
gridColumn: `span ${columns + (select ? 1 : 0)}`,
|
|
22
|
+
height: getRowHeight(density) * 2,
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|