@forthtilliath/forth-ui 0.2.5 → 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.
Files changed (43) hide show
  1. package/README.md +16 -12
  2. package/dist/components/column-menu/column-menu.d.ts +39 -0
  3. package/dist/components/column-menu/column-menu.d.ts.map +1 -0
  4. package/dist/components/column-menu/column-menu.js +74 -0
  5. package/dist/components/column-menu/index.d.ts +6 -0
  6. package/dist/components/column-menu/index.d.ts.map +1 -0
  7. package/dist/components/column-menu/index.js +3 -0
  8. package/dist/components/column-menu/sortable-column-row.d.ts +13 -0
  9. package/dist/components/column-menu/sortable-column-row.d.ts.map +1 -0
  10. package/dist/components/column-menu/sortable-column-row.js +22 -0
  11. package/dist/components/column-menu/types.d.ts +21 -0
  12. package/dist/components/column-menu/types.d.ts.map +1 -0
  13. package/dist/components/column-menu/types.js +7 -0
  14. package/dist/components/column-menu/use-dnd-sensors.d.ts +6 -0
  15. package/dist/components/column-menu/use-dnd-sensors.d.ts.map +1 -0
  16. package/dist/components/column-menu/use-dnd-sensors.js +14 -0
  17. package/dist/components/gradient-frame/gradient-frame.d.ts +27 -0
  18. package/dist/components/gradient-frame/gradient-frame.d.ts.map +1 -0
  19. package/dist/components/gradient-frame/gradient-frame.js +16 -0
  20. package/dist/components/gradient-frame/index.d.ts +3 -0
  21. package/dist/components/gradient-frame/index.d.ts.map +1 -0
  22. package/dist/components/gradient-frame/index.js +1 -0
  23. package/dist/components/grid/variants.d.ts +1 -1
  24. package/dist/components/icon-bubble/icon-bubble.d.ts +21 -0
  25. package/dist/components/icon-bubble/icon-bubble.d.ts.map +1 -0
  26. package/dist/components/icon-bubble/icon-bubble.js +16 -0
  27. package/dist/components/icon-bubble/index.d.ts +4 -0
  28. package/dist/components/icon-bubble/index.d.ts.map +1 -0
  29. package/dist/components/icon-bubble/index.js +1 -0
  30. package/dist/components/icon-bubble/variants.d.ts +12 -0
  31. package/dist/components/icon-bubble/variants.d.ts.map +1 -0
  32. package/dist/components/icon-bubble/variants.js +19 -0
  33. package/dist/components/toggle-switch/index.d.ts +4 -0
  34. package/dist/components/toggle-switch/index.d.ts.map +1 -0
  35. package/dist/components/toggle-switch/index.js +1 -0
  36. package/dist/components/toggle-switch/toggle-switch.d.ts +37 -0
  37. package/dist/components/toggle-switch/toggle-switch.d.ts.map +1 -0
  38. package/dist/components/toggle-switch/toggle-switch.js +25 -0
  39. package/dist/components/toggle-switch/variants.d.ts +9 -0
  40. package/dist/components/toggle-switch/variants.d.ts.map +1 -0
  41. package/dist/components/toggle-switch/variants.js +30 -0
  42. package/dist/globals.css +290 -18
  43. package/package.json +6 -3
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
- 58 components across 8 categories, styled with Tailwind CSS and
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 | Import | Description |
215
- | ----------------------- | ----------------------------- | ------------------------------------------------------------------------ |
216
- | `Avatar`, `AvatarGroup` | `components/avatar` | An image with a fallback, status/badge indicators, and stacked grouping. |
217
- | `DescriptionList` | `components/description-list` | A description list, with terms and descriptions. |
218
- | `FileList` | `components/file-list` | Displays a list of files with name, size, and upload progress. |
219
- | `Kbd` | `components/kbd` | Displays which key or combination of keys performs a given action. |
220
- | `QrCode` | `components/qr-code` | Generates a QR code from a string, rendered as inline SVG. |
221
- | `RelativeTime` | `components/relative-time` | Displays a live-updating time, defaulting to the local timezone. |
222
- | `Sortable` | `components/sortable` | A drag-to-reorder list built on native HTML5 drag events. |
223
- | `Timeline` | `components/timeline` | A vertical sequence of dated events, connected by a line. |
224
- | `Tree` | `components/tree` | A hierarchical, expandable/collapsible list of nodes. |
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,3 @@
1
+ export { ColumnMenu } from "./column-menu";
2
+ export { DEFAULT_COLUMN_MENU_LABELS } from "./types";
3
+ export { useDndSensors } from "./use-dnd-sensors";
@@ -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,7 @@
1
+ export const DEFAULT_COLUMN_MENU_LABELS = {
2
+ trigger: "Columns",
3
+ title: "Visible columns",
4
+ reset: "Reset",
5
+ hint: "Drag to reorder · toggle to show/hide",
6
+ dragHandle: (label) => `Move column ${label}`,
7
+ };
@@ -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
+ }
@@ -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,3 @@
1
+ export type { GradientFrameProps } from "./gradient-frame";
2
+ export { GradientFrame } from "./gradient-frame";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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 | 12 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | null | undefined;
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,4 @@
1
+ export type { IconBubbleProps } from "./icon-bubble";
2
+ export { IconBubble } from "./icon-bubble";
3
+ export type { IconBubbleVariants } from "./variants";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ };
@@ -0,0 +1,4 @@
1
+ export type { ToggleSwitchProps } from "./toggle-switch";
2
+ export { ToggleSwitch } from "./toggle-switch";
3
+ export type { ToggleSwitchSizeVariants } from "./variants";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -0,0 +1,30 @@
1
+ import { cva } from "class-variance-authority";
2
+ export const toggleSwitchTrackVariants = cva([
3
+ "relative shrink-0 rounded-full bg-foreground/20 transition-colors",
4
+ "group-data-[state=checked]/toggle-switch:bg-primary",
5
+ "group-focus-visible/toggle-switch:ring-2 group-focus-visible/toggle-switch:ring-primary",
6
+ "group-focus-visible/toggle-switch:ring-offset-2 group-focus-visible/toggle-switch:ring-offset-background",
7
+ ], {
8
+ variants: {
9
+ size: {
10
+ sm: "h-5 w-8",
11
+ default: "h-5 w-9",
12
+ lg: "h-6 w-10",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ size: "default",
17
+ },
18
+ });
19
+ export const toggleSwitchThumbVariants = cva("absolute size-4 rounded-full bg-white shadow transition-transform", {
20
+ variants: {
21
+ size: {
22
+ sm: "top-0.5 left-0.5 group-data-[state=checked]/toggle-switch:translate-x-3",
23
+ default: "top-0.5 left-0.5 group-data-[state=checked]/toggle-switch:translate-x-4",
24
+ lg: "top-1 left-1 group-data-[state=checked]/toggle-switch:translate-x-4",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ size: "default",
29
+ },
30
+ });
package/dist/globals.css CHANGED
@@ -73,6 +73,7 @@
73
73
  --radius-md: calc(var(--radius) - 2px);
74
74
  --radius-lg: var(--radius);
75
75
  --radius-xl: calc(var(--radius) + 4px);
76
+ --radius-3xl: 1.5rem;
76
77
  --shadow-2xs: var(--shadow-2xs);
77
78
  --shadow-xs: var(--shadow-xs);
78
79
  --shadow-sm: var(--shadow-sm);
@@ -323,9 +324,15 @@
323
324
  .top-0 {
324
325
  top: 0px;
325
326
  }
327
+ .top-0\.5 {
328
+ top: calc(var(--spacing) * 0.5);
329
+ }
326
330
  .top-0\.25 {
327
331
  top: calc(var(--spacing) * 0.25);
328
332
  }
333
+ .top-1 {
334
+ top: var(--spacing);
335
+ }
329
336
  .top-2 {
330
337
  top: calc(var(--spacing) * 2);
331
338
  }
@@ -338,6 +345,9 @@
338
345
  .top-4 {
339
346
  top: calc(var(--spacing) * 4);
340
347
  }
348
+ .top-full {
349
+ top: 100%;
350
+ }
341
351
  .-right-1 {
342
352
  right: calc(var(--spacing) * -1);
343
353
  }
@@ -365,9 +375,21 @@
365
375
  .-left-1 {
366
376
  left: calc(var(--spacing) * -1);
367
377
  }
378
+ .left-0 {
379
+ left: 0px;
380
+ }
381
+ .left-0\.5 {
382
+ left: calc(var(--spacing) * 0.5);
383
+ }
368
384
  .left-0\.25 {
369
385
  left: calc(var(--spacing) * 0.25);
370
386
  }
387
+ .left-1 {
388
+ left: var(--spacing);
389
+ }
390
+ .z-20 {
391
+ z-index: 20;
392
+ }
371
393
  .z-50 {
372
394
  z-index: 50;
373
395
  }
@@ -557,6 +579,10 @@
557
579
  width: calc(var(--spacing) * 10);
558
580
  height: calc(var(--spacing) * 10);
559
581
  }
582
+ .size-11 {
583
+ width: calc(var(--spacing) * 11);
584
+ height: calc(var(--spacing) * 11);
585
+ }
560
586
  .size-14 {
561
587
  width: calc(var(--spacing) * 14);
562
588
  height: calc(var(--spacing) * 14);
@@ -636,12 +662,24 @@
636
662
  .w-1 {
637
663
  width: var(--spacing);
638
664
  }
665
+ .w-8 {
666
+ width: calc(var(--spacing) * 8);
667
+ }
668
+ .w-9 {
669
+ width: calc(var(--spacing) * 9);
670
+ }
671
+ .w-10 {
672
+ width: calc(var(--spacing) * 10);
673
+ }
639
674
  .w-16 {
640
675
  width: calc(var(--spacing) * 16);
641
676
  }
642
677
  .w-28 {
643
678
  width: calc(var(--spacing) * 28);
644
679
  }
680
+ .w-64 {
681
+ width: calc(var(--spacing) * 64);
682
+ }
645
683
  .w-80 {
646
684
  width: calc(var(--spacing) * 80);
647
685
  }
@@ -687,6 +725,18 @@
687
725
  .origin-top {
688
726
  transform-origin: top;
689
727
  }
728
+ .-translate-x-3 {
729
+ --tw-translate-x: calc(var(--spacing) * -3);
730
+ translate: var(--tw-translate-x) var(--tw-translate-y);
731
+ }
732
+ .translate-x-3 {
733
+ --tw-translate-x: calc(var(--spacing) * 3);
734
+ translate: var(--tw-translate-x) var(--tw-translate-y);
735
+ }
736
+ .translate-y-3 {
737
+ --tw-translate-y: calc(var(--spacing) * 3);
738
+ translate: var(--tw-translate-x) var(--tw-translate-y);
739
+ }
690
740
  .scale-y-0 {
691
741
  --tw-scale-y: 0%;
692
742
  scale: var(--tw-scale-x) var(--tw-scale-y);
@@ -695,6 +745,9 @@
695
745
  --tw-scale-y: 100%;
696
746
  scale: var(--tw-scale-x) var(--tw-scale-y);
697
747
  }
748
+ .rotate-180 {
749
+ rotate: 180deg;
750
+ }
698
751
  .transform {
699
752
  transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
700
753
  }
@@ -728,6 +781,9 @@
728
781
  .cursor-pointer {
729
782
  cursor: pointer;
730
783
  }
784
+ .touch-none {
785
+ touch-action: none;
786
+ }
731
787
  .\[appearance\:textfield\] {
732
788
  appearance: textfield;
733
789
  }
@@ -788,6 +844,9 @@
788
844
  .flex-wrap {
789
845
  flex-wrap: wrap;
790
846
  }
847
+ .place-items-center {
848
+ place-items: center;
849
+ }
791
850
  .items-center {
792
851
  align-items: center;
793
852
  }
@@ -895,6 +954,9 @@
895
954
  .rounded {
896
955
  border-radius: 0.25rem;
897
956
  }
957
+ .rounded-3xl {
958
+ border-radius: var(--radius-3xl);
959
+ }
898
960
  .rounded-full {
899
961
  border-radius: calc(infinity * 1px);
900
962
  }
@@ -1101,6 +1163,18 @@
1101
1163
  background-color: color-mix(in oklab, var(--color-emerald-500) 25%, transparent);
1102
1164
  }
1103
1165
  }
