@proteos/ui 0.31.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 +105 -0
- package/dist/chunk-IKAX76MB.js +113 -0
- package/dist/chunk-IKAX76MB.js.map +1 -0
- package/dist/chunk-MWAXK66H.cjs +119 -0
- package/dist/chunk-MWAXK66H.cjs.map +1 -0
- package/dist/index.cjs +6246 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2126 -0
- package/dist/index.d.ts +2126 -0
- package/dist/index.js +6069 -0
- package/dist/index.js.map +1 -0
- package/dist/pdf-view-3ZMMS6ML.cjs +95 -0
- package/dist/pdf-view-3ZMMS6ML.cjs.map +1 -0
- package/dist/pdf-view-BV5ZD6OC.js +93 -0
- package/dist/pdf-view-BV5ZD6OC.js.map +1 -0
- package/package.json +96 -0
- package/src/components/attachment-chip/attachment-chip.tsx +129 -0
- package/src/components/attachment-chip/index.ts +2 -0
- package/src/components/attribute-type-icon/attribute-type-icon.stories.tsx +70 -0
- package/src/components/attribute-type-icon/attribute-type-icon.tsx +125 -0
- package/src/components/attribute-type-icon/index.ts +8 -0
- package/src/components/avatar/avatar.stories.tsx +42 -0
- package/src/components/avatar/avatar.tsx +48 -0
- package/src/components/avatar/index.ts +2 -0
- package/src/components/avatar-group/avatar-group.test.tsx +38 -0
- package/src/components/avatar-group/avatar-group.tsx +60 -0
- package/src/components/avatar-group/index.ts +2 -0
- package/src/components/badge/badge.stories.tsx +62 -0
- package/src/components/badge/badge.test.tsx +36 -0
- package/src/components/badge/badge.tsx +54 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/badge-multi-select/badge-multi-select.stories.tsx +177 -0
- package/src/components/badge-multi-select/badge-multi-select.test.tsx +155 -0
- package/src/components/badge-multi-select/badge-multi-select.tsx +272 -0
- package/src/components/badge-multi-select/index.ts +2 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +43 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +86 -0
- package/src/components/breadcrumbs/index.ts +2 -0
- package/src/components/button/button.stories.tsx +290 -0
- package/src/components/button/button.test.tsx +59 -0
- package/src/components/button/button.tsx +86 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/card/card.stories.tsx +351 -0
- package/src/components/card/card.test.tsx +141 -0
- package/src/components/card/card.tsx +173 -0
- package/src/components/card/index.ts +13 -0
- package/src/components/checkbox/checkbox-group.tsx +52 -0
- package/src/components/checkbox/checkbox.stories.tsx +48 -0
- package/src/components/checkbox/checkbox.test.tsx +50 -0
- package/src/components/checkbox/checkbox.tsx +71 -0
- package/src/components/checkbox/index.ts +4 -0
- package/src/components/combobox/combobox.stories.tsx +97 -0
- package/src/components/combobox/combobox.test.tsx +123 -0
- package/src/components/combobox/combobox.tsx +239 -0
- package/src/components/combobox/index.ts +2 -0
- package/src/components/command/command-hint.tsx +46 -0
- package/src/components/command/command.stories.tsx +88 -0
- package/src/components/command/command.test.tsx +102 -0
- package/src/components/command/command.tsx +76 -0
- package/src/components/command/index.ts +16 -0
- package/src/components/currency-input/currency-input.tsx +192 -0
- package/src/components/currency-input/index.ts +6 -0
- package/src/components/data-table/cells.stories.tsx +120 -0
- package/src/components/data-table/cells.tsx +165 -0
- package/src/components/data-table/data-table.stories.tsx +371 -0
- package/src/components/data-table/data-table.tsx +556 -0
- package/src/components/data-table/index.ts +22 -0
- package/src/components/data-table/make-actions-column.ts +30 -0
- package/src/components/data-table/states/data-table-empty.tsx +40 -0
- package/src/components/data-table/states/data-table-error.tsx +30 -0
- package/src/components/data-table/states/data-table-skeleton.tsx +42 -0
- package/src/components/data-table/use-infinite-scroll-sentinel.ts +51 -0
- package/src/components/date-picker/calendar-internals.tsx +138 -0
- package/src/components/date-picker/date-picker.stories.tsx +78 -0
- package/src/components/date-picker/date-picker.test.tsx +71 -0
- package/src/components/date-picker/date-picker.tsx +140 -0
- package/src/components/date-picker/index.ts +2 -0
- package/src/components/date-time-picker/date-time-picker.stories.tsx +96 -0
- package/src/components/date-time-picker/date-time-picker.test.tsx +87 -0
- package/src/components/date-time-picker/date-time-picker.tsx +203 -0
- package/src/components/date-time-picker/index.ts +2 -0
- package/src/components/empty-state/empty-state.tsx +63 -0
- package/src/components/empty-state/index.ts +2 -0
- package/src/components/error-animation/error-animation.stories.tsx +50 -0
- package/src/components/error-animation/error-animation.test.tsx +22 -0
- package/src/components/error-animation/error-animation.tsx +26 -0
- package/src/components/error-animation/index.ts +2 -0
- package/src/components/field/field.tsx +142 -0
- package/src/components/field/index.ts +8 -0
- package/src/components/file-field/file-field.tsx +285 -0
- package/src/components/file-field/index.ts +2 -0
- package/src/components/file-viewer/fallback-card.tsx +68 -0
- package/src/components/file-viewer/file-viewer.stories.tsx +67 -0
- package/src/components/file-viewer/file-viewer.test.tsx +81 -0
- package/src/components/file-viewer/file-viewer.tsx +116 -0
- package/src/components/file-viewer/image-view.tsx +38 -0
- package/src/components/file-viewer/index.ts +7 -0
- package/src/components/file-viewer/pdf-view.tsx +122 -0
- package/src/components/file-viewer/video-view.tsx +16 -0
- package/src/components/image-attachment/image-attachment.stories.tsx +39 -0
- package/src/components/image-attachment/image-attachment.test.tsx +54 -0
- package/src/components/image-attachment/image-attachment.tsx +77 -0
- package/src/components/image-attachment/index.ts +3 -0
- package/src/components/image-attachment/is-previewable-image-name.ts +14 -0
- package/src/components/image-lightbox/image-lightbox.stories.tsx +38 -0
- package/src/components/image-lightbox/image-lightbox.test.tsx +72 -0
- package/src/components/image-lightbox/image-lightbox.tsx +92 -0
- package/src/components/image-lightbox/index.ts +2 -0
- package/src/components/input/index.ts +2 -0
- package/src/components/input/input.stories.tsx +71 -0
- package/src/components/input/input.test.tsx +36 -0
- package/src/components/input/input.tsx +76 -0
- package/src/components/kbd/index.ts +2 -0
- package/src/components/kbd/kbd.stories.tsx +34 -0
- package/src/components/kbd/kbd.tsx +27 -0
- package/src/components/menu/index.ts +2 -0
- package/src/components/menu/menu.tsx +121 -0
- package/src/components/modal/index.ts +10 -0
- package/src/components/modal/modal.stories.tsx +153 -0
- package/src/components/modal/modal.test.tsx +93 -0
- package/src/components/modal/modal.tsx +149 -0
- package/src/components/multi-select/index.ts +2 -0
- package/src/components/multi-select/multi-select.stories.tsx +211 -0
- package/src/components/multi-select/multi-select.test.tsx +151 -0
- package/src/components/multi-select/multi-select.tsx +339 -0
- package/src/components/nav/index.ts +7 -0
- package/src/components/nav/nav.stories.tsx +139 -0
- package/src/components/nav/nav.tsx +246 -0
- package/src/components/popover/index.ts +8 -0
- package/src/components/popover/popover.stories.tsx +92 -0
- package/src/components/popover/popover.tsx +135 -0
- package/src/components/progress-bar/index.ts +2 -0
- package/src/components/progress-bar/progress-bar.stories.tsx +75 -0
- package/src/components/progress-bar/progress-bar.test.tsx +29 -0
- package/src/components/progress-bar/progress-bar.tsx +57 -0
- package/src/components/radio-group/index.ts +2 -0
- package/src/components/radio-group/radio-group.stories.tsx +113 -0
- package/src/components/radio-group/radio-group.test.tsx +87 -0
- package/src/components/radio-group/radio-group.tsx +104 -0
- package/src/components/record-filter/attribute-picker.tsx +74 -0
- package/src/components/record-filter/chip-value.tsx +171 -0
- package/src/components/record-filter/filter-row.tsx +262 -0
- package/src/components/record-filter/filter-value-input.tsx +351 -0
- package/src/components/record-filter/group-add-controls.tsx +77 -0
- package/src/components/record-filter/group-body.tsx +173 -0
- package/src/components/record-filter/group-mutations.test.ts +104 -0
- package/src/components/record-filter/group-mutations.ts +116 -0
- package/src/components/record-filter/index.ts +58 -0
- package/src/components/record-filter/nested-group.tsx +138 -0
- package/src/components/record-filter/operator-picker.tsx +62 -0
- package/src/components/record-filter/operators.test.ts +105 -0
- package/src/components/record-filter/operators.ts +97 -0
- package/src/components/record-filter/record-filter-builder-panel.tsx +236 -0
- package/src/components/record-filter/record-filter-button.tsx +126 -0
- package/src/components/record-filter/record-filter-chips.tsx +527 -0
- package/src/components/record-filter/record-filter-editor.tsx +91 -0
- package/src/components/record-filter/record-filter.stories.tsx +196 -0
- package/src/components/record-filter/record-filter.test.tsx +437 -0
- package/src/components/record-filter/show-details-store.ts +72 -0
- package/src/components/record-filter/types.ts +150 -0
- package/src/components/segmented-control/index.ts +6 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +67 -0
- package/src/components/segmented-control/segmented-control.test.tsx +47 -0
- package/src/components/segmented-control/segmented-control.tsx +105 -0
- package/src/components/select/index.ts +2 -0
- package/src/components/select/select.stories.tsx +76 -0
- package/src/components/select/select.test.tsx +56 -0
- package/src/components/select/select.tsx +159 -0
- package/src/components/separator/index.ts +2 -0
- package/src/components/separator/separator.stories.tsx +73 -0
- package/src/components/separator/separator.test.tsx +29 -0
- package/src/components/separator/separator.tsx +51 -0
- package/src/components/skeleton/index.ts +2 -0
- package/src/components/skeleton/skeleton.stories.tsx +68 -0
- package/src/components/skeleton/skeleton.test.tsx +38 -0
- package/src/components/skeleton/skeleton.tsx +43 -0
- package/src/components/success-animation/index.ts +2 -0
- package/src/components/success-animation/success-animation.stories.tsx +50 -0
- package/src/components/success-animation/success-animation.test.tsx +22 -0
- package/src/components/success-animation/success-animation.tsx +26 -0
- package/src/components/switch/index.ts +2 -0
- package/src/components/switch/switch.stories.tsx +60 -0
- package/src/components/switch/switch.test.tsx +50 -0
- package/src/components/switch/switch.tsx +49 -0
- package/src/components/table/index.ts +25 -0
- package/src/components/table/table.stories.tsx +306 -0
- package/src/components/table/table.test.tsx +115 -0
- package/src/components/table/table.tsx +543 -0
- package/src/components/tabs/index.ts +8 -0
- package/src/components/tabs/tabs.tsx +141 -0
- package/src/components/tag-input/index.ts +2 -0
- package/src/components/tag-input/tag-input.stories.tsx +73 -0
- package/src/components/tag-input/tag-input.test.tsx +80 -0
- package/src/components/tag-input/tag-input.tsx +193 -0
- package/src/components/textarea/index.ts +2 -0
- package/src/components/textarea/textarea.stories.tsx +75 -0
- package/src/components/textarea/textarea.test.tsx +41 -0
- package/src/components/textarea/textarea.tsx +86 -0
- package/src/components/time-field/index.ts +2 -0
- package/src/components/time-field/time-field.stories.tsx +76 -0
- package/src/components/time-field/time-field.test.tsx +51 -0
- package/src/components/time-field/time-field.tsx +98 -0
- package/src/components/tooltip/index.ts +2 -0
- package/src/components/tooltip/tooltip.stories.tsx +176 -0
- package/src/components/tooltip/tooltip.test.tsx +52 -0
- package/src/components/tooltip/tooltip.tsx +56 -0
- package/src/index.ts +47 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/commands/discovery-store.test.tsx +76 -0
- package/src/lib/commands/discovery-store.ts +110 -0
- package/src/lib/commands/index.ts +9 -0
- package/src/lib/commands/match-event.test.ts +99 -0
- package/src/lib/commands/match-event.ts +31 -0
- package/src/lib/commands/parse-combo.test.ts +65 -0
- package/src/lib/commands/parse-combo.ts +48 -0
- package/src/lib/commands/platform.ts +27 -0
- package/src/lib/commands/protected-combos.test.ts +18 -0
- package/src/lib/commands/protected-combos.ts +38 -0
- package/src/lib/commands/registry.test.ts +97 -0
- package/src/lib/commands/registry.ts +59 -0
- package/src/lib/commands/use-command.test.tsx +91 -0
- package/src/lib/commands/use-command.ts +32 -0
- package/src/lib/commands/use-escape-key.test.tsx +97 -0
- package/src/lib/commands/use-escape-key.ts +62 -0
- package/src/styles.css +321 -0
- package/src/test/setup.ts +9 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine'
|
|
2
|
+
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
|
|
3
|
+
import {
|
|
4
|
+
attachClosestEdge,
|
|
5
|
+
type Edge,
|
|
6
|
+
extractClosestEdge,
|
|
7
|
+
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge'
|
|
8
|
+
import { GripVertical, X } from 'lucide-react'
|
|
9
|
+
import {
|
|
10
|
+
type CSSProperties,
|
|
11
|
+
type HTMLAttributes,
|
|
12
|
+
type MouseEvent,
|
|
13
|
+
type Ref,
|
|
14
|
+
useEffect,
|
|
15
|
+
useRef,
|
|
16
|
+
useState,
|
|
17
|
+
} from 'react'
|
|
18
|
+
import { tv, type VariantProps } from 'tailwind-variants'
|
|
19
|
+
|
|
20
|
+
import { cn } from '../../lib/cn'
|
|
21
|
+
|
|
22
|
+
/* =========================================================================
|
|
23
|
+
Table — pure presentational chrome.
|
|
24
|
+
Div + ARIA grid roles (not semantic <table>) so virtualizers can position
|
|
25
|
+
rows absolutely. The slot pattern mirrors Card: each slot owns its own
|
|
26
|
+
spacing; the root carries no padding. The same primitives compose for
|
|
27
|
+
short non-virtualized tables and for the virtualized DataTable layer.
|
|
28
|
+
========================================================================= */
|
|
29
|
+
|
|
30
|
+
const tableStyles = tv({
|
|
31
|
+
base: ['flex flex-col overflow-hidden min-h-0', 'text-[14px] text-ink-2'],
|
|
32
|
+
variants: {
|
|
33
|
+
variant: {
|
|
34
|
+
// Tables sit on the page bg, not the raised surface color — rows read
|
|
35
|
+
// as part of the page, framed by the panel border (Tonio's call, 2026-07).
|
|
36
|
+
panel: ['bg-bg border border-rule rounded-lg', 'shadow-[var(--shadow-rest)]'],
|
|
37
|
+
bare: '',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: { variant: 'panel' },
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export type TableVariants = VariantProps<typeof tableStyles>
|
|
44
|
+
export type TableProps = HTMLAttributes<HTMLDivElement> &
|
|
45
|
+
TableVariants & {
|
|
46
|
+
'aria-label'?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function Table({ variant, className, ...props }: TableProps) {
|
|
50
|
+
return <div role="grid" {...props} className={cn(tableStyles({ variant }), className)} />
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ---------- Header row ---------------------------------------------------- */
|
|
54
|
+
|
|
55
|
+
const tableHeaderStyles = tv({
|
|
56
|
+
base: [
|
|
57
|
+
'flex items-stretch shrink-0',
|
|
58
|
+
'bg-[color-mix(in_oklab,var(--color-surface-2)_60%,var(--color-surface))]',
|
|
59
|
+
'border-b border-rule',
|
|
60
|
+
],
|
|
61
|
+
variants: {
|
|
62
|
+
sticky: { true: 'sticky top-0 z-[2]', false: '' },
|
|
63
|
+
},
|
|
64
|
+
defaultVariants: { sticky: false },
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
export type TableHeaderProps = HTMLAttributes<HTMLDivElement> & {
|
|
68
|
+
sticky?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function TableHeader({ sticky, className, ...props }: TableHeaderProps) {
|
|
72
|
+
return <div role="row" {...props} className={cn(tableHeaderStyles({ sticky }), className)} />
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ---------- Header cell --------------------------------------------------- */
|
|
76
|
+
|
|
77
|
+
export const tableHeaderCellStyles = tv({
|
|
78
|
+
base: [
|
|
79
|
+
// `relative` is the default positioning context (for the drag-edge
|
|
80
|
+
// indicator + sort affordance). Declared as a class — not an inline
|
|
81
|
+
// style — so the `sticky` variant below can override it; an inline
|
|
82
|
+
// `position` would always win and a sticky gutter header could never
|
|
83
|
+
// freeze.
|
|
84
|
+
'relative',
|
|
85
|
+
'flex items-center gap-[7px] shrink-0 select-none',
|
|
86
|
+
'px-[14px] h-[36px]',
|
|
87
|
+
'border-r border-rule-2 last:border-r-0',
|
|
88
|
+
'font-mono text-[10.5px] font-normal uppercase tracking-[0.08em]',
|
|
89
|
+
'text-ink-4',
|
|
90
|
+
'transition-[background-color,color] duration-150',
|
|
91
|
+
],
|
|
92
|
+
variants: {
|
|
93
|
+
align: {
|
|
94
|
+
left: 'justify-start text-left',
|
|
95
|
+
right: 'justify-end text-right',
|
|
96
|
+
center: 'justify-center text-center',
|
|
97
|
+
},
|
|
98
|
+
interactive: {
|
|
99
|
+
true: [
|
|
100
|
+
'cursor-pointer outline-none appearance-none border-0 border-r border-rule-2 last:border-r-0 bg-transparent',
|
|
101
|
+
'hover:bg-surface-2 hover:text-ink-2',
|
|
102
|
+
'focus-visible:shadow-[var(--shadow-focus)]',
|
|
103
|
+
],
|
|
104
|
+
false: '',
|
|
105
|
+
},
|
|
106
|
+
active: {
|
|
107
|
+
true: 'text-ink',
|
|
108
|
+
false: '',
|
|
109
|
+
},
|
|
110
|
+
sticky: {
|
|
111
|
+
left: 'sticky left-0 z-[3] bg-[color-mix(in_oklab,var(--color-surface-2)_60%,var(--color-surface))]',
|
|
112
|
+
right: 'sticky right-0 z-[3] bg-[color-mix(in_oklab,var(--color-surface-2)_60%,var(--color-surface))]',
|
|
113
|
+
false: '',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
defaultVariants: { align: 'left', interactive: false, active: false, sticky: false },
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
export type SortDirection = 'asc' | 'desc' | null
|
|
120
|
+
|
|
121
|
+
/** Discriminator stored on every column drag payload. */
|
|
122
|
+
export const COLUMN_DRAG_KIND = 'proteos:table:column' as const
|
|
123
|
+
|
|
124
|
+
export type ColumnDragData = {
|
|
125
|
+
kind: typeof COLUMN_DRAG_KIND
|
|
126
|
+
columnId: string
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isColumnDragData(
|
|
130
|
+
data: Record<string | symbol, unknown>,
|
|
131
|
+
): data is ColumnDragData & Record<string | symbol, unknown> {
|
|
132
|
+
return data.kind === COLUMN_DRAG_KIND && typeof data.columnId === 'string'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type DragState =
|
|
136
|
+
| { kind: 'idle' }
|
|
137
|
+
| { kind: 'dragging' }
|
|
138
|
+
| { kind: 'over'; edge: Extract<Edge, 'left' | 'right'> }
|
|
139
|
+
|
|
140
|
+
export type TableHeaderCellProps = Omit<HTMLAttributes<HTMLDivElement>, 'onClick'> & {
|
|
141
|
+
width?: number | string
|
|
142
|
+
align?: 'left' | 'right' | 'center'
|
|
143
|
+
/** Pin the cell to the left or right edge during horizontal scroll. */
|
|
144
|
+
sticky?: 'left' | 'right' | false
|
|
145
|
+
/**
|
|
146
|
+
* When non-null, renders a sort affordance and treats clicks on the cell
|
|
147
|
+
* body as a sort toggle. `null` (the default) is "not sortable".
|
|
148
|
+
*/
|
|
149
|
+
sortDirection?: SortDirection
|
|
150
|
+
onSortToggle?: () => void
|
|
151
|
+
/**
|
|
152
|
+
* Make the header draggable for column reorder. Requires `columnId`.
|
|
153
|
+
* The cell registers pragmatic-dnd `draggable` + `dropTargetForElements`
|
|
154
|
+
* with left/right edge detection; the actual reorder runs in a global
|
|
155
|
+
* `monitorForElements` (e.g. inside DataTable) by reading the published
|
|
156
|
+
* `ColumnDragData`.
|
|
157
|
+
*/
|
|
158
|
+
draggable?: boolean
|
|
159
|
+
columnId?: string
|
|
160
|
+
/**
|
|
161
|
+
* When provided, renders a trailing `×` button. Click events
|
|
162
|
+
* `stopPropagation` so removal never starts a drag or toggles sort.
|
|
163
|
+
*/
|
|
164
|
+
onRemove?: () => void
|
|
165
|
+
/** Aria label fragment used in the remove button label and any drag a11y. */
|
|
166
|
+
ariaLabel?: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function TableHeaderCell({
|
|
170
|
+
width,
|
|
171
|
+
align = 'left',
|
|
172
|
+
className,
|
|
173
|
+
style,
|
|
174
|
+
children,
|
|
175
|
+
sortDirection = null,
|
|
176
|
+
onSortToggle,
|
|
177
|
+
draggable: isDraggable = false,
|
|
178
|
+
columnId,
|
|
179
|
+
onRemove,
|
|
180
|
+
ariaLabel,
|
|
181
|
+
sticky = false,
|
|
182
|
+
...rest
|
|
183
|
+
}: TableHeaderCellProps) {
|
|
184
|
+
const cellRef = useRef<HTMLDivElement | null>(null)
|
|
185
|
+
const handleRef = useRef<HTMLSpanElement | null>(null)
|
|
186
|
+
const [dragState, setDragState] = useState<DragState>({ kind: 'idle' })
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (!isDraggable || !columnId) return
|
|
190
|
+
const cell = cellRef.current
|
|
191
|
+
const handle = handleRef.current
|
|
192
|
+
if (!cell || !handle) return
|
|
193
|
+
|
|
194
|
+
const data: ColumnDragData = { kind: COLUMN_DRAG_KIND, columnId }
|
|
195
|
+
|
|
196
|
+
return combine(
|
|
197
|
+
draggable({
|
|
198
|
+
element: cell,
|
|
199
|
+
dragHandle: handle,
|
|
200
|
+
getInitialData: () => ({ ...data }),
|
|
201
|
+
onDragStart: () => setDragState({ kind: 'dragging' }),
|
|
202
|
+
onDrop: () => setDragState({ kind: 'idle' }),
|
|
203
|
+
}),
|
|
204
|
+
dropTargetForElements({
|
|
205
|
+
element: cell,
|
|
206
|
+
canDrop: ({ source }) =>
|
|
207
|
+
isColumnDragData(source.data) && source.data.columnId !== columnId,
|
|
208
|
+
getData: ({ input, element }) =>
|
|
209
|
+
attachClosestEdge(
|
|
210
|
+
{ ...data },
|
|
211
|
+
{ input, element, allowedEdges: ['left', 'right'] },
|
|
212
|
+
),
|
|
213
|
+
getIsSticky: () => true,
|
|
214
|
+
onDragEnter: ({ self }) => {
|
|
215
|
+
const edge = extractClosestEdge(self.data)
|
|
216
|
+
if (edge === 'left' || edge === 'right') setDragState({ kind: 'over', edge })
|
|
217
|
+
},
|
|
218
|
+
onDrag: ({ self }) => {
|
|
219
|
+
const edge = extractClosestEdge(self.data)
|
|
220
|
+
if (edge === 'left' || edge === 'right') {
|
|
221
|
+
setDragState((prev) =>
|
|
222
|
+
prev.kind === 'over' && prev.edge === edge ? prev : { kind: 'over', edge },
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
onDragLeave: () => setDragState({ kind: 'idle' }),
|
|
227
|
+
onDrop: () => setDragState({ kind: 'idle' }),
|
|
228
|
+
}),
|
|
229
|
+
)
|
|
230
|
+
}, [isDraggable, columnId])
|
|
231
|
+
|
|
232
|
+
const widthStyle: CSSProperties = width != null ? { width, flexBasis: width } : {}
|
|
233
|
+
const isSortable = !!onSortToggle
|
|
234
|
+
const active = sortDirection !== null
|
|
235
|
+
|
|
236
|
+
const handleClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
237
|
+
if (!isSortable) return
|
|
238
|
+
const target = e.target as HTMLElement
|
|
239
|
+
if (target.closest('[data-dnd-handle="true"]')) return
|
|
240
|
+
if (target.closest('[data-column-remove="true"]')) return
|
|
241
|
+
onSortToggle?.()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
245
|
+
if (!isSortable) return
|
|
246
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
247
|
+
e.preventDefault()
|
|
248
|
+
onSortToggle?.()
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const ariaSort: 'ascending' | 'descending' | 'none' | undefined = isSortable
|
|
253
|
+
? sortDirection === 'asc'
|
|
254
|
+
? 'ascending'
|
|
255
|
+
: sortDirection === 'desc'
|
|
256
|
+
? 'descending'
|
|
257
|
+
: 'none'
|
|
258
|
+
: undefined
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div
|
|
262
|
+
{...rest}
|
|
263
|
+
ref={cellRef}
|
|
264
|
+
role="columnheader"
|
|
265
|
+
aria-sort={ariaSort}
|
|
266
|
+
tabIndex={isSortable ? 0 : undefined}
|
|
267
|
+
style={{ ...widthStyle, ...style }}
|
|
268
|
+
onClick={handleClick}
|
|
269
|
+
onKeyDown={handleKeyDown}
|
|
270
|
+
className={cn(
|
|
271
|
+
tableHeaderCellStyles({ align, interactive: isSortable, active, sticky }),
|
|
272
|
+
'group/header',
|
|
273
|
+
dragState.kind === 'dragging' ? 'opacity-40' : '',
|
|
274
|
+
className,
|
|
275
|
+
)}
|
|
276
|
+
>
|
|
277
|
+
{isDraggable && columnId ? (
|
|
278
|
+
<span
|
|
279
|
+
ref={handleRef}
|
|
280
|
+
data-dnd-handle="true"
|
|
281
|
+
aria-hidden="true"
|
|
282
|
+
className={cn(
|
|
283
|
+
'inline-flex items-center justify-center shrink-0 -ml-1.5',
|
|
284
|
+
'cursor-grab active:cursor-grabbing text-ink-4',
|
|
285
|
+
'opacity-0 group-hover/header:opacity-100 transition-opacity',
|
|
286
|
+
)}
|
|
287
|
+
>
|
|
288
|
+
<GripVertical className="size-3" />
|
|
289
|
+
</span>
|
|
290
|
+
) : null}
|
|
291
|
+
|
|
292
|
+
<span
|
|
293
|
+
className={cn(
|
|
294
|
+
'flex items-center gap-[7px] min-w-0 flex-1',
|
|
295
|
+
align === 'right' ? 'justify-end' : align === 'center' ? 'justify-center' : 'justify-start',
|
|
296
|
+
)}
|
|
297
|
+
>
|
|
298
|
+
<span className="truncate">{children}</span>
|
|
299
|
+
{isSortable ? <SortIcon dir={sortDirection} active={active} /> : null}
|
|
300
|
+
</span>
|
|
301
|
+
|
|
302
|
+
{onRemove ? (
|
|
303
|
+
<button
|
|
304
|
+
type="button"
|
|
305
|
+
data-column-remove="true"
|
|
306
|
+
aria-label={`Remove column ${ariaLabel ?? 'column'}`}
|
|
307
|
+
onClick={(e) => {
|
|
308
|
+
e.stopPropagation()
|
|
309
|
+
onRemove()
|
|
310
|
+
}}
|
|
311
|
+
className={cn(
|
|
312
|
+
'inline-flex items-center justify-center shrink-0 size-4 rounded',
|
|
313
|
+
'text-ink-4 hover:text-ink hover:bg-surface-2',
|
|
314
|
+
'opacity-0 group-hover/header:opacity-100 focus-visible:opacity-100',
|
|
315
|
+
'transition-opacity outline-none focus-visible:shadow-[var(--shadow-focus)]',
|
|
316
|
+
)}
|
|
317
|
+
>
|
|
318
|
+
<X className="size-3" />
|
|
319
|
+
</button>
|
|
320
|
+
) : null}
|
|
321
|
+
|
|
322
|
+
{dragState.kind === 'over' ? (
|
|
323
|
+
<div
|
|
324
|
+
aria-hidden="true"
|
|
325
|
+
className={cn(
|
|
326
|
+
'pointer-events-none absolute inset-y-0 z-10 w-0.5 bg-accent',
|
|
327
|
+
dragState.edge === 'left' ? '-left-px' : '-right-px',
|
|
328
|
+
)}
|
|
329
|
+
/>
|
|
330
|
+
) : null}
|
|
331
|
+
</div>
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function SortIcon({ dir, active }: { dir: SortDirection; active: boolean }) {
|
|
336
|
+
return (
|
|
337
|
+
<svg
|
|
338
|
+
width="8"
|
|
339
|
+
height="11"
|
|
340
|
+
viewBox="0 0 8 11"
|
|
341
|
+
aria-hidden="true"
|
|
342
|
+
className={cn('shrink-0', active ? 'text-accent' : 'text-ink-4')}
|
|
343
|
+
>
|
|
344
|
+
<path d="M4 0.5 L7.5 4 L0.5 4 Z" fill="currentColor" opacity={dir === 'asc' ? 1 : 0.22} />
|
|
345
|
+
<path d="M4 10.5 L0.5 7 L7.5 7 Z" fill="currentColor" opacity={dir === 'desc' ? 1 : 0.22} />
|
|
346
|
+
</svg>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Trailing flexible spacer so the header row fills the full row width when
|
|
352
|
+
* the sum of column widths is less than the viewport. Matches the design's
|
|
353
|
+
* `pt-th--spacer`.
|
|
354
|
+
*/
|
|
355
|
+
export function TableHeaderSpacer({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
356
|
+
return (
|
|
357
|
+
<div
|
|
358
|
+
aria-hidden="true"
|
|
359
|
+
{...props}
|
|
360
|
+
className={cn('flex-1 h-[36px] border-r-0', 'bg-transparent pointer-events-none', className)}
|
|
361
|
+
/>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* ---------- Body / scroll container -------------------------------------- */
|
|
366
|
+
|
|
367
|
+
/* Scrollbar chrome shared by `TableBody` (standalone short-table scroller)
|
|
368
|
+
and `TableScroller` (the virtualized DataTable's single both-axes scroller). */
|
|
369
|
+
const scrollbarChrome = [
|
|
370
|
+
'[scrollbar-width:thin]',
|
|
371
|
+
'[scrollbar-color:var(--color-rule)_transparent]',
|
|
372
|
+
'[&::-webkit-scrollbar]:size-3',
|
|
373
|
+
'[&::-webkit-scrollbar-thumb]:rounded-pill',
|
|
374
|
+
'[&::-webkit-scrollbar-thumb]:bg-rule',
|
|
375
|
+
'[&::-webkit-scrollbar-thumb]:border-[3px]',
|
|
376
|
+
'[&::-webkit-scrollbar-thumb]:border-solid',
|
|
377
|
+
'[&::-webkit-scrollbar-thumb]:border-bg',
|
|
378
|
+
'[&::-webkit-scrollbar-thumb:hover]:bg-ink-4',
|
|
379
|
+
'[&::-webkit-scrollbar-corner]:bg-bg',
|
|
380
|
+
] as const
|
|
381
|
+
|
|
382
|
+
const tableBodyStyles = tv({
|
|
383
|
+
base: ['flex-1 min-h-0 overflow-auto relative', ...scrollbarChrome],
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
export type TableBodyProps = HTMLAttributes<HTMLDivElement> & {
|
|
387
|
+
ref?: Ref<HTMLDivElement>
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function TableBody({ className, ref, ...props }: TableBodyProps) {
|
|
391
|
+
return <div role="rowgroup" ref={ref} {...props} className={cn(tableBodyStyles(), className)} />
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* ---------- Scroller ------------------------------------------------------ */
|
|
395
|
+
|
|
396
|
+
/* `TableScroller` — the single scroll container for the virtualized
|
|
397
|
+
DataTable. It scrolls on BOTH axes so a min-width content track (sticky
|
|
398
|
+
header + rows) scrolls horizontally as one unit, the sticky header pins to
|
|
399
|
+
the top on vertical scroll, and any `sticky left-0` gutter column freezes
|
|
400
|
+
to the left on horizontal scroll (Excel-style). `relative` makes it the
|
|
401
|
+
offsetParent so the virtualizer can measure the row list's offset (the
|
|
402
|
+
sticky header's height) for its `scrollMargin`. Presentational: ARIA sees
|
|
403
|
+
through it to the header row + rowgroup it contains. */
|
|
404
|
+
const tableScrollerStyles = tv({
|
|
405
|
+
base: ['relative flex-1 min-h-0 overflow-auto', ...scrollbarChrome],
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
export function TableScroller({ className, ref, ...props }: TableBodyProps) {
|
|
409
|
+
return (
|
|
410
|
+
<div role="presentation" ref={ref} {...props} className={cn(tableScrollerStyles(), className)} />
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/* ---------- Row ----------------------------------------------------------- */
|
|
415
|
+
|
|
416
|
+
/* Stripe + hover tint rows by mixing ink into the page bg they sit on. Ink is
|
|
417
|
+
always the opposing pole of bg, so one formula holds in every theme —
|
|
418
|
+
stripes darken on paper/light and lighten on dark. surface-2 can't do this:
|
|
419
|
+
it's a surface-relative step and collapses into bg in the light theme.
|
|
420
|
+
Strength is a per-theme knob (--row-stripe/--row-hover in styles.css) since a
|
|
421
|
+
near-white base shows a given delta more; --row-hover > --row-stripe keeps
|
|
422
|
+
the states ordered in every theme. */
|
|
423
|
+
const rowStripeBg = 'bg-[color-mix(in_oklab,var(--color-ink)_var(--row-stripe),var(--color-bg))]'
|
|
424
|
+
const rowHoverBg =
|
|
425
|
+
'hover:bg-[color-mix(in_oklab,var(--color-ink)_var(--row-hover),var(--color-bg))]'
|
|
426
|
+
|
|
427
|
+
const tableRowStyles = tv({
|
|
428
|
+
base: ['flex items-stretch', 'border-b border-rule-2 last:border-b-0', 'bg-bg'],
|
|
429
|
+
variants: {
|
|
430
|
+
alt: {
|
|
431
|
+
true: rowStripeBg,
|
|
432
|
+
false: '',
|
|
433
|
+
},
|
|
434
|
+
interactive: {
|
|
435
|
+
true: `cursor-pointer ${rowHoverBg}`,
|
|
436
|
+
false: rowHoverBg,
|
|
437
|
+
},
|
|
438
|
+
absolute: {
|
|
439
|
+
true: 'absolute left-0 right-0',
|
|
440
|
+
false: '',
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
compoundVariants: [{ alt: true, interactive: true, class: rowHoverBg }],
|
|
444
|
+
defaultVariants: { alt: false, interactive: false, absolute: false },
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
export type TableRowProps = HTMLAttributes<HTMLDivElement> & {
|
|
448
|
+
alt?: boolean
|
|
449
|
+
interactive?: boolean
|
|
450
|
+
/**
|
|
451
|
+
* Position the row absolutely (used by virtualizers that translate rows
|
|
452
|
+
* into place via inline `style.transform`).
|
|
453
|
+
*/
|
|
454
|
+
absolute?: boolean
|
|
455
|
+
onRowClick?: (e: MouseEvent<HTMLDivElement>) => void
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function TableRow({
|
|
459
|
+
alt,
|
|
460
|
+
interactive,
|
|
461
|
+
absolute,
|
|
462
|
+
className,
|
|
463
|
+
onRowClick,
|
|
464
|
+
onClick,
|
|
465
|
+
...props
|
|
466
|
+
}: TableRowProps) {
|
|
467
|
+
return (
|
|
468
|
+
<div
|
|
469
|
+
role="row"
|
|
470
|
+
{...props}
|
|
471
|
+
onClick={(e) => {
|
|
472
|
+
onClick?.(e)
|
|
473
|
+
onRowClick?.(e)
|
|
474
|
+
}}
|
|
475
|
+
className={cn(tableRowStyles({ alt, interactive, absolute }), className)}
|
|
476
|
+
/>
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/* ---------- Cell ---------------------------------------------------------- */
|
|
481
|
+
|
|
482
|
+
export const tableCellStyles = tv({
|
|
483
|
+
base: [
|
|
484
|
+
'flex items-center shrink-0 min-w-0',
|
|
485
|
+
'px-[14px]',
|
|
486
|
+
'whitespace-nowrap overflow-hidden text-ellipsis',
|
|
487
|
+
'font-sans text-[13px] tracking-[-0.005em]',
|
|
488
|
+
'text-ink-2',
|
|
489
|
+
],
|
|
490
|
+
variants: {
|
|
491
|
+
align: {
|
|
492
|
+
left: 'justify-start text-left',
|
|
493
|
+
right: 'justify-end text-right [font-variant-numeric:tabular-nums]',
|
|
494
|
+
center: 'justify-center text-center',
|
|
495
|
+
},
|
|
496
|
+
mono: {
|
|
497
|
+
true: 'font-mono text-[12.5px] [font-feature-settings:"tnum","zero"]',
|
|
498
|
+
false: '',
|
|
499
|
+
},
|
|
500
|
+
ink: {
|
|
501
|
+
true: 'text-ink',
|
|
502
|
+
false: '',
|
|
503
|
+
},
|
|
504
|
+
sticky: {
|
|
505
|
+
// Sticky cells inherit the row's background via bg-inherit so the
|
|
506
|
+
// alternating-zebra + hover states pass through.
|
|
507
|
+
left: 'sticky left-0 z-[1] bg-inherit',
|
|
508
|
+
right: 'sticky right-0 z-[1] bg-inherit',
|
|
509
|
+
false: '',
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
defaultVariants: { align: 'left', mono: false, ink: false, sticky: false },
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
export type TableCellProps = HTMLAttributes<HTMLDivElement> & {
|
|
516
|
+
width?: number | string
|
|
517
|
+
align?: 'left' | 'right' | 'center'
|
|
518
|
+
mono?: boolean
|
|
519
|
+
ink?: boolean
|
|
520
|
+
/** Pin the cell to the left or right edge during horizontal scroll. */
|
|
521
|
+
sticky?: 'left' | 'right' | false
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export function TableCell({
|
|
525
|
+
width,
|
|
526
|
+
align,
|
|
527
|
+
mono,
|
|
528
|
+
ink,
|
|
529
|
+
sticky,
|
|
530
|
+
className,
|
|
531
|
+
style,
|
|
532
|
+
...props
|
|
533
|
+
}: TableCellProps) {
|
|
534
|
+
const widthStyle: CSSProperties = width != null ? { width, flexBasis: width } : {}
|
|
535
|
+
return (
|
|
536
|
+
<div
|
|
537
|
+
role="gridcell"
|
|
538
|
+
{...props}
|
|
539
|
+
style={{ ...widthStyle, ...style }}
|
|
540
|
+
className={cn(tableCellStyles({ align, mono, ink, sticky }), className)}
|
|
541
|
+
/>
|
|
542
|
+
)
|
|
543
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Tab as AriaTab,
|
|
4
|
+
TabList as AriaTabList,
|
|
5
|
+
TabPanel as AriaTabPanel,
|
|
6
|
+
Tabs as AriaTabs,
|
|
7
|
+
type TabListProps as AriaTabListProps,
|
|
8
|
+
type TabPanelProps as AriaTabPanelProps,
|
|
9
|
+
type TabProps as AriaTabProps,
|
|
10
|
+
type TabsProps as AriaTabsProps,
|
|
11
|
+
} from 'react-aria-components'
|
|
12
|
+
import { tv, type VariantProps } from 'tailwind-variants'
|
|
13
|
+
|
|
14
|
+
import { cn } from '../../lib/cn'
|
|
15
|
+
|
|
16
|
+
/* =========================================================================
|
|
17
|
+
Tabs — generic tab strip wrapping RAC `<Tabs>` / `<TabList>` / `<Tab>` /
|
|
18
|
+
`<TabPanel>`. Chrome only — no product knowledge. Used in the page
|
|
19
|
+
renderer's `TabsElement` and the page-designer inspector (`Style /
|
|
20
|
+
Settings / Rules`).
|
|
21
|
+
|
|
22
|
+
Sub-component form (`<Tabs.Root>` / `<Tabs.List>` / `<Tabs.Trigger>` /
|
|
23
|
+
`<Tabs.Panel>`) so each tab's content participates separately in the
|
|
24
|
+
designer drop-zone tree. The compound names follow the lib's existing
|
|
25
|
+
slot-style components (Card, Modal).
|
|
26
|
+
========================================================================= */
|
|
27
|
+
|
|
28
|
+
export const tabsStyles = tv({
|
|
29
|
+
slots: {
|
|
30
|
+
root: 'flex flex-col gap-3 outline-none',
|
|
31
|
+
list: 'flex gap-[18px] border-b border-rule pl-0.5',
|
|
32
|
+
trigger: [
|
|
33
|
+
'cursor-pointer outline-none select-none',
|
|
34
|
+
'border-b-[1.5px] -mb-px transition-[color,border-color] duration-150',
|
|
35
|
+
'text-ink-3 border-transparent',
|
|
36
|
+
'data-[hovered]:text-ink',
|
|
37
|
+
'data-[selected]:text-ink data-[selected]:border-accent',
|
|
38
|
+
'data-[disabled]:opacity-45 data-[disabled]:cursor-not-allowed',
|
|
39
|
+
'data-[focus-visible]:shadow-[var(--shadow-focus)] data-[focus-visible]:rounded-sm',
|
|
40
|
+
],
|
|
41
|
+
panel: 'outline-none',
|
|
42
|
+
},
|
|
43
|
+
variants: {
|
|
44
|
+
size: {
|
|
45
|
+
sm: {
|
|
46
|
+
list: 'gap-3',
|
|
47
|
+
trigger:
|
|
48
|
+
'py-1.5 font-mono text-[11px] uppercase tracking-[0.08em]',
|
|
49
|
+
},
|
|
50
|
+
md: {
|
|
51
|
+
list: 'gap-[18px]',
|
|
52
|
+
trigger: 'py-2 text-[13px]',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
defaultVariants: { size: 'md' },
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export type TabsVariants = VariantProps<typeof tabsStyles>
|
|
60
|
+
|
|
61
|
+
export type TabsRootProps = Omit<AriaTabsProps, 'children'> &
|
|
62
|
+
TabsVariants & {
|
|
63
|
+
children?: ReactNode
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Root({ size, className, children, ...props }: TabsRootProps) {
|
|
67
|
+
const styles = tabsStyles({ size })
|
|
68
|
+
return (
|
|
69
|
+
<AriaTabs
|
|
70
|
+
{...props}
|
|
71
|
+
className={(rp) =>
|
|
72
|
+
cn(styles.root(), typeof className === 'function' ? className(rp) : className)
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
{children}
|
|
76
|
+
</AriaTabs>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type TabsListProps = Omit<AriaTabListProps<object>, 'children'> &
|
|
81
|
+
TabsVariants & {
|
|
82
|
+
children?: ReactNode
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function List({ size, className, children, ...props }: TabsListProps) {
|
|
86
|
+
const styles = tabsStyles({ size })
|
|
87
|
+
return (
|
|
88
|
+
<AriaTabList
|
|
89
|
+
{...(props as AriaTabListProps<object>)}
|
|
90
|
+
className={(rp) =>
|
|
91
|
+
cn(styles.list(), typeof className === 'function' ? className(rp) : className)
|
|
92
|
+
}
|
|
93
|
+
>
|
|
94
|
+
{children as never}
|
|
95
|
+
</AriaTabList>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type TabsTriggerProps = Omit<AriaTabProps, 'children'> &
|
|
100
|
+
TabsVariants & {
|
|
101
|
+
children?: ReactNode
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function Trigger({ size, className, children, ...props }: TabsTriggerProps) {
|
|
105
|
+
const styles = tabsStyles({ size })
|
|
106
|
+
return (
|
|
107
|
+
<AriaTab
|
|
108
|
+
{...props}
|
|
109
|
+
className={(rp) =>
|
|
110
|
+
cn(styles.trigger(), typeof className === 'function' ? className(rp) : className)
|
|
111
|
+
}
|
|
112
|
+
>
|
|
113
|
+
{children}
|
|
114
|
+
</AriaTab>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type TabsPanelProps = Omit<AriaTabPanelProps, 'children'> & {
|
|
119
|
+
children?: ReactNode
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function Panel({ className, children, ...props }: TabsPanelProps) {
|
|
123
|
+
const styles = tabsStyles({})
|
|
124
|
+
return (
|
|
125
|
+
<AriaTabPanel
|
|
126
|
+
{...props}
|
|
127
|
+
className={(rp) =>
|
|
128
|
+
cn(styles.panel(), typeof className === 'function' ? className(rp) : className)
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
{children}
|
|
132
|
+
</AriaTabPanel>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const Tabs = Object.assign(Root, {
|
|
137
|
+
Root,
|
|
138
|
+
List,
|
|
139
|
+
Trigger,
|
|
140
|
+
Panel,
|
|
141
|
+
})
|