@wheelhouse/ui 0.2.0 → 0.2.2
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/dist/components/accordion/accordion.js +2 -2
- package/dist/components/alert/alert.stories.js +4 -4
- package/dist/components/alert-dialog/alert-dialog.stories.js +2 -2
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/breadcrumb/breadcrumb.js +3 -4
- package/dist/components/calendar/calendar.d.ts +34 -0
- package/dist/components/calendar/calendar.d.ts.map +1 -0
- package/dist/components/calendar/calendar.js +82 -0
- package/dist/components/calendar/calendar.stories.d.ts +16 -0
- package/dist/components/calendar/calendar.stories.d.ts.map +1 -0
- package/dist/components/calendar/calendar.stories.js +33 -0
- package/dist/components/calendar/index.d.ts +3 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/index.js +1 -0
- package/dist/components/checkbox/checkbox.js +2 -2
- package/dist/components/collapsible/collapsible.stories.js +4 -4
- package/dist/components/combobox/combobox.d.ts.map +1 -1
- package/dist/components/combobox/combobox.js +5 -7
- package/dist/components/command/command.d.ts.map +1 -1
- package/dist/components/command/command.js +3 -4
- package/dist/components/context-menu/context-menu.d.ts.map +1 -1
- package/dist/components/context-menu/context-menu.js +4 -5
- package/dist/components/date-selector/date-selector-context.d.ts +6 -0
- package/dist/components/date-selector/date-selector-context.d.ts.map +1 -0
- package/dist/components/date-selector/date-selector-context.js +11 -0
- package/dist/components/date-selector/date-selector-parts.d.ts +68 -0
- package/dist/components/date-selector/date-selector-parts.d.ts.map +1 -0
- package/dist/components/date-selector/date-selector-parts.js +131 -0
- package/dist/components/date-selector/date-selector-types.d.ts +118 -0
- package/dist/components/date-selector/date-selector-types.d.ts.map +1 -0
- package/dist/components/date-selector/date-selector-types.js +32 -0
- package/dist/components/date-selector/date-selector-value.d.ts +47 -0
- package/dist/components/date-selector/date-selector-value.d.ts.map +1 -0
- package/dist/components/date-selector/date-selector-value.js +183 -0
- package/dist/components/date-selector/date-selector.d.ts +6 -0
- package/dist/components/date-selector/date-selector.d.ts.map +1 -0
- package/dist/components/date-selector/date-selector.js +144 -0
- package/dist/components/date-selector/date-selector.stories.d.ts +135 -0
- package/dist/components/date-selector/date-selector.stories.d.ts.map +1 -0
- package/dist/components/date-selector/date-selector.stories.js +144 -0
- package/dist/components/date-selector/index.d.ts +7 -0
- package/dist/components/date-selector/index.d.ts.map +1 -0
- package/dist/components/date-selector/index.js +5 -0
- package/dist/components/date-selector/use-date-selector.d.ts +50 -0
- package/dist/components/date-selector/use-date-selector.d.ts.map +1 -0
- package/dist/components/date-selector/use-date-selector.js +305 -0
- package/dist/components/dialog/dialog.js +2 -2
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.js +5 -6
- package/dist/components/empty/empty.stories.js +2 -2
- package/dist/components/filters/filters.js +4 -4
- package/dist/components/filters/filters.stories.js +3 -3
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -0
- package/dist/components/input-group/input-group.stories.js +3 -3
- package/dist/components/item/item.stories.d.ts.map +1 -1
- package/dist/components/item/item.stories.js +5 -6
- package/dist/components/kbd/kbd.stories.js +2 -2
- package/dist/components/menubar/menubar.js +3 -3
- package/dist/components/native-select/native-select.js +2 -2
- package/dist/components/navigation-menu/navigation-menu.js +2 -2
- package/dist/components/navigation-pattern-1/index.d.ts +3 -0
- package/dist/components/navigation-pattern-1/index.d.ts.map +1 -0
- package/dist/components/navigation-pattern-1/index.js +1 -0
- package/dist/components/navigation-pattern-1/pattern-1.config.d.ts +47 -0
- package/dist/components/navigation-pattern-1/pattern-1.config.d.ts.map +1 -0
- package/dist/components/navigation-pattern-1/pattern-1.config.js +55 -0
- package/dist/components/navigation-pattern-1/pattern-1.d.ts +7 -0
- package/dist/components/navigation-pattern-1/pattern-1.d.ts.map +1 -0
- package/dist/components/navigation-pattern-1/pattern-1.js +83 -0
- package/dist/components/navigation-pattern-1/pattern-1.stories.d.ts +16 -0
- package/dist/components/navigation-pattern-1/pattern-1.stories.d.ts.map +1 -0
- package/dist/components/navigation-pattern-1/pattern-1.stories.js +20 -0
- package/dist/components/pagination/pagination.js +4 -4
- package/dist/components/select/select.js +5 -5
- package/dist/components/sheet/sheet.js +2 -2
- package/dist/components/sonner/sonner.d.ts +1 -1
- package/dist/components/sonner/sonner.js +7 -7
- package/dist/components/sortable/index.d.ts +3 -0
- package/dist/components/sortable/index.d.ts.map +1 -0
- package/dist/components/sortable/index.js +1 -0
- package/dist/components/sortable/sortable.d.ts +94 -0
- package/dist/components/sortable/sortable.d.ts.map +1 -0
- package/dist/components/sortable/sortable.js +210 -0
- package/dist/components/sortable/sortable.stories.d.ts +14 -0
- package/dist/components/sortable/sortable.stories.d.ts.map +1 -0
- package/dist/components/sortable/sortable.stories.js +38 -0
- package/dist/components/spinner/spinner.d.ts +3 -3
- package/dist/components/spinner/spinner.d.ts.map +1 -1
- package/dist/components/spinner/spinner.js +2 -2
- package/dist/components/status-indicator/index.d.ts +3 -0
- package/dist/components/status-indicator/index.d.ts.map +1 -0
- package/dist/components/status-indicator/index.js +1 -0
- package/dist/components/status-indicator/status-indicator.d.ts +51 -0
- package/dist/components/status-indicator/status-indicator.d.ts.map +1 -0
- package/dist/components/status-indicator/status-indicator.js +48 -0
- package/dist/components/status-indicator/status-indicator.stories.d.ts +20 -0
- package/dist/components/status-indicator/status-indicator.stories.d.ts.map +1 -0
- package/dist/components/status-indicator/status-indicator.stories.js +97 -0
- package/dist/components/text/text.js +1 -1
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +15 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/llms.txt +4 -4
- package/package.json +7 -4
- package/src/styles/globals.css +4 -12
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drag-and-drop sortable lists and grids built on [dnd-kit](https://dndkit.com/).
|
|
3
|
+
*
|
|
4
|
+
* Compose **`Sortable`** (root) with **`SortableItem`** rows and an optional **`SortableItemHandle`**
|
|
5
|
+
* for the drag affordance. The root renders a **`DragOverlay`** portal while dragging; use
|
|
6
|
+
* **`SortableOverlay`** only when you need a fully custom overlay.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import * as React from 'react';
|
|
11
|
+
import { type ReactNode } from 'react';
|
|
12
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
13
|
+
import { type DragEndEvent, DragOverlay, type DragStartEvent, type Modifiers, type UniqueIdentifier } from '@dnd-kit/core';
|
|
14
|
+
/**
|
|
15
|
+
* Props for {@link Sortable}. `T` is your row model; stable string ids come from {@link SortableRootProps.getItemValue}.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - Item type in the controlled `value` array.
|
|
18
|
+
*/
|
|
19
|
+
export interface SortableRootProps<T> extends Omit<useRender.ComponentProps<'div'>, 'onDragStart' | 'onDragEnd' | 'children'> {
|
|
20
|
+
/** Controlled list; order updates when items are reordered (unless {@link SortableRootProps.onMove} handles persistence). */
|
|
21
|
+
value: T[];
|
|
22
|
+
/** Called with the next array when the user drops an item in a new position. */
|
|
23
|
+
onValueChange: (value: T[]) => void;
|
|
24
|
+
/** Stable string id for each item; must match the corresponding {@link SortableItemProps.value}. */
|
|
25
|
+
getItemValue: (item: T) => string;
|
|
26
|
+
/** Typically a list of `SortableItem` elements (one per row). */
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* Optional full control on drop: receive indices and apply `value` yourself (e.g. server mutation).
|
|
30
|
+
* When omitted, the root calls `onValueChange` with an array moved via dnd-kit `arrayMove`.
|
|
31
|
+
*/
|
|
32
|
+
onMove?: (event: {
|
|
33
|
+
event: DragEndEvent;
|
|
34
|
+
activeIndex: number;
|
|
35
|
+
overIndex: number;
|
|
36
|
+
}) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Layout strategy for `@dnd-kit/sortable`: vertical list, horizontal/flow, or grid (both use rect sorting).
|
|
39
|
+
* @defaultValue vertical
|
|
40
|
+
*/
|
|
41
|
+
strategy?: 'horizontal' | 'vertical' | 'grid';
|
|
42
|
+
/** Passed through to `DndContext`; invoked when a drag starts. */
|
|
43
|
+
onDragStart?: (event: DragStartEvent) => void;
|
|
44
|
+
/** Passed through to `DndContext`; invoked when a drag ends (after reorder logic). */
|
|
45
|
+
onDragEnd?: (event: DragEndEvent) => void;
|
|
46
|
+
/** Optional dnd-kit modifiers (e.g. `restrictToParentElement`) for both context and overlay. */
|
|
47
|
+
modifiers?: Modifiers;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Root for a sortable region. Wires `DndContext`, `SortableContext`, sensors, and a `DragOverlay` portal on the client.
|
|
51
|
+
*
|
|
52
|
+
* Renders the default element as a `div` with `data-slot="sortable"` (see Base UI `useRender` / `render` prop).
|
|
53
|
+
*/
|
|
54
|
+
declare function Sortable<T>({ value, onValueChange, getItemValue, className, render, onMove, strategy, onDragStart, onDragEnd, modifiers, children, ...props }: SortableRootProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
55
|
+
/**
|
|
56
|
+
* Props for {@link SortableItem}. `value` is the sortable id (string) registered with dnd-kit.
|
|
57
|
+
*/
|
|
58
|
+
export interface SortableItemProps extends useRender.ComponentProps<'div'> {
|
|
59
|
+
/** Must match `getItemValue(item)` for that row and be unique among siblings. */
|
|
60
|
+
value: string;
|
|
61
|
+
/** When true, the item does not participate in dragging. */
|
|
62
|
+
disabled?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One row in a sortable list. Provides `data-slot="sortable-item"` and sortable transforms unless rendered inside a drag overlay clone.
|
|
66
|
+
*/
|
|
67
|
+
declare function SortableItem({ value, className, render, disabled, ...props }: SortableItemProps): import("react/jsx-runtime").JSX.Element;
|
|
68
|
+
/**
|
|
69
|
+
* Props for {@link SortableItemHandle}. Spread `listeners` from context onto this node (handled internally).
|
|
70
|
+
*/
|
|
71
|
+
export interface SortableItemHandleProps extends useRender.ComponentProps<'div'> {
|
|
72
|
+
/** When true (default), applies grab/grabbing cursor classes while dragging. */
|
|
73
|
+
cursor?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Drag handle for a `SortableItem`. Must be nested under `SortableItem` so listeners and drag state apply.
|
|
77
|
+
*/
|
|
78
|
+
declare function SortableItemHandle({ className, render, cursor, ...props }: SortableItemHandleProps): React.ReactElement<unknown, string | React.JSXElementConstructor<any>>;
|
|
79
|
+
/**
|
|
80
|
+
* Props for {@link SortableOverlay}. Extends dnd-kit `DragOverlay` except `children`, which may be a render function receiving the active id.
|
|
81
|
+
*/
|
|
82
|
+
export interface SortableOverlayProps extends Omit<React.ComponentProps<typeof DragOverlay>, 'children'> {
|
|
83
|
+
/** Static node or function `({ value }) => …` for custom overlay content tied to the active item id. */
|
|
84
|
+
children?: ReactNode | ((params: {
|
|
85
|
+
value: UniqueIdentifier;
|
|
86
|
+
}) => ReactNode);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Alternate overlay entry: renders `DragOverlay` in a portal with shared modifiers/animation from `Sortable` context.
|
|
90
|
+
* Prefer the built-in overlay on `Sortable` unless you need this API.
|
|
91
|
+
*/
|
|
92
|
+
declare function SortableOverlay({ children, className, ...props }: SortableOverlayProps): React.ReactPortal | null;
|
|
93
|
+
export { Sortable, SortableItem, SortableItemHandle, SortableOverlay };
|
|
94
|
+
//# sourceMappingURL=sortable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sortable.d.ts","sourceRoot":"","sources":["../../../src/components/sortable/sortable.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Drag-and-drop sortable lists and grids built on [dnd-kit](https://dndkit.com/).
|
|
5
|
+
*
|
|
6
|
+
* Compose **`Sortable`** (root) with **`SortableItem`** rows and an optional **`SortableItemHandle`**
|
|
7
|
+
* for the drag affordance. The root renders a **`DragOverlay`** portal while dragging; use
|
|
8
|
+
* **`SortableOverlay`** only when you need a fully custom overlay.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useMemo, useState, useSyncExternalStore, } from 'react';
|
|
14
|
+
import { mergeProps } from '@base-ui/react/merge-props';
|
|
15
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
16
|
+
import { defaultDropAnimationSideEffects, DndContext, DragOverlay, KeyboardSensor, MeasuringStrategy, MouseSensor, TouchSensor, useSensor, useSensors, } from '@dnd-kit/core';
|
|
17
|
+
import { arrayMove, defaultAnimateLayoutChanges, rectSortingStrategy, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable';
|
|
18
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
19
|
+
import { createPortal } from 'react-dom';
|
|
20
|
+
import { cn } from '../../lib/utils';
|
|
21
|
+
// Sortable Item Context
|
|
22
|
+
const SortableItemContext = createContext({
|
|
23
|
+
listeners: undefined,
|
|
24
|
+
isDragging: false,
|
|
25
|
+
disabled: false,
|
|
26
|
+
});
|
|
27
|
+
const IsOverlayContext = createContext(false);
|
|
28
|
+
const SortableInternalContext = createContext({
|
|
29
|
+
activeId: null,
|
|
30
|
+
modifiers: undefined,
|
|
31
|
+
});
|
|
32
|
+
const animateLayoutChanges = (args) => defaultAnimateLayoutChanges({ ...args, wasDragging: true });
|
|
33
|
+
const dropAnimationConfig = {
|
|
34
|
+
sideEffects: defaultDropAnimationSideEffects({
|
|
35
|
+
styles: {
|
|
36
|
+
active: {
|
|
37
|
+
opacity: '0.4',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
const emptySubscribe = () => () => { };
|
|
43
|
+
function useIsClient() {
|
|
44
|
+
return useSyncExternalStore(emptySubscribe, () => true, () => false);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Root for a sortable region. Wires `DndContext`, `SortableContext`, sensors, and a `DragOverlay` portal on the client.
|
|
48
|
+
*
|
|
49
|
+
* Renders the default element as a `div` with `data-slot="sortable"` (see Base UI `useRender` / `render` prop).
|
|
50
|
+
*/
|
|
51
|
+
function Sortable({ value, onValueChange, getItemValue, className, render, onMove, strategy = 'vertical', onDragStart, onDragEnd, modifiers, children, ...props }) {
|
|
52
|
+
const [activeId, setActiveId] = useState(null);
|
|
53
|
+
const isClient = useIsClient();
|
|
54
|
+
const sensors = useSensors(useSensor(MouseSensor, {
|
|
55
|
+
activationConstraint: {
|
|
56
|
+
distance: 10,
|
|
57
|
+
},
|
|
58
|
+
}), useSensor(TouchSensor, {
|
|
59
|
+
activationConstraint: {
|
|
60
|
+
delay: 250,
|
|
61
|
+
tolerance: 5,
|
|
62
|
+
},
|
|
63
|
+
}), useSensor(KeyboardSensor, {
|
|
64
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
65
|
+
}));
|
|
66
|
+
const handleDragStart = useCallback((event) => {
|
|
67
|
+
setActiveId(event.active.id);
|
|
68
|
+
onDragStart?.(event);
|
|
69
|
+
}, [onDragStart]);
|
|
70
|
+
const handleDragEnd = useCallback((event) => {
|
|
71
|
+
const { active, over } = event;
|
|
72
|
+
setActiveId(null);
|
|
73
|
+
onDragEnd?.(event);
|
|
74
|
+
if (!over)
|
|
75
|
+
return;
|
|
76
|
+
// Handle item reordering
|
|
77
|
+
const activeIndex = value.findIndex((item) => getItemValue(item) === active.id);
|
|
78
|
+
const overIndex = value.findIndex((item) => getItemValue(item) === over.id);
|
|
79
|
+
if (activeIndex !== overIndex) {
|
|
80
|
+
if (onMove) {
|
|
81
|
+
onMove({ event, activeIndex, overIndex });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const newValue = arrayMove(value, activeIndex, overIndex);
|
|
85
|
+
onValueChange(newValue);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, [value, getItemValue, onValueChange, onMove, onDragEnd]);
|
|
89
|
+
const handleDragCancel = useCallback(() => {
|
|
90
|
+
setActiveId(null);
|
|
91
|
+
}, []);
|
|
92
|
+
const getStrategy = () => {
|
|
93
|
+
switch (strategy) {
|
|
94
|
+
case 'horizontal':
|
|
95
|
+
return rectSortingStrategy;
|
|
96
|
+
case 'grid':
|
|
97
|
+
return rectSortingStrategy;
|
|
98
|
+
case 'vertical':
|
|
99
|
+
default:
|
|
100
|
+
return verticalListSortingStrategy;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const itemIds = useMemo(() => value.map(getItemValue), [value, getItemValue]);
|
|
104
|
+
const contextValue = useMemo(() => ({ activeId, modifiers }), [activeId, modifiers]);
|
|
105
|
+
const defaultProps = {
|
|
106
|
+
'data-slot': 'sortable',
|
|
107
|
+
'data-dragging': activeId !== null,
|
|
108
|
+
className: cn(activeId !== null && 'cursor-grabbing!', className),
|
|
109
|
+
children,
|
|
110
|
+
};
|
|
111
|
+
// Find the active child for the overlay
|
|
112
|
+
const overlayContent = useMemo(() => {
|
|
113
|
+
if (!activeId)
|
|
114
|
+
return null;
|
|
115
|
+
let result = null;
|
|
116
|
+
Children.forEach(children, (child) => {
|
|
117
|
+
if (!isValidElement(child))
|
|
118
|
+
return;
|
|
119
|
+
if (child.props.value !== activeId)
|
|
120
|
+
return;
|
|
121
|
+
result = cloneElement(child, {
|
|
122
|
+
...child.props,
|
|
123
|
+
className: cn(child.props.className, 'z-50'),
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
return result;
|
|
127
|
+
}, [activeId, children]);
|
|
128
|
+
return (_jsx(SortableInternalContext.Provider, { value: contextValue, children: _jsxs(DndContext, { sensors: sensors, modifiers: modifiers, measuring: {
|
|
129
|
+
droppable: {
|
|
130
|
+
strategy: MeasuringStrategy.Always,
|
|
131
|
+
},
|
|
132
|
+
}, onDragStart: handleDragStart, onDragEnd: handleDragEnd, onDragCancel: handleDragCancel, children: [_jsx(SortableContext, { items: itemIds, strategy: getStrategy(), children: useRender({
|
|
133
|
+
defaultTagName: 'div',
|
|
134
|
+
render,
|
|
135
|
+
props: mergeProps(defaultProps, props),
|
|
136
|
+
}) }), isClient &&
|
|
137
|
+
createPortal(_jsx(DragOverlay, { dropAnimation: dropAnimationConfig, modifiers: modifiers, className: cn('z-50', activeId && 'cursor-grabbing'), children: _jsx(IsOverlayContext.Provider, { value: true, children: overlayContent }) }), document.body)] }) }));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* One row in a sortable list. Provides `data-slot="sortable-item"` and sortable transforms unless rendered inside a drag overlay clone.
|
|
141
|
+
*/
|
|
142
|
+
function SortableItem({ value, className, render, disabled, ...props }) {
|
|
143
|
+
const isOverlay = useContext(IsOverlayContext);
|
|
144
|
+
const { setNodeRef, transform, transition, attributes, listeners, isDragging: isSortableDragging, } = useSortable({
|
|
145
|
+
id: value,
|
|
146
|
+
disabled: disabled || isOverlay,
|
|
147
|
+
animateLayoutChanges,
|
|
148
|
+
});
|
|
149
|
+
const defaultProps = isOverlay
|
|
150
|
+
? {
|
|
151
|
+
'data-slot': 'sortable-item',
|
|
152
|
+
'data-value': value,
|
|
153
|
+
'data-dragging': true,
|
|
154
|
+
className: cn(className),
|
|
155
|
+
children: props.children,
|
|
156
|
+
}
|
|
157
|
+
: {
|
|
158
|
+
'data-slot': 'sortable-item',
|
|
159
|
+
'data-value': value,
|
|
160
|
+
'data-dragging': isSortableDragging,
|
|
161
|
+
'data-disabled': disabled,
|
|
162
|
+
ref: setNodeRef,
|
|
163
|
+
style: {
|
|
164
|
+
transition,
|
|
165
|
+
transform: CSS.Transform.toString(transform),
|
|
166
|
+
},
|
|
167
|
+
...attributes,
|
|
168
|
+
className: cn(isSortableDragging && 'z-50 opacity-50', disabled && 'opacity-50', className),
|
|
169
|
+
children: props.children,
|
|
170
|
+
};
|
|
171
|
+
const itemContextValue = isOverlay ? { listeners: undefined, isDragging: true, disabled: false } : { listeners, isDragging: isSortableDragging, disabled };
|
|
172
|
+
const rendered = useRender({
|
|
173
|
+
defaultTagName: 'div',
|
|
174
|
+
render,
|
|
175
|
+
props: mergeProps(defaultProps, props),
|
|
176
|
+
});
|
|
177
|
+
return _jsx(SortableItemContext.Provider, { value: itemContextValue, children: rendered });
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Drag handle for a `SortableItem`. Must be nested under `SortableItem` so listeners and drag state apply.
|
|
181
|
+
*/
|
|
182
|
+
function SortableItemHandle({ className, render, cursor = true, ...props }) {
|
|
183
|
+
const { listeners, isDragging, disabled } = useContext(SortableItemContext);
|
|
184
|
+
const defaultProps = {
|
|
185
|
+
'data-slot': 'sortable-item-handle',
|
|
186
|
+
'data-dragging': isDragging,
|
|
187
|
+
'data-disabled': disabled,
|
|
188
|
+
...listeners,
|
|
189
|
+
className: cn(cursor && (isDragging ? 'cursor-grabbing!' : 'cursor-grab!'), className),
|
|
190
|
+
children: props.children,
|
|
191
|
+
};
|
|
192
|
+
return useRender({
|
|
193
|
+
defaultTagName: 'div',
|
|
194
|
+
render,
|
|
195
|
+
props: mergeProps(defaultProps, props),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Alternate overlay entry: renders `DragOverlay` in a portal with shared modifiers/animation from `Sortable` context.
|
|
200
|
+
* Prefer the built-in overlay on `Sortable` unless you need this API.
|
|
201
|
+
*/
|
|
202
|
+
function SortableOverlay({ children, className, ...props }) {
|
|
203
|
+
const { activeId, modifiers } = useContext(SortableInternalContext);
|
|
204
|
+
const isClient = useIsClient();
|
|
205
|
+
const content = activeId && children ? (typeof children === 'function' ? children({ value: activeId }) : children) : null;
|
|
206
|
+
if (!isClient)
|
|
207
|
+
return null;
|
|
208
|
+
return createPortal(_jsx(DragOverlay, { dropAnimation: dropAnimationConfig, modifiers: modifiers, className: cn('z-50', activeId && 'cursor-grabbing', className), ...props, children: _jsx(IsOverlayContext.Provider, { value: true, children: content }) }), document.body);
|
|
209
|
+
}
|
|
210
|
+
export { Sortable, SortableItem, SortableItemHandle, SortableOverlay };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
declare const meta: {
|
|
3
|
+
title: string;
|
|
4
|
+
tags: string[];
|
|
5
|
+
parameters: {
|
|
6
|
+
layout: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof meta>;
|
|
11
|
+
export declare const Vertical: Story;
|
|
12
|
+
export declare const Horizontal: Story;
|
|
13
|
+
export declare const Grid: Story;
|
|
14
|
+
//# sourceMappingURL=sortable.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sortable.stories.d.ts","sourceRoot":"","sources":["../../../src/components/sortable/sortable.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAcvD,QAAA,MAAM,IAAI;;;;;;CAMM,CAAC;AAEjB,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAiEnC,eAAO,MAAM,QAAQ,EAAE,KAEtB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,KAElB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { GripVertical } from 'lucide-react';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Sortable, SortableItem, SortableItemHandle } from '.';
|
|
5
|
+
const initialItems = [
|
|
6
|
+
{ id: 'a', title: 'First item' },
|
|
7
|
+
{ id: 'b', title: 'Second item' },
|
|
8
|
+
{ id: 'c', title: 'Third item' },
|
|
9
|
+
];
|
|
10
|
+
const meta = {
|
|
11
|
+
title: 'UI/Sortable',
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'padded',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
export default meta;
|
|
18
|
+
function VerticalListStory() {
|
|
19
|
+
const [items, setItems] = useState(initialItems);
|
|
20
|
+
return (_jsx("div", { className: "w-full max-w-md", children: _jsx(Sortable, { value: items, onValueChange: setItems, getItemValue: (item) => item.id, className: "flex flex-col gap-2", children: items.map((item) => (_jsxs(SortableItem, { value: item.id, className: "flex items-center gap-3 rounded-md border bg-card px-3 py-2 shadow-sm", children: [_jsx(SortableItemHandle, { className: "text-muted-foreground hover:text-foreground", "aria-label": `Drag ${item.title}`, children: _jsx(GripVertical, { className: "size-5", strokeWidth: 2.5 }) }), _jsx("span", { className: "text-sm font-medium", children: item.title })] }, item.id))) }) }));
|
|
21
|
+
}
|
|
22
|
+
function HorizontalListStory() {
|
|
23
|
+
const [items, setItems] = useState(initialItems);
|
|
24
|
+
return (_jsx("div", { className: "w-full max-w-3xl", children: _jsx(Sortable, { value: items, onValueChange: setItems, getItemValue: (item) => item.id, strategy: "horizontal", className: "flex flex-row flex-wrap gap-2", children: items.map((item) => (_jsxs(SortableItem, { value: item.id, className: "flex min-w-[10rem] items-center gap-2 rounded-md border bg-card px-3 py-2 shadow-sm", children: [_jsx(SortableItemHandle, { className: "text-muted-foreground hover:text-foreground", "aria-label": `Drag ${item.title}`, children: _jsx(GripVertical, { className: "size-5", strokeWidth: 2.5 }) }), _jsx("span", { className: "text-sm font-medium", children: item.title })] }, item.id))) }) }));
|
|
25
|
+
}
|
|
26
|
+
function GridStory() {
|
|
27
|
+
const [items, setItems] = useState(initialItems);
|
|
28
|
+
return (_jsx("div", { className: "w-full max-w-lg", children: _jsx(Sortable, { value: items, onValueChange: setItems, getItemValue: (item) => item.id, strategy: "grid", className: "grid grid-cols-2 gap-2", children: items.map((item) => (_jsxs(SortableItem, { value: item.id, className: "flex items-center gap-2 rounded-md border bg-card px-3 py-4 shadow-sm", children: [_jsx(SortableItemHandle, { className: "text-muted-foreground hover:text-foreground", "aria-label": `Drag ${item.title}`, children: _jsx(GripVertical, { className: "size-5", strokeWidth: 2.5 }) }), _jsx("span", { className: "text-sm font-medium", children: item.title })] }, item.id))) }) }));
|
|
29
|
+
}
|
|
30
|
+
export const Vertical = {
|
|
31
|
+
render: () => _jsx(VerticalListStory, {}),
|
|
32
|
+
};
|
|
33
|
+
export const Horizontal = {
|
|
34
|
+
render: () => _jsx(HorizontalListStory, {}),
|
|
35
|
+
};
|
|
36
|
+
export const Grid = {
|
|
37
|
+
render: () => _jsx(GridStory, {}),
|
|
38
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { Loader2 } from 'lucide-react';
|
|
3
3
|
/**
|
|
4
|
-
* Props for `Spinner`. Accepts standard SVG attributes (via
|
|
4
|
+
* Props for `Spinner`. Accepts standard SVG attributes (via Lucide’s `Loader2`).
|
|
5
5
|
*/
|
|
6
|
-
export type SpinnerProps = React.ComponentPropsWithoutRef<typeof
|
|
6
|
+
export type SpinnerProps = React.ComponentPropsWithoutRef<typeof Loader2>;
|
|
7
7
|
/**
|
|
8
8
|
* A compact loading indicator: spinning icon with `role="status"` and default `aria-label="Loading"`.
|
|
9
9
|
* Pass `aria-label`, `aria-hidden`, or `role` through props to override (spread is applied last).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../../src/components/spinner/spinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../../src/components/spinner/spinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAIvC;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,OAAO,CAAC,CAAC;AAE1E;;;GAGG;AACH,iBAAS,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAErD;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { Loader2 } from 'lucide-react';
|
|
4
4
|
import { cn } from '../../lib/utils';
|
|
5
5
|
/**
|
|
6
6
|
* A compact loading indicator: spinning icon with `role="status"` and default `aria-label="Loading"`.
|
|
7
7
|
* Pass `aria-label`, `aria-hidden`, or `role` through props to override (spread is applied last).
|
|
8
8
|
*/
|
|
9
9
|
function Spinner({ className, ...props }) {
|
|
10
|
-
return _jsx(
|
|
10
|
+
return _jsx(Loader2, { "data-slot": "spinner", className: cn('size-4 animate-spin', className), role: "status", "aria-label": "Loading", ...props });
|
|
11
11
|
}
|
|
12
12
|
export { Spinner };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/status-indicator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/F,YAAY,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StatusIndicator, StatusIndicatorDot, StatusIndicatorLabel } from './status-indicator';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operational status: compound **`StatusIndicator`** (row shell), **`StatusIndicatorDot`** (ping + dot),
|
|
3
|
+
* and **`StatusIndicatorLabel`** (muted text). Colors use Wheelhouse semantic tokens.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import type { ComponentPropsWithoutRef } from 'react';
|
|
8
|
+
/** Semantic states for {@link StatusIndicatorDot}. */
|
|
9
|
+
export type StatusIndicatorState = 'active' | 'down' | 'fixing' | 'idle';
|
|
10
|
+
/**
|
|
11
|
+
* Props for {@link StatusIndicatorDot}.
|
|
12
|
+
*/
|
|
13
|
+
export interface StatusIndicatorDotProps {
|
|
14
|
+
/**
|
|
15
|
+
* Visual state: **`active`**, **`down`**, **`fixing`**, **`idle`**.
|
|
16
|
+
* Active, down, and fixing show a ping animation; idle is a static dot.
|
|
17
|
+
*/
|
|
18
|
+
state?: StatusIndicatorState;
|
|
19
|
+
/**
|
|
20
|
+
* Dot and ping diameter (defaults to **`sm`**).
|
|
21
|
+
*/
|
|
22
|
+
size?: 'sm' | 'md' | 'lg';
|
|
23
|
+
/**
|
|
24
|
+
* Optional Tailwind class(es) merged onto the solid dot **after** semantic colors.
|
|
25
|
+
*/
|
|
26
|
+
color?: string;
|
|
27
|
+
/** Classes on the dot wrapper (`data-slot="status-indicator-dot"`). */
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Props for {@link StatusIndicatorLabel}.
|
|
32
|
+
*/
|
|
33
|
+
export type StatusIndicatorLabelProps = ComponentPropsWithoutRef<'span'>;
|
|
34
|
+
/**
|
|
35
|
+
* Props for {@link StatusIndicator} — a flex row shell; compose with {@link StatusIndicatorDot} and {@link StatusIndicatorLabel}.
|
|
36
|
+
*/
|
|
37
|
+
export type StatusIndicatorProps = ComponentPropsWithoutRef<'div'>;
|
|
38
|
+
/**
|
|
39
|
+
* Ping + solid dot only. Use inside {@link StatusIndicator} or alone when no label is needed.
|
|
40
|
+
*/
|
|
41
|
+
declare function StatusIndicatorDot({ state, size, color, className }: StatusIndicatorDotProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
/**
|
|
43
|
+
* Muted label text beside the dot. Typically placed after {@link StatusIndicatorDot} inside {@link StatusIndicator}.
|
|
44
|
+
*/
|
|
45
|
+
declare function StatusIndicatorLabel({ className, ...props }: StatusIndicatorLabelProps): import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
/**
|
|
47
|
+
* Flex row container (`gap-2`, `items-center`). Compose with {@link StatusIndicatorDot} and optionally {@link StatusIndicatorLabel}.
|
|
48
|
+
*/
|
|
49
|
+
declare function StatusIndicator({ className, ...props }: StatusIndicatorProps): import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export { StatusIndicator, StatusIndicatorDot, StatusIndicatorLabel };
|
|
51
|
+
//# sourceMappingURL=status-indicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-indicator.d.ts","sourceRoot":"","sources":["../../../src/components/status-indicator/status-indicator.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAItD,sDAAsD;AACtD,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B;;OAEG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;AA4BnE;;GAEG;AACH,iBAAS,kBAAkB,CAAC,EAAE,KAAc,EAAE,IAAW,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,uBAAuB,2CAWrG;AAED;;GAEG;AACH,iBAAS,oBAAoB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,yBAAyB,2CAE/E;AAED;;GAEG;AACH,iBAAS,eAAe,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,oBAAoB,2CAErE;AAED,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
const getStateColors = (state) => {
|
|
4
|
+
switch (state) {
|
|
5
|
+
case 'active':
|
|
6
|
+
return { dot: 'bg-success', ping: 'bg-success/50' };
|
|
7
|
+
case 'down':
|
|
8
|
+
return { dot: 'bg-destructive-foreground', ping: 'bg-destructive-foreground/45' };
|
|
9
|
+
case 'fixing':
|
|
10
|
+
return { dot: 'bg-warning', ping: 'bg-warning/50' };
|
|
11
|
+
case 'idle':
|
|
12
|
+
default:
|
|
13
|
+
return { dot: 'bg-muted-foreground', ping: 'bg-muted-foreground/40' };
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const getSizeClasses = (size) => {
|
|
17
|
+
switch (size) {
|
|
18
|
+
case 'md':
|
|
19
|
+
return { dot: 'h-3 w-3', ping: 'h-3 w-3' };
|
|
20
|
+
case 'lg':
|
|
21
|
+
return { dot: 'h-4 w-4', ping: 'h-4 w-4' };
|
|
22
|
+
case 'sm':
|
|
23
|
+
default:
|
|
24
|
+
return { dot: 'h-2 w-2', ping: 'h-2 w-2' };
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Ping + solid dot only. Use inside {@link StatusIndicator} or alone when no label is needed.
|
|
29
|
+
*/
|
|
30
|
+
function StatusIndicatorDot({ state = 'idle', size = 'sm', color, className }) {
|
|
31
|
+
const shouldAnimate = state === 'active' || state === 'fixing' || state === 'down';
|
|
32
|
+
const colors = getStateColors(state);
|
|
33
|
+
const sizeClasses = getSizeClasses(size);
|
|
34
|
+
return (_jsxs("div", { "data-slot": "status-indicator-dot", className: cn('relative flex items-center', className), children: [shouldAnimate && _jsx("span", { className: cn('absolute inline-flex animate-ping rounded-full opacity-75', sizeClasses.ping, colors.ping) }), _jsx("span", { className: cn('relative inline-flex rounded-full', sizeClasses.dot, colors.dot, color) })] }));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Muted label text beside the dot. Typically placed after {@link StatusIndicatorDot} inside {@link StatusIndicator}.
|
|
38
|
+
*/
|
|
39
|
+
function StatusIndicatorLabel({ className, ...props }) {
|
|
40
|
+
return _jsx("span", { "data-slot": "status-indicator-label", className: cn('text-sm text-muted-foreground', className), ...props });
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Flex row container (`gap-2`, `items-center`). Compose with {@link StatusIndicatorDot} and optionally {@link StatusIndicatorLabel}.
|
|
44
|
+
*/
|
|
45
|
+
function StatusIndicator({ className, ...props }) {
|
|
46
|
+
return _jsx("div", { "data-slot": "status-indicator", className: cn('flex items-center gap-2', className), ...props });
|
|
47
|
+
}
|
|
48
|
+
export { StatusIndicator, StatusIndicatorDot, StatusIndicatorLabel };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
declare const meta: {
|
|
3
|
+
title: string;
|
|
4
|
+
tags: string[];
|
|
5
|
+
parameters: {
|
|
6
|
+
layout: string;
|
|
7
|
+
docs: {
|
|
8
|
+
description: {
|
|
9
|
+
component: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
export declare const Default: Story;
|
|
17
|
+
export declare const AllStates: Story;
|
|
18
|
+
export declare const Sizes: Story;
|
|
19
|
+
export declare const DotOnly: Story;
|
|
20
|
+
//# sourceMappingURL=status-indicator.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-indicator.stories.d.ts","sourceRoot":"","sources":["../../../src/components/status-indicator/status-indicator.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIvD,QAAA,MAAM,IAAI;;;;;;;;;;;CAYM,CAAC;AAEjB,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAmBrB,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KA+CvB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAuCnB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAYrB,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StatusIndicator, StatusIndicatorDot, StatusIndicatorLabel } from './status-indicator';
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'UI/StatusIndicator',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
parameters: {
|
|
7
|
+
layout: 'centered',
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: 'Compose **StatusIndicator** (flex row) with **StatusIndicatorDot** (ping + dot) and optionally **StatusIndicatorLabel** (muted text). Use the dot alone when no label is needed.',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export default meta;
|
|
16
|
+
export const Default = {
|
|
17
|
+
parameters: {
|
|
18
|
+
docs: {
|
|
19
|
+
source: {
|
|
20
|
+
code: `import { StatusIndicator, StatusIndicatorDot, StatusIndicatorLabel } from '@wheelhouse/ui';
|
|
21
|
+
|
|
22
|
+
<StatusIndicator>
|
|
23
|
+
<StatusIndicatorDot state="idle" />
|
|
24
|
+
<StatusIndicatorLabel>Status</StatusIndicatorLabel>
|
|
25
|
+
</StatusIndicator>`,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
render: () => (_jsxs(StatusIndicator, { children: [_jsx(StatusIndicatorDot, { state: "idle" }), _jsx(StatusIndicatorLabel, { children: "Status" })] })),
|
|
30
|
+
};
|
|
31
|
+
export const AllStates = {
|
|
32
|
+
parameters: {
|
|
33
|
+
docs: {
|
|
34
|
+
source: {
|
|
35
|
+
code: `import { StatusIndicator, StatusIndicatorDot, StatusIndicatorLabel } from '@wheelhouse/ui';
|
|
36
|
+
|
|
37
|
+
<div className="flex flex-col gap-4">
|
|
38
|
+
<StatusIndicator>
|
|
39
|
+
<StatusIndicatorDot state="active" />
|
|
40
|
+
<StatusIndicatorLabel>Active</StatusIndicatorLabel>
|
|
41
|
+
</StatusIndicator>
|
|
42
|
+
<StatusIndicator>
|
|
43
|
+
<StatusIndicatorDot state="fixing" />
|
|
44
|
+
<StatusIndicatorLabel>Fixing</StatusIndicatorLabel>
|
|
45
|
+
</StatusIndicator>
|
|
46
|
+
<StatusIndicator>
|
|
47
|
+
<StatusIndicatorDot state="down" />
|
|
48
|
+
<StatusIndicatorLabel>Down</StatusIndicatorLabel>
|
|
49
|
+
</StatusIndicator>
|
|
50
|
+
<StatusIndicator>
|
|
51
|
+
<StatusIndicatorDot state="idle" />
|
|
52
|
+
<StatusIndicatorLabel>Idle</StatusIndicatorLabel>
|
|
53
|
+
</StatusIndicator>
|
|
54
|
+
</div>`,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
render: () => (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(StatusIndicator, { children: [_jsx(StatusIndicatorDot, { state: "active" }), _jsx(StatusIndicatorLabel, { children: "Active" })] }), _jsxs(StatusIndicator, { children: [_jsx(StatusIndicatorDot, { state: "fixing" }), _jsx(StatusIndicatorLabel, { children: "Fixing" })] }), _jsxs(StatusIndicator, { children: [_jsx(StatusIndicatorDot, { state: "down" }), _jsx(StatusIndicatorLabel, { children: "Down" })] }), _jsxs(StatusIndicator, { children: [_jsx(StatusIndicatorDot, { state: "idle" }), _jsx(StatusIndicatorLabel, { children: "Idle" })] })] })),
|
|
59
|
+
};
|
|
60
|
+
export const Sizes = {
|
|
61
|
+
parameters: {
|
|
62
|
+
docs: {
|
|
63
|
+
source: {
|
|
64
|
+
code: `import { StatusIndicator, StatusIndicatorDot, StatusIndicatorLabel } from '@wheelhouse/ui';
|
|
65
|
+
|
|
66
|
+
<div className="flex items-end gap-6">
|
|
67
|
+
<StatusIndicator>
|
|
68
|
+
<StatusIndicatorDot state="active" size="sm" />
|
|
69
|
+
<StatusIndicatorLabel>Small</StatusIndicatorLabel>
|
|
70
|
+
</StatusIndicator>
|
|
71
|
+
<StatusIndicator>
|
|
72
|
+
<StatusIndicatorDot state="active" size="md" />
|
|
73
|
+
<StatusIndicatorLabel>Medium</StatusIndicatorLabel>
|
|
74
|
+
</StatusIndicator>
|
|
75
|
+
<StatusIndicator>
|
|
76
|
+
<StatusIndicatorDot state="active" size="lg" />
|
|
77
|
+
<StatusIndicatorLabel>Large</StatusIndicatorLabel>
|
|
78
|
+
</StatusIndicator>
|
|
79
|
+
</div>`,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
render: () => (_jsxs("div", { className: "flex items-end gap-6", children: [_jsxs(StatusIndicator, { children: [_jsx(StatusIndicatorDot, { state: "active", size: "sm" }), _jsx(StatusIndicatorLabel, { children: "Small" })] }), _jsxs(StatusIndicator, { children: [_jsx(StatusIndicatorDot, { state: "active", size: "md" }), _jsx(StatusIndicatorLabel, { children: "Medium" })] }), _jsxs(StatusIndicator, { children: [_jsx(StatusIndicatorDot, { state: "active", size: "lg" }), _jsx(StatusIndicatorLabel, { children: "Large" })] })] })),
|
|
84
|
+
};
|
|
85
|
+
export const DotOnly = {
|
|
86
|
+
parameters: {
|
|
87
|
+
docs: {
|
|
88
|
+
description: { story: 'Use **StatusIndicatorDot** without the shell when the dot sits inside another control (e.g. button, table cell).' },
|
|
89
|
+
source: {
|
|
90
|
+
code: `import { StatusIndicatorDot } from '@wheelhouse/ui';
|
|
91
|
+
|
|
92
|
+
<StatusIndicatorDot state="active" />`,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
render: () => _jsx(StatusIndicatorDot, { state: "active" }),
|
|
97
|
+
};
|