1166
+ .bg-foreground\/10 {
1167
+ background-color: var(--color-foreground);
1168
+ @supports (color: color-mix(in lab, red, red)) {
1169
+ background-color: color-mix(in oklab, var(--color-foreground) 10%, transparent);
1170
+ }
1171
+ }
1172
+ .bg-foreground\/20 {
1173
+ background-color: var(--color-foreground);
1174
+ @supports (color: color-mix(in lab, red, red)) {
1175
+ background-color: color-mix(in oklab, var(--color-foreground) 20%, transparent);
1176
+ }
1177
+ }
1104
1178
  .bg-fuchsia-500 {
1105
1179
  background-color: var(--color-fuchsia-500);
1106
1180
  }
@@ -1167,6 +1241,12 @@
1167
1241
  background-color: color-mix(in oklab, var(--color-muted) 40%, transparent);
1168
1242
  }
1169
1243
  }
1244
+ .bg-muted\/50 {
1245
+ background-color: var(--color-muted);
1246
+ @supports (color: color-mix(in lab, red, red)) {
1247
+ background-color: color-mix(in oklab, var(--color-muted) 50%, transparent);
1248
+ }
1249
+ }
1170
1250
  .bg-orange-500 {
1171
1251
  background-color: var(--color-orange-500);
1172
1252
  }
