raft-ui 0.0.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.
- package/README.md +24 -0
- package/dist/cn.d.mts +2 -0
- package/dist/cn.mjs +2 -0
- package/dist/index.d.mts +2710 -0
- package/dist/index.mjs +4821 -0
- package/dist/label-DWq4ia5I.mjs +115 -0
- package/dist/styled-props-B3SNekJg.d.mts +16 -0
- package/dist/wip.d.mts +1238 -0
- package/dist/wip.mjs +1416 -0
- package/package.json +70 -0
- package/src/components/avatar/avatar.tsx +375 -0
- package/src/components/avatar/index.ts +17 -0
- package/src/components/badge/badge.tsx +393 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/banner/banner.tsx +269 -0
- package/src/components/banner/index.ts +8 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +152 -0
- package/src/components/bottom-sheet/index.ts +22 -0
- package/src/components/button/button.tsx +497 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/checkbox/checkbox-indicator.tsx +128 -0
- package/src/components/checkbox/checkbox.tsx +350 -0
- package/src/components/checkbox/index.ts +2 -0
- package/src/components/combobox/combobox.tsx +603 -0
- package/src/components/combobox/index.ts +52 -0
- package/src/components/context-menu/context-menu.tsx +380 -0
- package/src/components/context-menu/index.ts +34 -0
- package/src/components/conversation-preview-card/conversation-preview-card.tsx +336 -0
- package/src/components/conversation-preview-card/index.ts +32 -0
- package/src/components/description-list/description-list.tsx +116 -0
- package/src/components/description-list/index.ts +14 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +405 -0
- package/src/components/dropdown-menu/index.ts +38 -0
- package/src/components/empty-state/empty-state.tsx +177 -0
- package/src/components/empty-state/index.ts +16 -0
- package/src/components/field/field.tsx +125 -0
- package/src/components/field/index.ts +18 -0
- package/src/components/inline-code/index.ts +2 -0
- package/src/components/inline-code/inline-code.tsx +33 -0
- package/src/components/input/index.ts +2 -0
- package/src/components/input/input.tsx +76 -0
- package/src/components/input-group/index.ts +7 -0
- package/src/components/input-group/input-group.tsx +215 -0
- package/src/components/kbd/index.ts +2 -0
- package/src/components/kbd/kbd.tsx +43 -0
- package/src/components/label/index.ts +7 -0
- package/src/components/label/label.tsx +99 -0
- package/src/components/lightbox/index.ts +24 -0
- package/src/components/lightbox/lightbox.tsx +245 -0
- package/src/components/list-item/index.ts +20 -0
- package/src/components/list-item/list-item.tsx +263 -0
- package/src/components/media-list-item/index.ts +20 -0
- package/src/components/media-list-item/media-list-item.tsx +169 -0
- package/src/components/notification-center/index.ts +39 -0
- package/src/components/notification-center/notification-center.tsx +558 -0
- package/src/components/panel-header/index.ts +22 -0
- package/src/components/panel-header/panel-header.tsx +199 -0
- package/src/components/popover/index.ts +30 -0
- package/src/components/popover/popover.tsx +248 -0
- package/src/components/preview-shell/index.ts +2 -0
- package/src/components/preview-shell/preview-shell.tsx +51 -0
- package/src/components/quoted-message-card/index.ts +36 -0
- package/src/components/quoted-message-card/quoted-message-card.tsx +391 -0
- package/src/components/radio-group/index.ts +6 -0
- package/src/components/radio-group/radio-group.tsx +119 -0
- package/src/components/section-header/index.ts +16 -0
- package/src/components/section-header/section-header.tsx +117 -0
- package/src/components/section-label/index.ts +2 -0
- package/src/components/section-label/section-label.tsx +38 -0
- package/src/components/segmented-control/index.ts +12 -0
- package/src/components/segmented-control/segmented-control.tsx +129 -0
- package/src/components/select/index.ts +40 -0
- package/src/components/select/select.tsx +382 -0
- package/src/components/spinner/index.ts +2 -0
- package/src/components/spinner/spinner.tsx +228 -0
- package/src/components/status/index.ts +2 -0
- package/src/components/status/status.tsx +152 -0
- package/src/components/tabs/index.ts +24 -0
- package/src/components/tabs/tabs.tsx +417 -0
- package/src/components/text/index.ts +2 -0
- package/src/components/text/text.tsx +130 -0
- package/src/components/textarea/index.ts +2 -0
- package/src/components/textarea/textarea.tsx +136 -0
- package/src/components/toggle-group/index.ts +12 -0
- package/src/components/toggle-group/toggle-group.tsx +111 -0
- package/src/components/tooltip/index.ts +14 -0
- package/src/components/tooltip/tooltip.tsx +186 -0
- package/src/fonts/brutal.css +2 -0
- package/src/index.ts +404 -0
- package/src/legacy/AttentionDot.tsx +28 -0
- package/src/legacy/AvatarListRow.tsx +87 -0
- package/src/legacy/AvatarSlot.tsx +157 -0
- package/src/legacy/Badge.tsx +95 -0
- package/src/legacy/Banner.tsx +74 -0
- package/src/legacy/BottomSheet.tsx +76 -0
- package/src/legacy/CheckMarker.tsx +72 -0
- package/src/legacy/Checkbox.tsx +81 -0
- package/src/legacy/ConversationPreviewCard.tsx +169 -0
- package/src/legacy/DismissBackdrop.tsx +68 -0
- package/src/legacy/EmptyState.tsx +63 -0
- package/src/legacy/ExperimentalBadge.tsx +19 -0
- package/src/legacy/FormField.tsx +106 -0
- package/src/legacy/KeyValueRow.tsx +67 -0
- package/src/legacy/Lightbox.tsx +119 -0
- package/src/legacy/NotificationCenter.tsx +217 -0
- package/src/legacy/PanelHeader.tsx +94 -0
- package/src/legacy/PreviewShell.tsx +40 -0
- package/src/legacy/QuotedMessageCard.tsx +181 -0
- package/src/legacy/ReorderablePanelTabs.tsx +151 -0
- package/src/legacy/SandboxedPreviewFrame.tsx +59 -0
- package/src/legacy/SearchInput.tsx +23 -0
- package/src/legacy/SectionEyebrow.tsx +60 -0
- package/src/legacy/SectionHeader.tsx +68 -0
- package/src/legacy/SegmentedControl.tsx +74 -0
- package/src/legacy/SlugInput.tsx +29 -0
- package/src/legacy/Spinner.tsx +89 -0
- package/src/legacy/StatusDot.tsx +51 -0
- package/src/legacy/SurfaceListItem.tsx +32 -0
- package/src/legacy/Textarea.tsx +137 -0
- package/src/legacy/button.tsx +100 -0
- package/src/legacy/context-menu-divider.tsx +12 -0
- package/src/legacy/menu-item.tsx +66 -0
- package/src/legacy/selection-popover.tsx +212 -0
- package/src/legacy/server-switcher-menu.tsx +203 -0
- package/src/legacy/source.css +240 -0
- package/src/lib/cn.ts +1 -0
- package/src/lib/styled-props.ts +11 -0
- package/src/lib/theme/theme-context.ts +28 -0
- package/src/lib/theme/theme-provider.tsx +181 -0
- package/src/lib/theme/use-theme.ts +12 -0
- package/src/lib/tv.ts +29 -0
- package/src/lib/without-children.ts +6 -0
- package/src/styles.css +467 -0
- package/src/wip.ts +255 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
use,
|
|
4
|
+
useCallback,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
type ComponentPropsWithRef,
|
|
8
|
+
type CSSProperties,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { Tabs as BaseTabs } from "@base-ui/react/tabs";
|
|
11
|
+
import {
|
|
12
|
+
closestCenter,
|
|
13
|
+
DndContext,
|
|
14
|
+
PointerSensor,
|
|
15
|
+
TouchSensor,
|
|
16
|
+
useSensor,
|
|
17
|
+
useSensors,
|
|
18
|
+
type DragEndEvent,
|
|
19
|
+
} from "@dnd-kit/core";
|
|
20
|
+
import {
|
|
21
|
+
arrayMove,
|
|
22
|
+
horizontalListSortingStrategy,
|
|
23
|
+
SortableContext,
|
|
24
|
+
useSortable,
|
|
25
|
+
} from "@dnd-kit/sortable";
|
|
26
|
+
import { tv, type VariantProps } from "../../lib/tv.ts";
|
|
27
|
+
import type { StyledProps } from "../../lib/styled-props.ts";
|
|
28
|
+
import { useThemeFamily } from "../../lib/theme/use-theme.ts";
|
|
29
|
+
|
|
30
|
+
const tabs = tv({
|
|
31
|
+
slots: {
|
|
32
|
+
root: ["w-full"],
|
|
33
|
+
list: [""],
|
|
34
|
+
background: [""],
|
|
35
|
+
tab: [
|
|
36
|
+
"relative z-10 flex shrink-0 touch-manipulation items-center gap-1.5 whitespace-nowrap outline-none",
|
|
37
|
+
"data-[reorderable=true]:cursor-grab data-[reorderable=true]:active:cursor-grabbing",
|
|
38
|
+
],
|
|
39
|
+
content: ["relative z-10 inline-flex items-center gap-[inherit]"],
|
|
40
|
+
label: [""],
|
|
41
|
+
panel: ["outline-none"],
|
|
42
|
+
indicator: [""],
|
|
43
|
+
},
|
|
44
|
+
variants: {
|
|
45
|
+
theme: {
|
|
46
|
+
brutal: {
|
|
47
|
+
list: [
|
|
48
|
+
"flex w-max max-w-full overflow-x-auto border-2 border-line-strong bg-layer-panel",
|
|
49
|
+
"[scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
50
|
+
],
|
|
51
|
+
background: "hidden",
|
|
52
|
+
tab: [
|
|
53
|
+
"bg-layer-panel px-4 py-1.5 text-xs font-semibold transition-colors",
|
|
54
|
+
"hover:bg-[color-mix(in_oklch,var(--line-strong)_6%,var(--layer-panel))]",
|
|
55
|
+
"[&+&]:before:absolute [&+&]:before:inset-y-0 [&+&]:before:left-0 [&+&]:before:w-0.5 [&+&]:before:bg-line-strong",
|
|
56
|
+
"data-[dragging-source=true]:before:hidden",
|
|
57
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:[stroke-width:1.5] [&_svg:not([class*='size-'])]:size-3.5",
|
|
58
|
+
"data-[active]:bg-primary-400 data-[active]:hover:bg-primary-400",
|
|
59
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-40",
|
|
60
|
+
"data-[dragging-source=true]:cursor-grabbing",
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
elegant: {},
|
|
64
|
+
},
|
|
65
|
+
variant: {
|
|
66
|
+
default: {},
|
|
67
|
+
underline: {},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
defaultVariants: {
|
|
71
|
+
variant: "default",
|
|
72
|
+
},
|
|
73
|
+
compoundVariants: [
|
|
74
|
+
{
|
|
75
|
+
theme: "elegant",
|
|
76
|
+
variant: "underline",
|
|
77
|
+
class: {
|
|
78
|
+
list: [
|
|
79
|
+
"group/tabs-list relative flex h-12 w-max max-w-full items-center gap-6 px-4",
|
|
80
|
+
"overflow-x-auto overflow-y-hidden overscroll-contain whitespace-nowrap",
|
|
81
|
+
"border-b border-line-muted",
|
|
82
|
+
"[scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
83
|
+
],
|
|
84
|
+
background: ["pointer-events-none absolute inset-x-0 top-0 z-0 h-px bg-line-muted"],
|
|
85
|
+
tab: [
|
|
86
|
+
"group/tabs-tab h-12 justify-center px-3 pb-3 pt-3.5",
|
|
87
|
+
"text-[0.8125rem] font-medium tracking-[-0.005em] text-foreground-placeholder",
|
|
88
|
+
"transition-colors duration-200 ease-out",
|
|
89
|
+
"hover:text-foreground-muted",
|
|
90
|
+
"data-[active]:text-foreground-strong",
|
|
91
|
+
"data-[active]:hover:text-foreground-strong",
|
|
92
|
+
"[&_svg]:pointer-events-none [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:[stroke-width:1.5]",
|
|
93
|
+
"[&_svg]:text-foreground-placeholder [&_svg]:transition-colors [&_svg]:duration-200 [&_svg]:ease-out",
|
|
94
|
+
"hover:[&_svg]:text-foreground-muted",
|
|
95
|
+
"data-[active]:[&_svg]:text-foreground-strong",
|
|
96
|
+
"data-[active]:hover:[&_svg]:text-foreground-strong",
|
|
97
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-40",
|
|
98
|
+
"data-[dragging-source=true]:cursor-grabbing",
|
|
99
|
+
],
|
|
100
|
+
indicator: [
|
|
101
|
+
"absolute bottom-0 left-[var(--active-tab-left)] z-10 h-0.5",
|
|
102
|
+
"w-[var(--active-tab-width)] bg-foreground-strong",
|
|
103
|
+
"transition-[left,width] duration-300 ease-[cubic-bezier(0.65,0,0.35,1)]",
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
theme: "elegant",
|
|
109
|
+
variant: "default",
|
|
110
|
+
class: {
|
|
111
|
+
list: [
|
|
112
|
+
"group/tabs-list relative isolate flex h-auto w-max max-w-full items-center gap-1",
|
|
113
|
+
"overflow-x-auto overflow-y-visible overscroll-contain whitespace-nowrap",
|
|
114
|
+
"p-px",
|
|
115
|
+
"[scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
116
|
+
],
|
|
117
|
+
background: ["pointer-events-none absolute inset-0 z-0 rounded-[7px] bg-layer-muted"],
|
|
118
|
+
tab: [
|
|
119
|
+
"group/tabs-tab h-9 justify-center gap-1.5 rounded-md px-3.5 py-2",
|
|
120
|
+
"text-[0.8125rem] font-medium tracking-[-0.005em] text-foreground-placeholder",
|
|
121
|
+
"transition-[background-color,box-shadow,color] duration-150 ease-out",
|
|
122
|
+
"hover:text-[oklch(42%_.006_285)]",
|
|
123
|
+
"data-[active]:text-[oklch(38%_.006_285)]",
|
|
124
|
+
"data-[active]:hover:text-[oklch(38%_.006_285)]",
|
|
125
|
+
"data-[active]:[&_[data-slot=tabs-label]]:text-[0.8125rem]",
|
|
126
|
+
"[&_svg]:pointer-events-none [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:[stroke-width:1.5]",
|
|
127
|
+
"[&_svg]:text-foreground-placeholder [&_svg]:transition-colors [&_svg]:duration-150 [&_svg]:ease-out",
|
|
128
|
+
"hover:[&_svg]:text-[oklch(42%_.006_285)]",
|
|
129
|
+
"data-[active]:[&_svg]:text-[oklch(38%_.006_285)]",
|
|
130
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-40",
|
|
131
|
+
"data-[dragging-source=true]:cursor-grabbing",
|
|
132
|
+
],
|
|
133
|
+
indicator: [
|
|
134
|
+
"absolute z-0 rounded-md bg-layer-panel",
|
|
135
|
+
"[left:calc(var(--active-tab-left)+1px)] [top:calc(var(--active-tab-top)+1.671875px)]",
|
|
136
|
+
"[height:calc(var(--active-tab-height)-3.34375px)] [width:calc(var(--active-tab-width)-2px)]",
|
|
137
|
+
"shadow-[0_0_0_1px_oklch(0%_0_0/.1),0_1px_1px_-1px_oklch(0%_0_0/.04),0_1px_2px_oklch(0%_0_0/.03)]",
|
|
138
|
+
"group-has-[>[data-slot=tabs-background]]/tabs-list:shadow-[0_0_0_1px_oklch(0%_0_0/.045),0_1px_1px_-1px_oklch(0%_0_0/.025),0_1px_2px_oklch(0%_0_0/.02)]",
|
|
139
|
+
"transition-[left,width] duration-300 ease-[cubic-bezier(0.65,0,0.35,1)]",
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
type TabsReorderContextValue = {
|
|
147
|
+
enabled: boolean;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const TabsReorderContext = createContext<TabsReorderContextValue>({ enabled: false });
|
|
151
|
+
const TabsValueContext = createContext<string | undefined>(undefined);
|
|
152
|
+
type TabsListVariant = "default" | "underline";
|
|
153
|
+
|
|
154
|
+
const TabsListVariantContext = createContext<TabsListVariant>("default");
|
|
155
|
+
const REORDER_ENABLED_CONTEXT = { enabled: true } as const satisfies TabsReorderContextValue;
|
|
156
|
+
|
|
157
|
+
export type TabsProps<T extends string = string> = StyledProps<
|
|
158
|
+
Omit<BaseTabs.Root.Props, "defaultValue" | "onValueChange" | "value">
|
|
159
|
+
> &
|
|
160
|
+
Omit<VariantProps<typeof tabs>, "theme"> & {
|
|
161
|
+
value?: T;
|
|
162
|
+
defaultValue?: T;
|
|
163
|
+
onValueChange?: (value: T, eventDetails: BaseTabs.Root.ChangeEventDetails) => void;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export function Tabs<T extends string = string>({
|
|
167
|
+
value,
|
|
168
|
+
defaultValue,
|
|
169
|
+
onValueChange,
|
|
170
|
+
className,
|
|
171
|
+
...props
|
|
172
|
+
}: TabsProps<T>) {
|
|
173
|
+
const [uncontrolledValue, setUncontrolledValue] = useState<T | undefined>(defaultValue);
|
|
174
|
+
const resolvedValue = value ?? uncontrolledValue;
|
|
175
|
+
const { root } = tabs();
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<TabsValueContext.Provider value={resolvedValue == null ? undefined : String(resolvedValue)}>
|
|
179
|
+
<BaseTabs.Root
|
|
180
|
+
value={value}
|
|
181
|
+
defaultValue={defaultValue}
|
|
182
|
+
onValueChange={(nextValue, eventDetails) => {
|
|
183
|
+
if (nextValue == null) return;
|
|
184
|
+
const typedValue = String(nextValue) as T;
|
|
185
|
+
if (value === undefined) setUncontrolledValue(typedValue);
|
|
186
|
+
onValueChange?.(typedValue, eventDetails);
|
|
187
|
+
}}
|
|
188
|
+
data-slot="tabs"
|
|
189
|
+
className={root({ className })}
|
|
190
|
+
{...props}
|
|
191
|
+
/>
|
|
192
|
+
</TabsValueContext.Provider>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type TabsListProps = StyledProps<BaseTabs.List.Props> & {
|
|
197
|
+
variant?: TabsListVariant;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export function TabsList({ className, children, variant = "default", ...props }: TabsListProps) {
|
|
201
|
+
const theme = useThemeFamily();
|
|
202
|
+
const resolvedVariant = theme === "elegant" ? variant : "underline";
|
|
203
|
+
const { list } = tabs({ theme, variant: resolvedVariant });
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<TabsListVariantContext.Provider value={resolvedVariant}>
|
|
207
|
+
<BaseTabs.List
|
|
208
|
+
data-slot="tabs-list"
|
|
209
|
+
data-variant={resolvedVariant}
|
|
210
|
+
className={list({ className })}
|
|
211
|
+
{...props}
|
|
212
|
+
>
|
|
213
|
+
{children}
|
|
214
|
+
{theme === "elegant" ? <TabsIndicator /> : null}
|
|
215
|
+
</BaseTabs.List>
|
|
216
|
+
</TabsListVariantContext.Provider>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export type SortableTabsListProps<T extends string = string> = TabsListProps & {
|
|
221
|
+
value: readonly T[];
|
|
222
|
+
onReorder: (value: T[]) => void;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export function SortableTabsList<T extends string = string>({
|
|
226
|
+
value,
|
|
227
|
+
onReorder,
|
|
228
|
+
children,
|
|
229
|
+
variant = "default",
|
|
230
|
+
...props
|
|
231
|
+
}: SortableTabsListProps<T>) {
|
|
232
|
+
const sensors = useSensors(
|
|
233
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
|
|
234
|
+
useSensor(TouchSensor, { activationConstraint: { delay: 350, tolerance: 8 } }),
|
|
235
|
+
);
|
|
236
|
+
const tabIds = useMemo(() => value.map(String), [value]);
|
|
237
|
+
const handleDragEnd = useCallback(
|
|
238
|
+
({ active, over }: DragEndEvent) => {
|
|
239
|
+
if (!over || active.id === over.id) return;
|
|
240
|
+
const oldIndex = tabIds.indexOf(String(active.id));
|
|
241
|
+
const newIndex = tabIds.indexOf(String(over.id));
|
|
242
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
243
|
+
onReorder(arrayMove([...value], oldIndex, newIndex) as T[]);
|
|
244
|
+
},
|
|
245
|
+
[onReorder, tabIds, value],
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
|
250
|
+
<SortableContext items={tabIds} strategy={horizontalListSortingStrategy}>
|
|
251
|
+
<TabsReorderContext.Provider value={REORDER_ENABLED_CONTEXT}>
|
|
252
|
+
<TabsList variant={variant} {...props}>
|
|
253
|
+
{children}
|
|
254
|
+
</TabsList>
|
|
255
|
+
</TabsReorderContext.Provider>
|
|
256
|
+
</SortableContext>
|
|
257
|
+
</DndContext>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export type TabsTabProps<T extends string = string> = StyledProps<BaseTabs.Tab.Props> & {
|
|
262
|
+
value: T;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export function TabsTab<T extends string = string>({ value, ...props }: TabsTabProps<T>) {
|
|
266
|
+
return <TabsTabButton value={value} {...props} />;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export type SortableTabsTabProps<T extends string = string> = TabsTabProps<T>;
|
|
270
|
+
|
|
271
|
+
export function SortableTabsTab<T extends string = string>({
|
|
272
|
+
value,
|
|
273
|
+
...props
|
|
274
|
+
}: SortableTabsTabProps<T>) {
|
|
275
|
+
const reorder = use(TabsReorderContext);
|
|
276
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
277
|
+
id: value,
|
|
278
|
+
disabled: !reorder.enabled,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (!reorder.enabled) {
|
|
282
|
+
return <TabsTabButton value={value} {...props} />;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const { role: _sortableRole, tabIndex: _sortableTabIndex, ...sortableAttributes } = attributes;
|
|
286
|
+
const baseStyle = typeof props.style === "function" ? undefined : props.style;
|
|
287
|
+
const style: CSSProperties = {
|
|
288
|
+
...baseStyle,
|
|
289
|
+
transform: transform ? `translate3d(${transform.x}px, 0, 0)` : undefined,
|
|
290
|
+
transition,
|
|
291
|
+
zIndex: isDragging ? 20 : baseStyle?.zIndex,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<TabsTabButton
|
|
296
|
+
value={value}
|
|
297
|
+
sortableRef={setNodeRef}
|
|
298
|
+
data-dragging-source={isDragging ? "true" : undefined}
|
|
299
|
+
data-reorderable="true"
|
|
300
|
+
style={style}
|
|
301
|
+
{...props}
|
|
302
|
+
{...sortableAttributes}
|
|
303
|
+
{...listeners}
|
|
304
|
+
/>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function TabsTabButton<T extends string = string>({
|
|
309
|
+
value,
|
|
310
|
+
sortableRef,
|
|
311
|
+
className,
|
|
312
|
+
children,
|
|
313
|
+
...props
|
|
314
|
+
}: TabsTabProps<T> & {
|
|
315
|
+
sortableRef?: (node: HTMLElement | null) => void;
|
|
316
|
+
}) {
|
|
317
|
+
const theme = useThemeFamily();
|
|
318
|
+
const variant = use(TabsListVariantContext);
|
|
319
|
+
const { content, tab } = tabs({ theme, variant: theme === "elegant" ? variant : "underline" });
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<BaseTabs.Tab
|
|
323
|
+
ref={(node) => {
|
|
324
|
+
sortableRef?.(node);
|
|
325
|
+
}}
|
|
326
|
+
value={value}
|
|
327
|
+
data-slot="tabs-tab"
|
|
328
|
+
className={tab({ className })}
|
|
329
|
+
{...props}
|
|
330
|
+
>
|
|
331
|
+
<span data-slot="tabs-tab-content" className={content()}>
|
|
332
|
+
{children}
|
|
333
|
+
</span>
|
|
334
|
+
</BaseTabs.Tab>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export type TabsLabelProps = ComponentPropsWithRef<"span">;
|
|
339
|
+
|
|
340
|
+
export function TabsLabel({ className, ...props }: TabsLabelProps) {
|
|
341
|
+
const { label } = tabs();
|
|
342
|
+
return <span data-slot="tabs-label" className={label({ className })} {...props} />;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export type TabsPanelProps = StyledProps<BaseTabs.Panel.Props>;
|
|
346
|
+
|
|
347
|
+
export function TabsPanel({ className, ...props }: TabsPanelProps) {
|
|
348
|
+
const { panel } = tabs();
|
|
349
|
+
return <BaseTabs.Panel data-slot="tabs-panel" className={panel({ className })} {...props} />;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export type TabsBackgroundProps = ComponentPropsWithRef<"span">;
|
|
353
|
+
|
|
354
|
+
export function TabsBackground({ className, ...props }: TabsBackgroundProps) {
|
|
355
|
+
const theme = useThemeFamily();
|
|
356
|
+
const variant = use(TabsListVariantContext);
|
|
357
|
+
const { background } = tabs({ theme, variant: theme === "elegant" ? variant : "underline" });
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<span
|
|
361
|
+
aria-hidden="true"
|
|
362
|
+
data-slot="tabs-background"
|
|
363
|
+
className={background({ className })}
|
|
364
|
+
{...props}
|
|
365
|
+
/>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export type TabsIndicatorProps = StyledProps<BaseTabs.Indicator.Props>;
|
|
370
|
+
|
|
371
|
+
export function TabsIndicator({ className, ...props }: TabsIndicatorProps) {
|
|
372
|
+
const theme = useThemeFamily();
|
|
373
|
+
const variant = use(TabsListVariantContext);
|
|
374
|
+
const { indicator } = tabs({ theme, variant: theme === "elegant" ? variant : "underline" });
|
|
375
|
+
return (
|
|
376
|
+
<BaseTabs.Indicator
|
|
377
|
+
data-slot="tabs-indicator"
|
|
378
|
+
className={indicator({ className })}
|
|
379
|
+
{...props}
|
|
380
|
+
/>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export type OrderedTabItem<T extends string = string> = {
|
|
385
|
+
id: T;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
export function useOrderedTabs<T extends OrderedTabItem<string>>(
|
|
389
|
+
items: readonly T[],
|
|
390
|
+
storedOrder: readonly string[],
|
|
391
|
+
): T[] {
|
|
392
|
+
return useMemo(() => reconcileTabOrder(items, storedOrder), [items, storedOrder]);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function reconcileTabOrder<T extends OrderedTabItem<string>>(
|
|
396
|
+
items: readonly T[],
|
|
397
|
+
order: readonly string[],
|
|
398
|
+
): T[] {
|
|
399
|
+
const byId = new Map(items.map((item) => [item.id, item]));
|
|
400
|
+
const next: T[] = [];
|
|
401
|
+
const seen = new Set<string>();
|
|
402
|
+
|
|
403
|
+
for (const id of order) {
|
|
404
|
+
const item = byId.get(id);
|
|
405
|
+
if (!item || seen.has(item.id)) continue;
|
|
406
|
+
next.push(item);
|
|
407
|
+
seen.add(item.id);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
for (const item of items) {
|
|
411
|
+
if (seen.has(item.id)) continue;
|
|
412
|
+
next.push(item);
|
|
413
|
+
seen.add(item.id);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return next;
|
|
417
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { tv, type VariantProps } from "../../lib/tv.ts";
|
|
4
|
+
|
|
5
|
+
const textColorVariants = {
|
|
6
|
+
default: ["text-foreground-strong"],
|
|
7
|
+
primary: ["text-primary-700"],
|
|
8
|
+
information: ["text-info-dark"],
|
|
9
|
+
muted: ["text-foreground-muted"],
|
|
10
|
+
accent: ["text-accent-700"],
|
|
11
|
+
success: ["text-success-base"],
|
|
12
|
+
warning: ["text-warning-base"],
|
|
13
|
+
danger: ["text-danger-base"],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const heading = tv({
|
|
17
|
+
base: ["m-0 font-heading font-medium text-foreground-strong"],
|
|
18
|
+
variants: {
|
|
19
|
+
level: {
|
|
20
|
+
1: ["text-[3.5rem] tracking-[-0.01em] leading-[64px]"],
|
|
21
|
+
2: ["text-[3rem] tracking-[-0.01em] leading-[56px]"],
|
|
22
|
+
3: ["text-[2.5rem] tracking-[-0.01em] leading-[48px]"],
|
|
23
|
+
4: ["text-[2rem] tracking-[-0.005em] leading-[40px]"],
|
|
24
|
+
5: ["text-[1.5rem] leading-8"],
|
|
25
|
+
6: ["text-[1.25rem] leading-7"],
|
|
26
|
+
},
|
|
27
|
+
variant: textColorVariants,
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
level: 1,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const sans = tv({
|
|
35
|
+
base: ["m-0 font-sans font-normal"],
|
|
36
|
+
variants: {
|
|
37
|
+
size: {
|
|
38
|
+
large: ["text-lg leading-7 text-foreground"],
|
|
39
|
+
body: ["text-base leading-6 text-foreground"],
|
|
40
|
+
small: ["text-sm leading-5 text-foreground-muted"],
|
|
41
|
+
caption: ["text-xs leading-4 text-foreground-muted"],
|
|
42
|
+
},
|
|
43
|
+
variant: textColorVariants,
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: {
|
|
46
|
+
size: "body",
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const mono = tv({
|
|
51
|
+
base: ["m-0 font-mono font-normal"],
|
|
52
|
+
variants: {
|
|
53
|
+
size: {
|
|
54
|
+
code: ["text-sm leading-5 text-foreground"],
|
|
55
|
+
meta: ["text-xs leading-4 text-foreground-muted"],
|
|
56
|
+
eyebrow: [
|
|
57
|
+
"text-[10px] font-medium uppercase tracking-wide leading-none",
|
|
58
|
+
"text-foreground-placeholder",
|
|
59
|
+
],
|
|
60
|
+
tabular: ["text-sm tabular-nums leading-5 text-foreground"],
|
|
61
|
+
},
|
|
62
|
+
variant: textColorVariants,
|
|
63
|
+
},
|
|
64
|
+
defaultVariants: {
|
|
65
|
+
size: "code",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export type TextHeadingProps = useRender.ComponentProps<"h1"> & VariantProps<typeof heading>;
|
|
70
|
+
|
|
71
|
+
export function TextHeading({ level = 1, variant, className, render, ...props }: TextHeadingProps) {
|
|
72
|
+
const defaultProps: useRender.ElementProps<"h1"> = {
|
|
73
|
+
className: heading({ level, variant, className }),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return useRender({
|
|
77
|
+
defaultTagName: `h${level}`,
|
|
78
|
+
render,
|
|
79
|
+
props: mergeProps<"h1">(defaultProps, props),
|
|
80
|
+
state: {
|
|
81
|
+
slot: "text-heading",
|
|
82
|
+
level,
|
|
83
|
+
variant,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type TextSansProps = useRender.ComponentProps<"p"> & VariantProps<typeof sans>;
|
|
89
|
+
|
|
90
|
+
export function TextSans({ size = "body", variant, className, render, ...props }: TextSansProps) {
|
|
91
|
+
const defaultProps: useRender.ElementProps<"p"> = {
|
|
92
|
+
className: sans({ size, variant, className }),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return useRender({
|
|
96
|
+
defaultTagName: "p",
|
|
97
|
+
render,
|
|
98
|
+
props: mergeProps<"p">(defaultProps, props),
|
|
99
|
+
state: {
|
|
100
|
+
slot: "text-sans",
|
|
101
|
+
size,
|
|
102
|
+
variant,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type TextMonoProps = useRender.ComponentProps<"p"> & VariantProps<typeof mono>;
|
|
108
|
+
|
|
109
|
+
export function TextMono({ size = "code", variant, className, render, ...props }: TextMonoProps) {
|
|
110
|
+
const defaultProps: useRender.ElementProps<"p"> = {
|
|
111
|
+
className: mono({ size, variant, className }),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return useRender({
|
|
115
|
+
defaultTagName: "p",
|
|
116
|
+
render,
|
|
117
|
+
props: mergeProps<"p">(defaultProps, props),
|
|
118
|
+
state: {
|
|
119
|
+
slot: "text-mono",
|
|
120
|
+
size,
|
|
121
|
+
variant,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const Text = {
|
|
127
|
+
Heading: TextHeading,
|
|
128
|
+
Sans: TextSans,
|
|
129
|
+
Mono: TextMono,
|
|
130
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from "react";
|
|
2
|
+
import { tv } from "../../lib/tv.ts";
|
|
3
|
+
import { useThemeFamily } from "../../lib/theme/use-theme.ts";
|
|
4
|
+
import type { WithoutChildren } from "../../lib/without-children.ts";
|
|
5
|
+
|
|
6
|
+
const textarea = tv({
|
|
7
|
+
slots: {
|
|
8
|
+
root: ["block w-full min-h-24 resize-y px-3 py-2 outline-none"],
|
|
9
|
+
},
|
|
10
|
+
variants: {
|
|
11
|
+
theme: {
|
|
12
|
+
brutal: {
|
|
13
|
+
root: [
|
|
14
|
+
"border-2 border-line-strong bg-layer-panel font-heading",
|
|
15
|
+
"shadow-sm focus:shadow-md",
|
|
16
|
+
"aria-invalid:border-brutal-red aria-invalid:bg-brutal-red/5 aria-invalid:shadow-none aria-invalid:ring-2 aria-invalid:ring-brutal-red/60",
|
|
17
|
+
"disabled:cursor-not-allowed disabled:opacity-60 disabled:shadow-none",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
elegant: {
|
|
21
|
+
root: [
|
|
22
|
+
"rounded-md border border-[oklch(0.94_0_0)] bg-layer-field shadow-none",
|
|
23
|
+
"text-sm text-[oklch(0.205_0_0)] transition duration-200 ease-out dark:text-foreground",
|
|
24
|
+
"placeholder:text-foreground-placeholder/70",
|
|
25
|
+
"hover:not-aria-invalid:border-[oklch(0.91_0_0)] hover:shadow-[oklch(0_0_0_/_0)_0px_0px_0px_0px,oklch(0_0_0_/_0)_0px_0px_0px_0px,oklch(0.159_0.016_266.594_/_0.03)_0px_1px_2px_0px]",
|
|
26
|
+
"focus:border-line-strong/25 focus:bg-layer-field",
|
|
27
|
+
"focus-visible:ring-2 focus-visible:ring-line-strong/8",
|
|
28
|
+
"aria-invalid:border-danger-base/45 aria-invalid:hover:border-danger-base/75 aria-invalid:focus-visible:border-danger-base/75 aria-invalid:focus-visible:ring-3 aria-invalid:focus-visible:ring-danger-base/12",
|
|
29
|
+
"disabled:cursor-not-allowed disabled:border-line-subtle/60 disabled:bg-layer-muted/50 disabled:text-foreground-disabled disabled:placeholder:text-foreground-disabled",
|
|
30
|
+
"disabled:hover:border-line-subtle/60 disabled:hover:shadow-none",
|
|
31
|
+
"aria-invalid:disabled:border-line-subtle/60 aria-invalid:disabled:ring-0",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const textareaGroup = tv({
|
|
39
|
+
slots: {
|
|
40
|
+
root: ["w-full"],
|
|
41
|
+
},
|
|
42
|
+
variants: {
|
|
43
|
+
theme: {
|
|
44
|
+
brutal: {
|
|
45
|
+
root: "flex flex-col items-end gap-1",
|
|
46
|
+
},
|
|
47
|
+
elegant: {
|
|
48
|
+
root: [
|
|
49
|
+
"relative",
|
|
50
|
+
"has-[>[data-slot=textarea-counter]]:[&>[data-slot=textarea]]:scroll-pb-6",
|
|
51
|
+
"has-[>[data-slot=textarea-counter]]:[&>[data-slot=textarea]]:pr-16",
|
|
52
|
+
"[&>[data-slot=textarea-counter]]:pointer-events-none [&>[data-slot=textarea-counter]]:absolute",
|
|
53
|
+
"[&>[data-slot=textarea-counter]]:right-3 [&>[data-slot=textarea-counter]]:bottom-2.5 [&>[data-slot=textarea-counter]]:z-10",
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const textareaCounter = tv({
|
|
61
|
+
slots: {
|
|
62
|
+
root: ["font-mono text-xs tabular-nums"],
|
|
63
|
+
},
|
|
64
|
+
variants: {
|
|
65
|
+
theme: {
|
|
66
|
+
brutal: {
|
|
67
|
+
root: [
|
|
68
|
+
"text-foreground/50",
|
|
69
|
+
"group-data-[invalid]/field:font-bold group-data-[invalid]/field:text-danger-base",
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
elegant: {
|
|
73
|
+
root: [
|
|
74
|
+
"text-[11px] leading-none text-foreground-placeholder/80 transition duration-200 ease-out",
|
|
75
|
+
"group-data-[invalid]/field:font-medium group-data-[invalid]/field:text-danger-base",
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
overLimit: {
|
|
80
|
+
true: {
|
|
81
|
+
root: "text-danger-base",
|
|
82
|
+
},
|
|
83
|
+
false: {
|
|
84
|
+
root: "",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
defaultVariants: {
|
|
89
|
+
overLimit: false,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export type TextareaProps = WithoutChildren<ComponentPropsWithRef<"textarea">>;
|
|
94
|
+
|
|
95
|
+
export type TextareaGroupProps = ComponentPropsWithRef<"div">;
|
|
96
|
+
|
|
97
|
+
export function TextareaGroup({ className, ...props }: TextareaGroupProps) {
|
|
98
|
+
const theme = useThemeFamily();
|
|
99
|
+
const { root } = textareaGroup({ theme });
|
|
100
|
+
|
|
101
|
+
return <div data-slot="textarea-group" className={root({ className })} {...props} />;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function Textarea({ className, ...props }: TextareaProps) {
|
|
105
|
+
const theme = useThemeFamily();
|
|
106
|
+
const { root } = textarea({ theme });
|
|
107
|
+
|
|
108
|
+
return <textarea data-slot="textarea" className={root({ className })} {...props} />;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface TextareaCounterProps extends WithoutChildren<ComponentPropsWithRef<"span">> {
|
|
112
|
+
value?: string;
|
|
113
|
+
count?: number;
|
|
114
|
+
limit: number;
|
|
115
|
+
getLength?: (value: string) => number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function TextareaCounter({
|
|
119
|
+
value,
|
|
120
|
+
count,
|
|
121
|
+
limit,
|
|
122
|
+
getLength,
|
|
123
|
+
className,
|
|
124
|
+
...props
|
|
125
|
+
}: TextareaCounterProps) {
|
|
126
|
+
const theme = useThemeFamily();
|
|
127
|
+
const text = value ?? "";
|
|
128
|
+
const length = count ?? getLength?.(text) ?? text.length;
|
|
129
|
+
const overLimit = length > limit;
|
|
130
|
+
const { root } = textareaCounter({ theme, overLimit });
|
|
131
|
+
return (
|
|
132
|
+
<span data-slot="textarea-counter" className={root({ className })} {...props}>
|
|
133
|
+
{length}/{limit}
|
|
134
|
+
</span>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ToggleGroup,
|
|
3
|
+
ToggleGroupCount,
|
|
4
|
+
ToggleGroupItem,
|
|
5
|
+
ToggleGroupLabel,
|
|
6
|
+
} from "./toggle-group.tsx";
|
|
7
|
+
export type {
|
|
8
|
+
ToggleGroupCountProps,
|
|
9
|
+
ToggleGroupItemProps,
|
|
10
|
+
ToggleGroupLabelProps,
|
|
11
|
+
ToggleGroupProps,
|
|
12
|
+
} from "./toggle-group.tsx";
|