@forthtilliath/forth-ui 0.2.4 → 0.3.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/README.md +16 -12
- package/dist/components/column-menu/column-menu.d.ts +39 -0
- package/dist/components/column-menu/column-menu.d.ts.map +1 -0
- package/dist/components/column-menu/column-menu.js +74 -0
- package/dist/components/column-menu/index.d.ts +6 -0
- package/dist/components/column-menu/index.d.ts.map +1 -0
- package/dist/components/column-menu/index.js +3 -0
- package/dist/components/column-menu/sortable-column-row.d.ts +13 -0
- package/dist/components/column-menu/sortable-column-row.d.ts.map +1 -0
- package/dist/components/column-menu/sortable-column-row.js +22 -0
- package/dist/components/column-menu/types.d.ts +21 -0
- package/dist/components/column-menu/types.d.ts.map +1 -0
- package/dist/components/column-menu/types.js +7 -0
- package/dist/components/column-menu/use-dnd-sensors.d.ts +6 -0
- package/dist/components/column-menu/use-dnd-sensors.d.ts.map +1 -0
- package/dist/components/column-menu/use-dnd-sensors.js +14 -0
- package/dist/components/command-palette/command-palette.js +1 -1
- package/dist/components/gradient-frame/gradient-frame.d.ts +27 -0
- package/dist/components/gradient-frame/gradient-frame.d.ts.map +1 -0
- package/dist/components/gradient-frame/gradient-frame.js +16 -0
- package/dist/components/gradient-frame/index.d.ts +3 -0
- package/dist/components/gradient-frame/index.d.ts.map +1 -0
- package/dist/components/gradient-frame/index.js +1 -0
- package/dist/components/grid/variants.d.ts +1 -1
- package/dist/components/icon-bubble/icon-bubble.d.ts +21 -0
- package/dist/components/icon-bubble/icon-bubble.d.ts.map +1 -0
- package/dist/components/icon-bubble/icon-bubble.js +16 -0
- package/dist/components/icon-bubble/index.d.ts +4 -0
- package/dist/components/icon-bubble/index.d.ts.map +1 -0
- package/dist/components/icon-bubble/index.js +1 -0
- package/dist/components/icon-bubble/variants.d.ts +12 -0
- package/dist/components/icon-bubble/variants.d.ts.map +1 -0
- package/dist/components/icon-bubble/variants.js +19 -0
- package/dist/components/image-input/image-input.js +1 -1
- package/dist/components/relative-time/relative-time.js +1 -1
- package/dist/components/toggle-switch/index.d.ts +4 -0
- package/dist/components/toggle-switch/index.d.ts.map +1 -0
- package/dist/components/toggle-switch/index.js +1 -0
- package/dist/components/toggle-switch/toggle-switch.d.ts +37 -0
- package/dist/components/toggle-switch/toggle-switch.d.ts.map +1 -0
- package/dist/components/toggle-switch/toggle-switch.js +25 -0
- package/dist/components/toggle-switch/variants.d.ts +9 -0
- package/dist/components/toggle-switch/variants.d.ts.map +1 -0
- package/dist/components/toggle-switch/variants.js +30 -0
- package/dist/globals.css +290 -18
- package/package.json +24 -6
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ built from `Popover` + `Command`, a `PasswordInput` with a strength meter, a
|
|
|
8
8
|
`Dropdrawer` that's a `DropdownMenu` on desktop and a `Drawer` on mobile —
|
|
9
9
|
so you don't have to wire the same recipe from scratch in every project.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
62 components across 8 categories, styled with Tailwind CSS and
|
|
12
12
|
`class-variance-authority`, fully typed, and browsable in Storybook
|
|
13
13
|
(`pnpm run dev:sb` from the repo root).
|
|
14
14
|
|
|
@@ -186,6 +186,7 @@ Grouped the same way as the Storybook sidebar (`forth-ui/<Category>/...`).
|
|
|
186
186
|
| `Rating` | `components/rating` | A star rating control with keyboard navigation and hover preview. |
|
|
187
187
|
| `SubmitButton` | `components/submit-button` | A submit `Button` that shows its own loading state via `useFormStatus`. |
|
|
188
188
|
| `TagsInput` | `components/tags-input` | A flexible input for adding/removing multiple tags. |
|
|
189
|
+
| `ToggleSwitch` | `components/toggle-switch` | An on/off switch with an optional visible label and description. |
|
|
189
190
|
|
|
190
191
|
### Feedback
|
|
191
192
|
|
|
@@ -211,17 +212,20 @@ Grouped the same way as the Storybook sidebar (`forth-ui/<Category>/...`).
|
|
|
211
212
|
|
|
212
213
|
### Data Display
|
|
213
214
|
|
|
214
|
-
| Component
|
|
215
|
-
|
|
|
216
|
-
| `Avatar`, `AvatarGroup`
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
215
|
+
| Component | Import | Description |
|
|
216
|
+
| ------------------------------ | ----------------------------- | ------------------------------------------------------------------------ |
|
|
217
|
+
| `Avatar`, `AvatarGroup` | `components/avatar` | An image with a fallback, status/badge indicators, and stacked grouping. |
|
|
218
|
+
| `ColumnMenu` (`useDndSensors`) | `components/column-menu` | A dropdown to show/hide and reorder (drag and drop) a table's columns. |
|
|
219
|
+
| `DescriptionList` | `components/description-list` | A description list, with terms and descriptions. |
|
|
220
|
+
| `FileList` | `components/file-list` | Displays a list of files with name, size, and upload progress. |
|
|
221
|
+
| `GradientFrame` | `components/gradient-frame` | A visual laid on an offset, gradient-filled frame. |
|
|
222
|
+
| `IconBubble` | `components/icon-bubble` | An icon inside a round, tinted bubble. |
|
|
223
|
+
| `Kbd` | `components/kbd` | Displays which key or combination of keys performs a given action. |
|
|
224
|
+
| `QrCode` | `components/qr-code` | Generates a QR code from a string, rendered as inline SVG. |
|
|
225
|
+
| `RelativeTime` | `components/relative-time` | Displays a live-updating time, defaulting to the local timezone. |
|
|
226
|
+
| `Sortable` | `components/sortable` | A drag-to-reorder list built on native HTML5 drag events. |
|
|
227
|
+
| `Timeline` | `components/timeline` | A vertical sequence of dated events, connected by a line. |
|
|
228
|
+
| `Tree` | `components/tree` | A hierarchical, expandable/collapsible list of nodes. |
|
|
225
229
|
|
|
226
230
|
### Code & Content
|
|
227
231
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ColumnMenuColumn, ColumnMenuLabels } from "./types";
|
|
2
|
+
export interface ColumnMenuProps<C extends ColumnMenuColumn> {
|
|
3
|
+
columns: C[];
|
|
4
|
+
/** Restored by the reset button. */
|
|
5
|
+
defaultColumns: C[];
|
|
6
|
+
/** Receives an updater, so a `useState` setter can be passed directly. */
|
|
7
|
+
onColumnsChange: (update: (prev: C[]) => C[]) => void;
|
|
8
|
+
/** Overrides some or all of the displayed texts. */
|
|
9
|
+
labels?: Partial<ColumnMenuLabels>;
|
|
10
|
+
/** Side of the trigger the dropdown is aligned to (default: `"end"`). */
|
|
11
|
+
align?: "start" | "end";
|
|
12
|
+
/**
|
|
13
|
+
* Custom classes for each part of the component.
|
|
14
|
+
*
|
|
15
|
+
* - `root` wraps the trigger and the dropdown.
|
|
16
|
+
* - `trigger` is the toggle button.
|
|
17
|
+
* - `badge` is the "visible / total" counter inside the trigger.
|
|
18
|
+
* - `content` is the dropdown panel.
|
|
19
|
+
* - `header` is the title + reset bar.
|
|
20
|
+
* - `hint` is the usage hint under the header.
|
|
21
|
+
* - `list` wraps the rows.
|
|
22
|
+
* - `row` is applied to every column row.
|
|
23
|
+
*/
|
|
24
|
+
className?: Partial<Record<"root" | "trigger" | "badge" | "content" | "header" | "hint" | "list" | "row", string>>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A dropdown to show/hide and reorder (drag and drop, touch and keyboard
|
|
28
|
+
* included) the columns of a table, with a "visible / total" counter and a
|
|
29
|
+
* reset button. Closes on outside click and `Escape`.
|
|
30
|
+
*
|
|
31
|
+
* Pair it with `createColumnStorage` from `@forthtilliath/ts-kit` to persist
|
|
32
|
+
* the user's layout.
|
|
33
|
+
*
|
|
34
|
+
* @version 0.1.0
|
|
35
|
+
* @author Forth
|
|
36
|
+
* @copyright 2026 Forth
|
|
37
|
+
*/
|
|
38
|
+
export declare function ColumnMenu<C extends ColumnMenuColumn>({ columns, defaultColumns, onColumnsChange, labels, align, className, }: ColumnMenuProps<C>): import("react").JSX.Element;
|
|
39
|
+
//# sourceMappingURL=column-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-menu.d.ts","sourceRoot":"","sources":["../../../src/components/column-menu/column-menu.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAIlE,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,gBAAgB;IACzD,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,oCAAoC;IACpC,cAAc,EAAE,CAAC,EAAE,CAAC;IACpB,0EAA0E;IAC1E,eAAe,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC;IACtD,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnC,yEAAyE;IACzE,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IACxB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,OAAO,CACjB,MAAM,CACF,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,GACT,QAAQ,GACR,MAAM,GACN,MAAM,GACN,KAAK,EACP,MAAM,CACP,CACF,CAAC;CACH;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,gBAAgB,EAAE,EACrD,OAAO,EACP,cAAc,EACd,eAAe,EACf,MAAM,EACN,KAAa,EACb,SAAS,GACV,EAAE,eAAe,CAAC,CAAC,CAAC,+BAyJpB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
4
|
+
import { closestCenter, DndContext } from "@dnd-kit/core";
|
|
5
|
+
import { arrayMove, SortableContext, verticalListSortingStrategy, } from "@dnd-kit/sortable";
|
|
6
|
+
import { ChevronDown, RotateCcw } from "lucide-react";
|
|
7
|
+
import { cn } from "@forthtilliath/shadcn-ui/lib/utils";
|
|
8
|
+
import { SortableColumnRow } from "./sortable-column-row";
|
|
9
|
+
import { DEFAULT_COLUMN_MENU_LABELS } from "./types";
|
|
10
|
+
import { useDndSensors } from "./use-dnd-sensors";
|
|
11
|
+
/**
|
|
12
|
+
* A dropdown to show/hide and reorder (drag and drop, touch and keyboard
|
|
13
|
+
* included) the columns of a table, with a "visible / total" counter and a
|
|
14
|
+
* reset button. Closes on outside click and `Escape`.
|
|
15
|
+
*
|
|
16
|
+
* Pair it with `createColumnStorage` from `@forthtilliath/ts-kit` to persist
|
|
17
|
+
* the user's layout.
|
|
18
|
+
*
|
|
19
|
+
* @version 0.1.0
|
|
20
|
+
* @author Forth
|
|
21
|
+
* @copyright 2026 Forth
|
|
22
|
+
*/
|
|
23
|
+
export function ColumnMenu({ columns, defaultColumns, onColumnsChange, labels, align = "end", className, }) {
|
|
24
|
+
const text = { ...DEFAULT_COLUMN_MENU_LABELS, ...labels };
|
|
25
|
+
// Stable id: avoids a hydration mismatch on dnd-kit's aria-describedby
|
|
26
|
+
const dndId = useId();
|
|
27
|
+
const contentId = useId();
|
|
28
|
+
const rootRef = useRef(null);
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
const sensors = useDndSensors();
|
|
31
|
+
const visibleCount = columns.filter((c) => c.visible).length;
|
|
32
|
+
const allVisible = visibleCount === columns.length;
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!open)
|
|
35
|
+
return;
|
|
36
|
+
function handlePointerDown(event) {
|
|
37
|
+
if (!rootRef.current?.contains(event.target))
|
|
38
|
+
setOpen(false);
|
|
39
|
+
}
|
|
40
|
+
function handleKeyDown(event) {
|
|
41
|
+
if (event.key === "Escape")
|
|
42
|
+
setOpen(false);
|
|
43
|
+
}
|
|
44
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
45
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
46
|
+
return () => {
|
|
47
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
48
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
49
|
+
};
|
|
50
|
+
}, [open]);
|
|
51
|
+
function handleDragEnd({ active, over }) {
|
|
52
|
+
if (!over || active.id === over.id)
|
|
53
|
+
return;
|
|
54
|
+
onColumnsChange((prev) => {
|
|
55
|
+
const oldIndex = prev.findIndex((c) => c.key === active.id);
|
|
56
|
+
const newIndex = prev.findIndex((c) => c.key === over.id);
|
|
57
|
+
return arrayMove(prev, oldIndex, newIndex);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function toggleColumn(key) {
|
|
61
|
+
onColumnsChange((prev) => prev.map((c) => (c.key === key ? { ...c, visible: !c.visible } : c)));
|
|
62
|
+
}
|
|
63
|
+
function reset() {
|
|
64
|
+
onColumnsChange(() => defaultColumns);
|
|
65
|
+
setOpen(false);
|
|
66
|
+
}
|
|
67
|
+
return (_jsxs("div", { ref: rootRef, "data-slot": "column-menu", className: cn("relative", className?.root), children: [_jsxs("button", { type: "button", "aria-expanded": open, "aria-controls": contentId, onClick: () => {
|
|
68
|
+
setOpen((v) => !v);
|
|
69
|
+
}, className: cn("flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-sm transition-all", open
|
|
70
|
+
? "border-primary bg-primary/10 text-primary"
|
|
71
|
+
: "border-border text-foreground/60 hover:border-primary hover:text-foreground", className?.trigger), children: [_jsx("span", { className: "hidden sm:inline", children: text.trigger }), _jsxs("span", { className: cn("rounded-full px-1.5 py-0.5 text-xs font-medium", allVisible
|
|
72
|
+
? "bg-foreground/10 text-foreground/40"
|
|
73
|
+
: "bg-primary text-primary-foreground", className?.badge), children: [visibleCount, "/", columns.length] }), _jsx(ChevronDown, { className: cn("size-3.5 opacity-60 transition-transform", open && "rotate-180"), "aria-hidden": "true" })] }), open && (_jsxs("div", { id: contentId, "data-slot": "column-menu-content", className: cn("bg-background border-border absolute top-full z-20 mt-2 w-64 overflow-hidden rounded-xl border shadow-xl", align === "end" ? "right-0" : "left-0", className?.content), children: [_jsxs("div", { className: cn("border-border bg-muted flex items-center justify-between border-b px-4 py-3", className?.header), children: [_jsx("p", { className: "text-foreground text-xs font-medium", children: text.title }), _jsxs("button", { type: "button", onClick: reset, className: "text-foreground/50 hover:text-primary flex items-center gap-1 text-xs transition-colors", children: [_jsx(RotateCcw, { className: "size-3", "aria-hidden": "true" }), text.reset] })] }), _jsx("p", { className: cn("border-border bg-muted/50 text-foreground/50 border-b px-4 py-2 text-xs", className?.hint), children: text.hint }), _jsx("div", { className: cn("p-2", className?.list), children: _jsx(DndContext, { id: dndId, sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: columns.map((c) => c.key), strategy: verticalListSortingStrategy, children: columns.map((column, index) => (_jsx(SortableColumnRow, { column: column, index: index, total: columns.length, dragHandleLabel: text.dragHandle(column.label), onToggle: toggleColumn, className: className?.row }, column.key))) }) }) })] }))] }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { ColumnMenuProps } from "./column-menu";
|
|
2
|
+
export { ColumnMenu } from "./column-menu";
|
|
3
|
+
export type { ColumnMenuColumn, ColumnMenuLabels } from "./types";
|
|
4
|
+
export { DEFAULT_COLUMN_MENU_LABELS } from "./types";
|
|
5
|
+
export { useDndSensors } from "./use-dnd-sensors";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/column-menu/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ColumnMenuColumn } from "./types";
|
|
2
|
+
export interface SortableColumnRowProps<K extends string> {
|
|
3
|
+
column: ColumnMenuColumn<K>;
|
|
4
|
+
index: number;
|
|
5
|
+
total: number;
|
|
6
|
+
dragHandleLabel: string;
|
|
7
|
+
onToggle: (key: K) => void;
|
|
8
|
+
/** Carries `data-dragging` while the row is being dragged. */
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
/** One row of `ColumnMenu`: drag handle, visibility switch, position. */
|
|
12
|
+
export declare function SortableColumnRow<K extends string>({ column, index, total, dragHandleLabel, onToggle, className, }: SortableColumnRowProps<K>): import("react").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=sortable-column-row.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sortable-column-row.d.ts","sourceRoot":"","sources":["../../../src/components/column-menu/sortable-column-row.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,MAAM;IACtD,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IAC3B,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,EAClD,MAAM,EACN,KAAK,EACL,KAAK,EACL,eAAe,EACf,QAAQ,EACR,SAAS,GACV,EAAE,sBAAsB,CAAC,CAAC,CAAC,+BAuD3B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useSortable } from "@dnd-kit/sortable";
|
|
4
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
5
|
+
import { GripVertical } from "lucide-react";
|
|
6
|
+
import { cn } from "@forthtilliath/shadcn-ui/lib/utils";
|
|
7
|
+
import { ToggleSwitch } from "../toggle-switch";
|
|
8
|
+
/** One row of `ColumnMenu`: drag handle, visibility switch, position. */
|
|
9
|
+
export function SortableColumnRow({ column, index, total, dragHandleLabel, onToggle, className, }) {
|
|
10
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: column.key });
|
|
11
|
+
return (_jsxs("div", { ref: setNodeRef, "data-slot": "column-menu-row", "data-dragging": isDragging || undefined, style: {
|
|
12
|
+
transform: CSS.Transform.toString(transform),
|
|
13
|
+
transition,
|
|
14
|
+
opacity: isDragging ? 0.5 : 1,
|
|
15
|
+
zIndex: isDragging ? 10 : undefined,
|
|
16
|
+
}, className: cn("flex items-center gap-3 rounded-lg px-3 py-2 transition-colors", isDragging ? "bg-primary/5 border-primary/20 border" : "hover:bg-muted", className), children: [_jsx("button", { type: "button", ...attributes, ...listeners, "aria-label": dragHandleLabel, title: dragHandleLabel, className: "text-foreground/30 hover:text-foreground/60 shrink-0 cursor-grab touch-none transition-colors active:cursor-grabbing", children: _jsx(GripVertical, { className: "size-4", "aria-hidden": "true" }) }), _jsx(ToggleSwitch, { size: "sm", checked: column.visible, onCheckedChange: () => {
|
|
17
|
+
onToggle(column.key);
|
|
18
|
+
}, label: column.label, className: {
|
|
19
|
+
root: "flex-1 gap-3",
|
|
20
|
+
label: "text-foreground/40 group-data-[state=checked]/toggle-switch:text-foreground flex-1 text-left transition-colors",
|
|
21
|
+
} }), _jsxs("span", { className: "text-foreground/30 shrink-0 font-mono text-xs", children: [index + 1, "/", total] })] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** A table column the user can show/hide and reorder. */
|
|
2
|
+
export interface ColumnMenuColumn<K extends string = string> {
|
|
3
|
+
key: K;
|
|
4
|
+
label: string;
|
|
5
|
+
visible: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Texts displayed by `ColumnMenu` (English by default). */
|
|
8
|
+
export interface ColumnMenuLabels {
|
|
9
|
+
/** Trigger button text (hidden on small screens). */
|
|
10
|
+
trigger: string;
|
|
11
|
+
/** Menu title. */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Reset button text. */
|
|
14
|
+
reset: string;
|
|
15
|
+
/** Usage hint under the title. */
|
|
16
|
+
hint: string;
|
|
17
|
+
/** Tooltip / accessible name of a row's drag handle. */
|
|
18
|
+
dragHandle: (label: string) => string;
|
|
19
|
+
}
|
|
20
|
+
export declare const DEFAULT_COLUMN_MENU_LABELS: ColumnMenuLabels;
|
|
21
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/column-menu/types.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACzD,GAAG,EAAE,CAAC,CAAC;IACP,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC;AAED,eAAO,MAAM,0BAA0B,EAAE,gBAMxC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mouse (5px threshold, so clicks still work), touch (250ms long press, so
|
|
3
|
+
* the list still scrolls) and keyboard sensors for a sortable list.
|
|
4
|
+
*/
|
|
5
|
+
export declare function useDndSensors(): import("@dnd-kit/core").SensorDescriptor<import("@dnd-kit/core").SensorOptions>[];
|
|
6
|
+
//# sourceMappingURL=use-dnd-sensors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-dnd-sensors.d.ts","sourceRoot":"","sources":["../../../src/components/column-menu/use-dnd-sensors.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,wBAAgB,aAAa,sFAU5B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { KeyboardSensor, MouseSensor, TouchSensor, useSensor, useSensors, } from "@dnd-kit/core";
|
|
3
|
+
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
|
|
4
|
+
/**
|
|
5
|
+
* Mouse (5px threshold, so clicks still work), touch (250ms long press, so
|
|
6
|
+
* the list still scrolls) and keyboard sensors for a sortable list.
|
|
7
|
+
*/
|
|
8
|
+
export function useDndSensors() {
|
|
9
|
+
return useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 5 } }), useSensor(TouchSensor, {
|
|
10
|
+
activationConstraint: { delay: 250, tolerance: 5 },
|
|
11
|
+
}), useSensor(KeyboardSensor, {
|
|
12
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
@@ -35,7 +35,7 @@ export function CommandPalette({ groups, open, onOpenChange, placeholder = "Type
|
|
|
35
35
|
return () => {
|
|
36
36
|
document.removeEventListener("keydown", onKeyDown);
|
|
37
37
|
};
|
|
38
|
-
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
38
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps -- `setOpen` is a plain function redefined every render; including it would re-register this listener every render instead of only when `isOpen`/`shortcutKey` actually change, which is what its own re-closure over `isOpen` depends on
|
|
39
39
|
}, [isOpen, shortcutKey]);
|
|
40
40
|
return (_jsxs(CommandDialog, { open: isOpen, onOpenChange: setOpen, title: "Command Palette", description: placeholder, children: [_jsx(CommandInput, { placeholder: placeholder }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: emptyMessage }), groups.map((group, index) => (
|
|
41
41
|
// eslint-disable-next-line @eslint-react/no-array-index-key -- `heading` is optional and not guaranteed unique
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
export interface GradientFrameProps {
|
|
3
|
+
/** Side the gradient frame sticks out on (default: `"right"`). */
|
|
4
|
+
side?: "left" | "right";
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* Custom classes for each part of the component.
|
|
8
|
+
*
|
|
9
|
+
* - `root` wraps the frame and the content.
|
|
10
|
+
* - `frame` is the offset gradient panel — set its colors here with
|
|
11
|
+
* gradient stops (`from-* via-* to-*`), the direction (`bg-linear-135`)
|
|
12
|
+
* is already set.
|
|
13
|
+
* - `content` is the rounded, clipped container around `children`.
|
|
14
|
+
*/
|
|
15
|
+
className?: Partial<Record<"root" | "frame" | "content", string>>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A visual (image, video, card…) laid on a rounded frame offset diagonally
|
|
19
|
+
* behind it, filled with a gradient — a decorative "photo on a colored card"
|
|
20
|
+
* effect. The offset grows from `md` screens on.
|
|
21
|
+
*
|
|
22
|
+
* @version 0.1.0
|
|
23
|
+
* @author Forth
|
|
24
|
+
* @copyright 2026 Forth
|
|
25
|
+
*/
|
|
26
|
+
export declare function GradientFrame({ side, children, className, }: GradientFrameProps): React.JSX.Element;
|
|
27
|
+
//# sourceMappingURL=gradient-frame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradient-frame.d.ts","sourceRoot":"","sources":["../../../src/components/gradient-frame/gradient-frame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;CACnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAc,EACd,QAAQ,EACR,SAAS,GACV,EAAE,kBAAkB,qBAuBpB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "@forthtilliath/shadcn-ui/lib/utils";
|
|
3
|
+
/**
|
|
4
|
+
* A visual (image, video, card…) laid on a rounded frame offset diagonally
|
|
5
|
+
* behind it, filled with a gradient — a decorative "photo on a colored card"
|
|
6
|
+
* effect. The offset grows from `md` screens on.
|
|
7
|
+
*
|
|
8
|
+
* @version 0.1.0
|
|
9
|
+
* @author Forth
|
|
10
|
+
* @copyright 2026 Forth
|
|
11
|
+
*/
|
|
12
|
+
export function GradientFrame({ side = "right", children, className, }) {
|
|
13
|
+
return (_jsxs("div", { "data-slot": "gradient-frame", className: cn("relative", className?.root), children: [_jsx("div", { "aria-hidden": "true", className: cn("from-primary to-primary/40 absolute inset-0 rounded-3xl bg-linear-135", side === "right"
|
|
14
|
+
? "translate-x-3 translate-y-3 md:translate-x-5 md:translate-y-5"
|
|
15
|
+
: "-translate-x-3 translate-y-3 md:-translate-x-5 md:translate-y-5", className?.frame) }), _jsx("div", { className: cn("relative overflow-hidden rounded-3xl shadow-xl", className?.content), children: children })] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/gradient-frame/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { GradientFrame } from "./gradient-frame";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { VariantProps } from "class-variance-authority";
|
|
2
2
|
export declare const gridVariants: (props?: ({
|
|
3
|
-
cols?: 1 | 2 | 3 | 7 |
|
|
3
|
+
cols?: 1 | 2 | 3 | 7 | 10 | 5 | 12 | 4 | 6 | 8 | 9 | 11 | null | undefined;
|
|
4
4
|
spacing?: "sm" | "lg" | "none" | "xs" | "md" | "xl" | null | undefined;
|
|
5
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
6
|
export type GridVariants = VariantProps<typeof gridVariants>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { IconBubbleVariants } from "./variants";
|
|
3
|
+
export type IconBubbleProps = Omit<React.ComponentProps<"span">, "children"> & IconBubbleVariants & {
|
|
4
|
+
/** Icon component, e.g. a `lucide-react` icon. Decorative (`aria-hidden`). */
|
|
5
|
+
icon: React.ComponentType<{
|
|
6
|
+
size?: number | string;
|
|
7
|
+
"aria-hidden"?: boolean | "true" | "false";
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* An icon inside a round, tinted bubble — e.g. to lead an information line
|
|
12
|
+
* (date, place…). Tinted with `primary` by default; override both the
|
|
13
|
+
* background and the icon color through `className`
|
|
14
|
+
* (`bg-sky-500/15 text-sky-600`).
|
|
15
|
+
*
|
|
16
|
+
* @version 0.1.0
|
|
17
|
+
* @author Forth
|
|
18
|
+
* @copyright 2026 Forth
|
|
19
|
+
*/
|
|
20
|
+
export declare function IconBubble({ icon: Icon, size, className, ...props }: IconBubbleProps): React.JSX.Element;
|
|
21
|
+
//# sourceMappingURL=icon-bubble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon-bubble.d.ts","sourceRoot":"","sources":["../../../src/components/icon-bubble/icon-bubble.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAC1E,kBAAkB,GAAG;IACnB,8EAA8E;IAC9E,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC;QACxB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;KAC5C,CAAC,CAAC;CACJ,CAAC;AAEJ;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EAAE,IAAI,EACV,IAAI,EACJ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBAajB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "@forthtilliath/shadcn-ui/lib/utils";
|
|
3
|
+
import { ICON_BUBBLE_ICON_SIZES, iconBubbleVariants } from "./variants";
|
|
4
|
+
/**
|
|
5
|
+
* An icon inside a round, tinted bubble — e.g. to lead an information line
|
|
6
|
+
* (date, place…). Tinted with `primary` by default; override both the
|
|
7
|
+
* background and the icon color through `className`
|
|
8
|
+
* (`bg-sky-500/15 text-sky-600`).
|
|
9
|
+
*
|
|
10
|
+
* @version 0.1.0
|
|
11
|
+
* @author Forth
|
|
12
|
+
* @copyright 2026 Forth
|
|
13
|
+
*/
|
|
14
|
+
export function IconBubble({ icon: Icon, size, className, ...props }) {
|
|
15
|
+
return (_jsx("span", { "data-slot": "icon-bubble", className: cn(iconBubbleVariants({ size }), className), ...props, children: _jsx(Icon, { size: ICON_BUBBLE_ICON_SIZES[size ?? "default"], "aria-hidden": "true" }) }));
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/icon-bubble/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { IconBubble } from "./icon-bubble";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VariantProps } from "class-variance-authority";
|
|
2
|
+
export declare const iconBubbleVariants: (props?: ({
|
|
3
|
+
size?: "default" | "sm" | "lg" | null | undefined;
|
|
4
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
5
|
+
/** Icon size (px) matching each bubble size. */
|
|
6
|
+
export declare const ICON_BUBBLE_ICON_SIZES: {
|
|
7
|
+
readonly sm: 14;
|
|
8
|
+
readonly default: 17;
|
|
9
|
+
readonly lg: 20;
|
|
10
|
+
};
|
|
11
|
+
export type IconBubbleVariants = VariantProps<typeof iconBubbleVariants>;
|
|
12
|
+
//# sourceMappingURL=variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variants.d.ts","sourceRoot":"","sources":["../../../src/components/icon-bubble/variants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,eAAO,MAAM,kBAAkB;;8EAc9B,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,sBAAsB;;;;CAIzB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
2
|
+
export const iconBubbleVariants = cva("bg-primary/15 text-primary grid shrink-0 place-items-center rounded-full", {
|
|
3
|
+
variants: {
|
|
4
|
+
size: {
|
|
5
|
+
sm: "size-7",
|
|
6
|
+
default: "size-9",
|
|
7
|
+
lg: "size-11",
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
defaultVariants: {
|
|
11
|
+
size: "default",
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
/** Icon size (px) matching each bubble size. */
|
|
15
|
+
export const ICON_BUBBLE_ICON_SIZES = {
|
|
16
|
+
sm: 14,
|
|
17
|
+
default: 17,
|
|
18
|
+
lg: 20,
|
|
19
|
+
};
|
|
@@ -18,7 +18,7 @@ export function ImageInput({ value, onFileChange, disabled = false, className, a
|
|
|
18
18
|
const inputRef = React.useRef(null);
|
|
19
19
|
React.useEffect(() => {
|
|
20
20
|
if (file === null) {
|
|
21
|
-
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
21
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect -- syncs the object URL lifecycle (create/revoke) with the selected File, an external browser resource that can't be derived during render
|
|
22
22
|
setObjectUrl(null);
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
@@ -39,7 +39,7 @@ export function RelativeTime({ date, timeZone, label, formatOptions, className,
|
|
|
39
39
|
// ones — there's no way to read "now" during render without
|
|
40
40
|
// reintroducing the hydration mismatch this state is starting
|
|
41
41
|
// `undefined` to avoid.
|
|
42
|
-
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
42
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
43
43
|
setLiveNow(new Date());
|
|
44
44
|
const interval = setInterval(() => {
|
|
45
45
|
setLiveNow(new Date());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/toggle-switch/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ToggleSwitch } from "./toggle-switch";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { ToggleSwitchSizeVariants } from "./variants";
|
|
3
|
+
export type ToggleSwitchProps = Omit<React.ComponentProps<"button">, "children" | "className" | "onChange" | "onClick" | "role" | "type" | "value"> & ToggleSwitchSizeVariants & {
|
|
4
|
+
checked: boolean;
|
|
5
|
+
onCheckedChange: (checked: boolean) => void;
|
|
6
|
+
/** Visible text, also used as the accessible name. */
|
|
7
|
+
label?: React.ReactNode;
|
|
8
|
+
/** Secondary text under the label. */
|
|
9
|
+
description?: React.ReactNode;
|
|
10
|
+
/** Side of the text relative to the track (default: `"end"`). */
|
|
11
|
+
labelPosition?: "start" | "end";
|
|
12
|
+
/**
|
|
13
|
+
* Custom classes for each part of the component.
|
|
14
|
+
*
|
|
15
|
+
* - `root` is the `<button role="switch">` element — a `group/toggle-switch`
|
|
16
|
+
* carrying `data-state="checked" | "unchecked"`, so any part can be
|
|
17
|
+
* styled per state (`group-data-[state=checked]/toggle-switch:...`).
|
|
18
|
+
* - `track` / `thumb` are the switch itself.
|
|
19
|
+
* - `text` wraps the label and description (only when a description is set).
|
|
20
|
+
* - `label` / `description` are the text elements.
|
|
21
|
+
*/
|
|
22
|
+
className?: Partial<Record<"root" | "track" | "thumb" | "text" | "label" | "description", string>>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* An on/off switch (`role="switch"`) with an optional visible label and
|
|
26
|
+
* description — clicking the text toggles it too. Visually distinct from an
|
|
27
|
+
* action button.
|
|
28
|
+
*
|
|
29
|
+
* Unlike shadcn's `Switch`, it has no Radix dependency and bundles the label,
|
|
30
|
+
* so a whole settings row is a single, fully clickable element.
|
|
31
|
+
*
|
|
32
|
+
* @version 0.1.0
|
|
33
|
+
* @author Forth
|
|
34
|
+
* @copyright 2026 Forth
|
|
35
|
+
*/
|
|
36
|
+
export declare function ToggleSwitch({ checked, onCheckedChange, label, description, labelPosition, size, className, ...props }: ToggleSwitchProps): React.JSX.Element;
|
|
37
|
+
//# sourceMappingURL=toggle-switch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle-switch.d.ts","sourceRoot":"","sources":["../../../src/components/toggle-switch/toggle-switch.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAM3D,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAC9B,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAC9E,GACC,wBAAwB,GAAG;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,sDAAsD;IACtD,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,sCAAsC;IACtC,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,iEAAiE;IACjE,aAAa,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAChC;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,CACjB,MAAM,CACJ,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,EAC7D,MAAM,CACP,CACF,CAAC;CACH,CAAC;AAEJ;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,eAAe,EACf,KAAK,EACL,WAAW,EACX,aAAqB,EACrB,IAAI,EACJ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,iBAAiB,qBAmDnB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "@forthtilliath/shadcn-ui/lib/utils";
|
|
4
|
+
import { toggleSwitchThumbVariants, toggleSwitchTrackVariants, } from "./variants";
|
|
5
|
+
/**
|
|
6
|
+
* An on/off switch (`role="switch"`) with an optional visible label and
|
|
7
|
+
* description — clicking the text toggles it too. Visually distinct from an
|
|
8
|
+
* action button.
|
|
9
|
+
*
|
|
10
|
+
* Unlike shadcn's `Switch`, it has no Radix dependency and bundles the label,
|
|
11
|
+
* so a whole settings row is a single, fully clickable element.
|
|
12
|
+
*
|
|
13
|
+
* @version 0.1.0
|
|
14
|
+
* @author Forth
|
|
15
|
+
* @copyright 2026 Forth
|
|
16
|
+
*/
|
|
17
|
+
export function ToggleSwitch({ checked, onCheckedChange, label, description, labelPosition = "end", size, className, ...props }) {
|
|
18
|
+
const labelNode = label === undefined ? null : (_jsx("span", { className: cn("text-foreground text-sm", className?.label), children: label }));
|
|
19
|
+
// Without a description, the label is a direct flex child so that hiding it
|
|
20
|
+
// (e.g. `hidden sm:inline`) also removes the gap.
|
|
21
|
+
const text = description === undefined ? (labelNode) : (_jsxs("span", { className: cn("flex flex-col text-left", className?.text), children: [labelNode, _jsx("span", { className: cn("text-foreground/60 text-xs", className?.description), children: description })] }));
|
|
22
|
+
return (_jsxs("button", { type: "button", role: "switch", "aria-checked": checked, "data-slot": "toggle-switch", "data-state": checked ? "checked" : "unchecked", onClick: () => {
|
|
23
|
+
onCheckedChange(!checked);
|
|
24
|
+
}, className: cn("group/toggle-switch inline-flex cursor-pointer items-center gap-2 rounded-md focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50", className?.root), ...props, children: [labelPosition === "start" && text, _jsx("span", { "data-slot": "toggle-switch-track", className: cn(toggleSwitchTrackVariants({ size }), className?.track), children: _jsx("span", { className: cn(toggleSwitchThumbVariants({ size }), className?.thumb) }) }), labelPosition === "end" && text] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { VariantProps } from "class-variance-authority";
|
|
2
|
+
export declare const toggleSwitchTrackVariants: (props?: ({
|
|
3
|
+
size?: "default" | "sm" | "lg" | null | undefined;
|
|
4
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
5
|
+
export declare const toggleSwitchThumbVariants: (props?: ({
|
|
6
|
+
size?: "default" | "sm" | "lg" | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
export type ToggleSwitchSizeVariants = VariantProps<typeof toggleSwitchTrackVariants>;
|
|
9
|
+
//# sourceMappingURL=variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variants.d.ts","sourceRoot":"","sources":["../../../src/components/toggle-switch/variants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,eAAO,MAAM,yBAAyB;;8EAmBrC,CAAC;AAEF,eAAO,MAAM,yBAAyB;;8EAerC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,YAAY,CACjD,OAAO,yBAAyB,CACjC,CAAC"}
|