@@ -1188,12 +1268,24 @@
1188
1268
  .bg-primary {
1189
1269
  background-color: var(--color-primary);
1190
1270
  }
1271
+ .bg-primary\/5 {
1272
+ background-color: var(--color-primary);
1273
+ @supports (color: color-mix(in lab, red, red)) {
1274
+ background-color: color-mix(in oklab, var(--color-primary) 5%, transparent);
1275
+ }
1276
+ }
1191
1277
  .bg-primary\/10 {
1192
1278
  background-color: var(--color-primary);
1193
1279
  @supports (color: color-mix(in lab, red, red)) {
1194
1280
  background-color: color-mix(in oklab, var(--color-primary) 10%, transparent);
1195
1281
  }
1196
1282
  }
1283
+ .bg-primary\/15 {
1284
+ background-color: var(--color-primary);
1285
+ @supports (color: color-mix(in lab, red, red)) {
1286
+ background-color: color-mix(in oklab, var(--color-primary) 15%, transparent);
1287
+ }
1288
+ }
1197
1289
  .bg-primary\/20 {
1198
1290
  background-color: var(--color-primary);
1199
1291
  @supports (color: color-mix(in lab, red, red)) {
@@ -1242,6 +1334,12 @@
1242
1334
  .bg-sky-500 {
1243
1335
  background-color: var(--color-sky-500);
1244
1336
  }
1337
+ .bg-sky-500\/15 {
1338
+ background-color: color-mix(in srgb, oklch(68.5% 0.169 237.323) 15%, transparent);
1339
+ @supports (color: color-mix(in lab, red, red)) {
1340
+ background-color: color-mix(in oklab, var(--color-sky-500) 15%, transparent);
1341
+ }
1342
+ }
1245
1343
  .bg-sky-500\/25 {
1246
1344
  background-color: color-mix(in srgb, oklch(68.5% 0.169 237.323) 25%, transparent);
1247
1345
  @supports (color: color-mix(in lab, red, red)) {
@@ -1287,6 +1385,9 @@
1287
1385
  background-color: color-mix(in oklab, var(--color-violet-500) 25%, transparent);
1288
1386
  }
1289
1387
  }
1388
+ .bg-white {
1389
+ background-color: var(--color-white);
1390
+ }
1290
1391
  .bg-yellow-500 {
1291
1392
  background-color: var(--color-yellow-500);
1292
1393
  }
@@ -1296,6 +1397,24 @@
1296
1397
  background-color: color-mix(in oklab, var(--color-yellow-500) 25%, transparent);
1297
1398
  }
1298
1399
  }
1400
+ .bg-linear-135 {
1401
+ --tw-gradient-position: 135deg;
1402
+ @supports (background-image: linear-gradient(in lab, red, red)) {
1403
+ --tw-gradient-position: 135deg in oklab;
1404
+ }
1405
+ background-image: linear-gradient(var(--tw-gradient-stops));
1406
+ }
1407
+ .from-primary {
1408
+ --tw-gradient-from: var(--color-primary);
1409
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
1410
+ }
1411
+ .to-primary\/40 {
1412
+ --tw-gradient-to: var(--color-primary);
1413
+ @supports (color: color-mix(in lab, red, red)) {
1414
+ --tw-gradient-to: color-mix(in oklab, var(--color-primary) 40%, transparent);
1415
+ }
1416
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
1417
+ }
1299
1418
  .fill-amber-400 {
1300
1419
  fill: var(--color-amber-400);
1301
1420
  }
@@ -1311,6 +1430,9 @@
1311
1430
  .p-1 {
1312
1431
  padding: var(--spacing);
1313
1432
  }
1433
+ .p-2 {
1434
+ padding: calc(var(--spacing) * 2);
1435
+ }
1314
1436
  .p-3 {
1315
1437
  padding: calc(var(--spacing) * 3);
1316
1438
  }
@@ -1486,6 +1608,30 @@
1486
1608
  .text-foreground {
1487
1609
  color: var(--color-foreground);
1488
1610
  }
1611
+ .text-foreground\/30 {
1612
+ color: var(--color-foreground);
1613
+ @supports (color: color-mix(in lab, red, red)) {
1614
+ color: color-mix(in oklab, var(--color-foreground) 30%, transparent);
1615
+ }
1616
+ }
1617
+ .text-foreground\/40 {
1618
+ color: var(--color-foreground);
1619
+ @supports (color: color-mix(in lab, red, red)) {
1620
+ color: color-mix(in oklab, var(--color-foreground) 40%, transparent);
1621
+ }
1622
+ }
1623
+ .text-foreground\/50 {
1624
+ color: var(--color-foreground);
1625
+ @supports (color: color-mix(in lab, red, red)) {
1626
+ color: color-mix(in oklab, var(--color-foreground) 50%, transparent);
1627
+ }
1628
+ }
1629
+ .text-foreground\/60 {
1630
+ color: var(--color-foreground);
1631
+ @supports (color: color-mix(in lab, red, red)) {
1632
+ color: color-mix(in oklab, var(--color-foreground) 60%, transparent);
1633
+ }
1634
+ }
1489
1635
  .text-fuchsia-500 {
1490
1636
  color: var(--color-fuchsia-500);
1491
1637
  }
@@ -1543,6 +1689,9 @@
1543
1689
  .text-sky-500 {
1544
1690
  color: var(--color-sky-500);
1545
1691
  }
1692
+ .text-sky-600 {
1693
+ color: var(--color-sky-600);
1694
+ }
1546
1695
  .text-sky-700 {
1547
1696
  color: var(--color-sky-700);
1548
1697
  }
@@ -1586,6 +1735,9 @@
1586
1735
  .opacity-50 {
1587
1736
  opacity: 50%;
1588
1737
  }
1738
+ .opacity-60 {
1739
+ opacity: 60%;
1740
+ }
1589
1741
  .opacity-70 {
1590
1742
  opacity: 70%;
1591
1743
  }
@@ -1595,6 +1747,14 @@
1595
1747
  .opacity-100 {
1596
1748
  opacity: 100%;
1597
1749
  }
1750
+ .shadow {
1751
+ --tw-shadow: var(--shadow);
1752
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1753
+ }
1754
+ .shadow-xl {
1755
+ --tw-shadow: var(--shadow-xl);
1756
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1757
+ }
1598
1758
  .shadow-xs {
1599
1759
  --tw-shadow: var(--shadow-xs);
1600
1760
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -1700,6 +1860,34 @@
1700
1860
  animation-play-state: paused;
1701
1861
  }
1702
1862
  }
1863
+ .group-focus-visible\/toggle-switch\:ring-2:is(:where(.group\/toggle-switch):focus-visible *) {
1864
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1865
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1866
+ }
1867
+ .group-focus-visible\/toggle-switch\:ring-primary:is(:where(.group\/toggle-switch):focus-visible *) {
1868
+ --tw-ring-color: var(--color-primary);
1869
+ }
1870
+ .group-focus-visible\/toggle-switch\:ring-offset-2:is(:where(.group\/toggle-switch):focus-visible *) {
1871
+ --tw-ring-offset-width: 2px;
1872
+ --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1873
+ }
1874
+ .group-focus-visible\/toggle-switch\:ring-offset-background:is(:where(.group\/toggle-switch):focus-visible *) {
1875
+ --tw-ring-offset-color: var(--color-background);
1876
+ }
1877
+ .group-data-\[state\=checked\]\/toggle-switch\:translate-x-3:is(:where(.group\/toggle-switch)[data-state="checked"] *) {
1878
+ --tw-translate-x: calc(var(--spacing) * 3);
1879
+ translate: var(--tw-translate-x) var(--tw-translate-y);
1880
+ }
1881
+ .group-data-\[state\=checked\]\/toggle-switch\:translate-x-4:is(:where(.group\/toggle-switch)[data-state="checked"] *) {
1882
+ --tw-translate-x: calc(var(--spacing) * 4);
1883
+ translate: var(--tw-translate-x) var(--tw-translate-y);
1884
+ }
1885
+ .group-data-\[state\=checked\]\/toggle-switch\:bg-primary:is(:where(.group\/toggle-switch)[data-state="checked"] *) {
1886
+ background-color: var(--color-primary);
1887
+ }
1888
+ .group-data-\[state\=checked\]\/toggle-switch\:text-foreground:is(:where(.group\/toggle-switch)[data-state="checked"] *) {
1889
+ color: var(--color-foreground);
1890
+ }
1703
1891
  .placeholder\:text-muted-foreground::placeholder {
1704
1892
  color: var(--color-muted-foreground);
1705
1893
  }
@@ -1746,6 +1934,9 @@
1746
1934
  .hover\:z-10:hover {
1747
1935
  z-index: 10;
1748
1936
  }
1937
+ .hover\:border-primary:hover {
1938
+ border-color: var(--color-primary);
1939
+ }
1749
1940
  .hover\:bg-accent:hover {
1750
1941
  background-color: var(--color-accent);
1751
1942
  }
@@ -1760,6 +1951,9 @@
1760
1951
  .hover\:bg-background:hover {
1761
1952
  background-color: var(--color-background);
1762
1953
  }
1954
+ .hover\:bg-muted:hover {
1955
+ background-color: var(--color-muted);
1956
+ }
1763
1957
  .hover\:text-accent-foreground:hover {
1764
1958
  color: var(--color-accent-foreground);
1765
1959
  }
@@ -1774,6 +1968,14 @@
1774
1968
  .hover\:text-foreground:hover {
1775
1969
  color: var(--color-foreground);
1776
1970
  }
1971
+ .hover\:text-foreground\/60:hover {
1972
+ color: var(--color-foreground);
1973
+ }
1974
+ @supports (color: color-mix(in lab, red, red)) {
1975
+ .hover\:text-foreground\/60:hover {
1976
+ color: color-mix(in oklab, var(--color-foreground) 60%, transparent);
1977
+ }
1978
+ }
1777
1979
  .hover\:text-primary:hover {
1778
1980
  color: var(--color-primary);
1779
1981
  }
@@ -1812,6 +2014,10 @@
1812
2014
  --tw-ring-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
1813
2015
  }
1814
2016
  }
2017
+ .focus-visible\:outline-none:focus-visible {
2018
+ --tw-outline-style: none;
2019
+ outline-style: none;
2020
+ }
1815
2021
  .active\:cursor-grabbing:active {
1816
2022
  cursor: grabbing;
1817
2023
  }
@@ -1939,6 +2145,9 @@
1939
2145
  .sm\:hidden {
1940
2146
  display: none;
1941
2147
  }
2148
+ .sm\:inline {
2149
+ display: inline;
2150
+ }
1942
2151
  .sm\:w-1\/3 {
1943
2152
  width: calc(1 / 3 * 100%);
1944
2153
  }
@@ -1962,6 +2171,18 @@
1962
2171
  .md\:hidden {
1963
2172
  display: none;
1964
2173
  }
2174
+ .md\:-translate-x-5 {
2175
+ --tw-translate-x: calc(var(--spacing) * -5);
2176
+ translate: var(--tw-translate-x) var(--tw-translate-y);
2177
+ }
2178
+ .md\:translate-x-5 {
2179
+ --tw-translate-x: calc(var(--spacing) * 5);
2180
+ translate: var(--tw-translate-x) var(--tw-translate-y);
2181
+ }
2182
+ .md\:translate-y-5 {
2183
+ --tw-translate-y: calc(var(--spacing) * 5);
2184
+ translate: var(--tw-translate-x) var(--tw-translate-y);
2185
+ }
1965
2186
  }
1966
2187
  @media (width >= 64rem) {
1967
2188
  .lg\:hidden {
@@ -2642,6 +2863,21 @@
2642
2863
  transform: translateY(-100%);
2643
2864
  }
2644
2865
  }
2866
+ @property --tw-translate-x {
2867
+ syntax: "*";
2868
+ inherits: false;
2869
+ initial-value: 0;
2870
+ }
2871
+ @property --tw-translate-y {
2872
+ syntax: "*";
2873
+ inherits: false;
2874
+ initial-value: 0;
2875
+ }
2876
+ @property --tw-translate-z {
2877
+ syntax: "*";
2878
+ inherits: false;
2879
+ initial-value: 0;
2880
+ }
2645
2881
  @property --tw-scale-x {
2646
2882
  syntax: "*";
2647
2883
  inherits: false;
@@ -2697,6 +2933,48 @@
2697
2933
  inherits: false;
2698
2934
  initial-value: solid;
2699
2935
  }
2936
+ @property --tw-gradient-position {
2937
+ syntax: "*";
2938
+ inherits: false;
2939
+ }
2940
+ @property --tw-gradient-from {
2941
+ syntax: "<color>";
2942
+ inherits: false;
2943
+ initial-value: #0000;
2944
+ }
2945
+ @property --tw-gradient-via {
2946
+ syntax: "<color>";
2947
+ inherits: false;
2948
+ initial-value: #0000;
2949
+ }
2950
+ @property --tw-gradient-to {
2951
+ syntax: "<color>";
2952
+ inherits: false;
2953
+ initial-value: #0000;
2954
+ }
2955
+ @property --tw-gradient-stops {
2956
+ syntax: "*";
2957
+ inherits: false;
2958
+ }
2959
+ @property --tw-gradient-via-stops {
2960
+ syntax: "*";
2961
+ inherits: false;
2962
+ }
2963
+ @property --tw-gradient-from-position {
2964
+ syntax: "<length-percentage>";
2965
+ inherits: false;
2966
+ initial-value: 0%;
2967
+ }
2968
+ @property --tw-gradient-via-position {
2969
+ syntax: "<length-percentage>";
2970
+ inherits: false;
2971
+ initial-value: 50%;
2972
+ }
2973
+ @property --tw-gradient-to-position {
2974
+ syntax: "<length-percentage>";
2975
+ inherits: false;
2976
+ initial-value: 100%;
2977
+ }
2700
2978
  @property --tw-leading {
2701
2979
  syntax: "*";
2702
2980
  inherits: false;
@@ -2844,21 +3122,6 @@
2844
3122
  initial-value: "";
2845
3123
  inherits: false;
2846
3124
  }
2847
- @property --tw-translate-x {
2848
- syntax: "*";
2849
- inherits: false;
2850
- initial-value: 0;
2851
- }
2852
- @property --tw-translate-y {
2853
- syntax: "*";
2854
- inherits: false;
2855
- initial-value: 0;
2856
- }
2857
- @property --tw-translate-z {
2858
- syntax: "*";
2859
- inherits: false;
2860
- initial-value: 0;
2861
- }
2862
3125
  @keyframes spin {
2863
3126
  to {
2864
3127
  transform: rotate(360deg);
@@ -2930,6 +3193,9 @@
2930
3193
  @layer properties {
2931
3194
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
2932
3195
  *, ::before, ::after, ::backdrop {
3196
+ --tw-translate-x: 0;
3197
+ --tw-translate-y: 0;
3198
+ --tw-translate-z: 0;
2933
3199
  --tw-scale-x: 1;
2934
3200
  --tw-scale-y: 1;
2935
3201
  --tw-scale-z: 1;
@@ -2942,6 +3208,15 @@
2942
3208
  --tw-space-x-reverse: 0;
2943
3209
  --tw-divide-y-reverse: 0;
2944
3210
  --tw-border-style: solid;
3211
+ --tw-gradient-position: initial;
3212
+ --tw-gradient-from: #0000;
3213
+ --tw-gradient-via: #0000;
3214
+ --tw-gradient-to: #0000;
3215
+ --tw-gradient-stops: initial;
3216
+ --tw-gradient-via-stops: initial;
3217
+ --tw-gradient-from-position: 0%;
3218
+ --tw-gradient-via-position: 50%;
3219
+ --tw-gradient-to-position: 100%;
2945
3220
  --tw-leading: initial;
2946
3221
  --tw-font-weight: initial;
2947
3222
  --tw-ordinal: initial;
@@ -2976,9 +3251,6 @@
2976
3251
  --tw-duration: initial;
2977
3252
  --tw-ease: initial;
2978
3253
  --tw-content: "";
2979
- --tw-translate-x: 0;
2980
- --tw-translate-y: 0;
2981
- --tw-translate-z: 0;
2982
3254
  --tw-animation-delay: 0s;
2983
3255
  --tw-animation-direction: normal;
2984
3256
  --tw-animation-duration: initial;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forthtilliath/forth-ui",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -14,6 +14,9 @@
14
14
  "homepage": "https://github.com/Forthtilliath/forthtilliath-packages/tree/main/packages/react/forth-ui#readme",
15
15
  "bugs": "https://github.com/Forthtilliath/forthtilliath-packages/issues",
16
16
  "dependencies": {
17
+ "@dnd-kit/core": "^6.3.1",
18
+ "@dnd-kit/sortable": "^10.0.0",
19
+ "@dnd-kit/utilities": "^3.2.2",
17
20
  "@radix-ui/react-accordion": "^1.2.20",
18
21
  "@radix-ui/react-radio-group": "^1.4.7",
19
22
  "@radix-ui/react-slot": "^1.3.3",
@@ -40,9 +43,9 @@
40
43
  "size-limit": "^11.2.0",
41
44
  "tailwindcss": "4.1.12",
42
45
  "typescript": "^5.9.3",
43
- "@forthtilliath/eslint-config": "0.7.0",
44
46
  "@forthtilliath/ts-types": "0.2.0",
45
- "@forthtilliath/typescript-config": "0.1.1"
47
+ "@forthtilliath/typescript-config": "0.1.1",
48
+ "@forthtilliath/eslint-config": "0.7.0"
46
49
  },
47
50
  "files": [
48
51
  "dist"