@inkra/react 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -1
- package/dist/PencilIcon.js +14 -16
- package/dist/PencilLine.js +18 -12
- package/dist/PencilText.js +192 -59
- package/dist/alert-dialog/index.d.ts +64 -0
- package/dist/alert-dialog/index.js +109 -0
- package/dist/audio-player/index.d.ts +60 -0
- package/dist/audio-player/index.js +101 -0
- package/dist/carousel/index.d.ts +51 -0
- package/dist/carousel/index.js +290 -0
- package/dist/choice-grid/index.d.ts +38 -0
- package/dist/choice-grid/index.js +85 -0
- package/dist/color-picker/color.d.ts +15 -0
- package/dist/color-picker/color.js +55 -0
- package/dist/color-picker/index.d.ts +31 -0
- package/dist/color-picker/index.js +69 -0
- package/dist/command/index.d.ts +23 -0
- package/dist/command/index.js +39 -0
- package/dist/context-menu/index.d.ts +76 -0
- package/dist/context-menu/index.js +240 -0
- package/dist/data-table/index.d.ts +47 -0
- package/dist/data-table/index.js +91 -0
- package/dist/date-picker/index.d.ts +50 -0
- package/dist/date-picker/index.js +180 -0
- package/dist/display/ProgressRing.d.ts +6 -0
- package/dist/display/ProgressRing.js +146 -0
- package/dist/display/index.d.ts +27 -0
- package/dist/display/index.js +18 -2
- package/dist/dockable-panel/index.d.ts +41 -0
- package/dist/dockable-panel/index.js +253 -0
- package/dist/dockable-panel/model.d.ts +21 -0
- package/dist/dockable-panel/model.js +61 -0
- package/dist/drawer/index.d.ts +63 -0
- package/dist/drawer/index.js +86 -0
- package/dist/editable/index.d.ts +53 -0
- package/dist/editable/index.js +162 -0
- package/dist/empty/index.d.ts +21 -0
- package/dist/empty/index.js +23 -0
- package/dist/flip/index.d.ts +49 -0
- package/dist/flip/index.js +138 -0
- package/dist/form/EditablePencilText.js +157 -146
- package/dist/form/Textarea.d.ts +12 -0
- package/dist/form/Textarea.js +83 -4
- package/dist/form/index.d.ts +1 -1
- package/dist/heatmap/index.d.ts +59 -0
- package/dist/heatmap/index.js +248 -0
- package/dist/hotkey/index.d.ts +37 -0
- package/dist/hotkey/index.js +130 -0
- package/dist/hotkey/model.d.ts +27 -0
- package/dist/hotkey/model.js +97 -0
- package/dist/hover-card/index.d.ts +33 -0
- package/dist/hover-card/index.js +141 -0
- package/dist/image-cropper/index.d.ts +82 -0
- package/dist/image-cropper/index.js +261 -0
- package/dist/image-cropper/model.d.ts +42 -0
- package/dist/image-cropper/model.js +94 -0
- package/dist/image-viewer/index.d.ts +79 -0
- package/dist/image-viewer/index.js +220 -0
- package/dist/image-viewer/model.d.ts +9 -0
- package/dist/image-viewer/model.js +18 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +47 -0
- package/dist/internal/cache.d.ts +1 -1
- package/dist/internal/cache.js +1 -1
- package/dist/internal/renderTokens.d.ts +10 -0
- package/dist/internal/renderTokens.js +13 -0
- package/dist/internal/surfaceRenderer.d.ts +14 -6
- package/dist/internal/surfaceRenderer.js +15 -12
- package/dist/internal/textRenderer.d.ts +1 -0
- package/dist/internal/textRenderer.js +33 -7
- package/dist/internal/usePencilSurface.js +16 -8
- package/dist/internal/useSelectionDrawing.js +15 -11
- package/dist/journey-map/MapScale.js +3 -1
- package/dist/journey-map/index.js +3 -1
- package/dist/keypad/index.d.ts +23 -0
- package/dist/keypad/index.js +63 -0
- package/dist/keypad/model.d.ts +20 -0
- package/dist/keypad/model.js +33 -0
- package/dist/layout/index.d.ts +63 -0
- package/dist/layout/index.js +62 -0
- package/dist/log/index.d.ts +45 -0
- package/dist/log/index.js +159 -0
- package/dist/log/model.d.ts +16 -0
- package/dist/log/model.js +13 -0
- package/dist/node-graph/index.d.ts +123 -0
- package/dist/node-graph/index.js +304 -0
- package/dist/number-ticker/index.d.ts +29 -0
- package/dist/number-ticker/index.js +132 -0
- package/dist/number-ticker/model.d.ts +8 -0
- package/dist/number-ticker/model.js +34 -0
- package/dist/overflow-list/index.d.ts +39 -0
- package/dist/overflow-list/index.js +230 -0
- package/dist/overflow-list/layout.d.ts +19 -0
- package/dist/overflow-list/layout.js +44 -0
- package/dist/pagination/index.d.ts +23 -0
- package/dist/pagination/index.js +41 -0
- package/dist/pagination/model.d.ts +4 -0
- package/dist/pagination/model.js +42 -0
- package/dist/pan-zoom/index.d.ts +71 -0
- package/dist/pan-zoom/index.js +175 -0
- package/dist/radar/index.d.ts +54 -0
- package/dist/radar/index.js +220 -0
- package/dist/range-slider/index.d.ts +16 -0
- package/dist/range-slider/index.js +22 -0
- package/dist/rating/index.d.ts +28 -0
- package/dist/rating/index.js +127 -0
- package/dist/resizable/index.d.ts +32 -0
- package/dist/resizable/index.js +50 -0
- package/dist/scroll-area/index.d.ts +36 -0
- package/dist/scroll-area/index.js +44 -0
- package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
- package/dist/segmented-progress/SegmentedProgress.js +42 -0
- package/dist/slider/index.js +1 -1
- package/dist/slot/index.d.ts +24 -0
- package/dist/slot/index.js +29 -0
- package/dist/sortable/index.d.ts +50 -0
- package/dist/sortable/index.js +278 -0
- package/dist/sortable/model.d.ts +30 -0
- package/dist/sortable/model.js +64 -0
- package/dist/sparkline/index.d.ts +19 -0
- package/dist/sparkline/index.js +240 -0
- package/dist/sparkline/model.d.ts +16 -0
- package/dist/sparkline/model.js +28 -0
- package/dist/steps/index.d.ts +16 -0
- package/dist/steps/index.js +37 -0
- package/dist/styles.css +4850 -287
- package/dist/tag-input/index.d.ts +69 -0
- package/dist/tag-input/index.js +325 -0
- package/dist/tag-input/model.d.ts +14 -0
- package/dist/tag-input/model.js +38 -0
- package/dist/time-picker/index.d.ts +15 -0
- package/dist/time-picker/index.js +168 -0
- package/dist/timeline/index.d.ts +14 -0
- package/dist/timeline/index.js +16 -0
- package/dist/toast/index.d.ts +27 -0
- package/dist/toast/index.js +36 -0
- package/dist/toggle-group/index.d.ts +17 -0
- package/dist/toggle-group/index.js +26 -0
- package/dist/tokens.css +10 -9
- package/dist/transfer/index.d.ts +64 -0
- package/dist/transfer/index.js +174 -0
- package/dist/transfer/model.d.ts +12 -0
- package/dist/transfer/model.js +33 -0
- package/dist/tree/index.d.ts +39 -0
- package/dist/tree/index.js +36 -0
- package/dist/tree-select/index.d.ts +59 -0
- package/dist/tree-select/index.js +142 -0
- package/dist/tree-select/model.d.ts +31 -0
- package/dist/tree-select/model.js +135 -0
- package/dist/typewriter/index.d.ts +21 -0
- package/dist/typewriter/index.js +101 -0
- package/dist/typewriter/model.d.ts +7 -0
- package/dist/typewriter/model.js +45 -0
- package/dist/upload/index.d.ts +59 -0
- package/dist/upload/index.js +230 -0
- package/dist/virtual-list/index.d.ts +44 -0
- package/dist/virtual-list/index.js +128 -0
- package/dist/virtual-list/model.d.ts +7 -0
- package/dist/virtual-list/model.js +27 -0
- package/package.json +22 -3
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
4
|
+
import { PencilLine } from '../PencilLine.js';
|
|
5
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
6
|
+
import { PencilText } from '../PencilText.js';
|
|
7
|
+
const markerPaths = [
|
|
8
|
+
{ d: 'M16 7a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z' },
|
|
9
|
+
{ d: 'M16 11v6l4 3' },
|
|
10
|
+
];
|
|
11
|
+
const copy = (value) => typeof value === 'string' ? _jsx(PencilText, { children: value }) : value;
|
|
12
|
+
export const Timeline = forwardRef(function Timeline({ 'aria-label': ariaLabel = 'Timeline', className, items, orientation = 'vertical', ...props }, ref) {
|
|
13
|
+
return _jsx("ol", { ...props, ref: ref, "aria-label": ariaLabel, className: ['inkra-timeline', className].filter(Boolean).join(' '), "data-orientation": orientation, children: items.map((item, index) => _jsxs("li", { className: "inkra-timeline__item", children: [_jsxs("span", { className: "inkra-timeline__rail", "aria-hidden": "true", children: [_jsx(PencilSurface, { as: "span", className: "inkra-timeline__marker", variant: "quiet", radius: 8, seed: `timeline-${item.id}`, children: _jsx(PencilIcon, { paths: markerPaths, size: 24, sourceSize: 32 }) }), index < items.length - 1 && _jsx(PencilLine, { className: "inkra-timeline__connector", orientation: orientation === 'vertical' ? 'vertical' : 'horizontal', seed: `timeline-line-${item.id}` })] }), _jsxs("div", { className: "inkra-timeline__body", children: [item.dateTime
|
|
14
|
+
? _jsx("time", { className: "inkra-timeline__date", dateTime: item.dateTime, children: copy(item.date) })
|
|
15
|
+
: _jsx("span", { className: "inkra-timeline__date", children: copy(item.date) }), _jsx("div", { className: "inkra-timeline__title", children: copy(item.title) }), item.description != null && _jsx("div", { className: "inkra-timeline__description", children: copy(item.description) }), item.content != null && _jsx("div", { className: "inkra-timeline__content", children: item.content })] })] }, item.id)) });
|
|
16
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Toast as BaseToast } from '@base-ui/react/toast';
|
|
2
|
+
import type { ToastManager as BaseToastManager } from '@base-ui/react/toast';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
export type ToastPlacement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
|
|
5
|
+
export type ToastTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info';
|
|
6
|
+
export type ToastSwipeDirection = 'up' | 'down' | 'left' | 'right';
|
|
7
|
+
export type ToastManager<Data extends object = any> = BaseToastManager<Data>;
|
|
8
|
+
export interface ToastProviderProps {
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
limit?: number;
|
|
12
|
+
toastManager?: ToastManager;
|
|
13
|
+
placement?: ToastPlacement;
|
|
14
|
+
swipeDirection?: ToastSwipeDirection | ToastSwipeDirection[];
|
|
15
|
+
closeLabel?: string;
|
|
16
|
+
viewportLabel?: string;
|
|
17
|
+
viewportClassName?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function Provider({ children, closeLabel, limit, placement, swipeDirection, timeout, toastManager, viewportClassName, viewportLabel, }: ToastProviderProps): import("react").JSX.Element;
|
|
20
|
+
export declare const useToastManager: typeof BaseToast.useToastManager;
|
|
21
|
+
export declare const createToastManager: typeof BaseToast.createToastManager;
|
|
22
|
+
export declare const Toast: {
|
|
23
|
+
Provider: typeof Provider;
|
|
24
|
+
useToastManager: typeof BaseToast.useToastManager;
|
|
25
|
+
createToastManager: typeof BaseToast.createToastManager;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Toast as BaseToast } from '@base-ui/react/toast';
|
|
3
|
+
import { Button } from '../Button.js';
|
|
4
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
5
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
6
|
+
import { PencilText } from '../PencilText.js';
|
|
7
|
+
import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef, } from '../overlay/themeBridge.js';
|
|
8
|
+
const closePaths = [
|
|
9
|
+
{ d: 'M6 6 18 18' },
|
|
10
|
+
{ d: 'M18 6 6 18' },
|
|
11
|
+
];
|
|
12
|
+
function text(content) {
|
|
13
|
+
return typeof content === 'string' ? _jsx(PencilText, { children: content }) : content;
|
|
14
|
+
}
|
|
15
|
+
function toneOf(type) {
|
|
16
|
+
return type === 'success' || type === 'warning' || type === 'danger' || type === 'info'
|
|
17
|
+
? type
|
|
18
|
+
: 'neutral';
|
|
19
|
+
}
|
|
20
|
+
function ToastThemeAnchor() {
|
|
21
|
+
const ref = usePortalTriggerRef(null);
|
|
22
|
+
return _jsx("span", { "aria-hidden": "true", className: "inkra-toast__theme-anchor", ref: ref });
|
|
23
|
+
}
|
|
24
|
+
function ToastQueue({ closeLabel, placement, swipeDirection, viewportClassName, viewportLabel, }) {
|
|
25
|
+
const { toasts } = BaseToast.useToastManager();
|
|
26
|
+
return (_jsx(BaseToast.Portal, { children: _jsx(PortalThemeBridge, { children: _jsx(BaseToast.Viewport, { "aria-label": viewportLabel, className: ['inkra-toast__viewport', viewportClassName].filter(Boolean).join(' '), "data-placement": placement, children: toasts.map((toastItem) => {
|
|
27
|
+
const tone = toneOf(toastItem.type);
|
|
28
|
+
return (_jsxs(BaseToast.Root, { className: "inkra-toast__root", "data-tone": tone, swipeDirection: swipeDirection, toast: toastItem, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-toast__surface", seed: `toast-${toastItem.id}-${toastItem.updateKey ?? 0}`, variant: tone === 'neutral' ? 'paper' : 'quiet' }), _jsxs(BaseToast.Content, { className: "inkra-toast__content", children: [_jsxs("div", { className: "inkra-toast__copy", children: [toastItem.title != null && (_jsx(BaseToast.Title, { className: "inkra-toast__title", children: text(toastItem.title) })), toastItem.description != null && (_jsx(BaseToast.Description, { className: "inkra-toast__description", children: text(toastItem.description) }))] }), _jsxs("div", { className: "inkra-toast__actions", children: [toastItem.actionProps?.children != null && (_jsx(BaseToast.Action, { className: "inkra-toast__action", render: _jsx(Button, { size: "sm", variant: "outline" }) })), _jsx(BaseToast.Close, { "aria-label": closeLabel, className: "inkra-toast__close", render: _jsx(Button, { size: "icon-sm", variant: "ghost" }), children: _jsx(PencilIcon, { paths: closePaths, size: 20, sourceSize: 24, decorative: true }) })] })] })] }, toastItem.id));
|
|
29
|
+
}) }) }) }));
|
|
30
|
+
}
|
|
31
|
+
function Provider({ children, closeLabel = 'Close notification', limit, placement = 'bottom-end', swipeDirection = ['down', 'right'], timeout, toastManager, viewportClassName, viewportLabel = 'Notifications', }) {
|
|
32
|
+
return (_jsx(PortalThemeProvider, { children: _jsxs(BaseToast.Provider, { limit: limit, timeout: timeout, toastManager: toastManager, children: [_jsx(ToastThemeAnchor, {}), children, _jsx(ToastQueue, { closeLabel: closeLabel, placement: placement, swipeDirection: swipeDirection, viewportClassName: viewportClassName, viewportLabel: viewportLabel })] }) }));
|
|
33
|
+
}
|
|
34
|
+
export const useToastManager = BaseToast.useToastManager;
|
|
35
|
+
export const createToastManager = BaseToast.createToastManager;
|
|
36
|
+
export const Toast = { Provider, useToastManager, createToastManager };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Toggle as BaseToggle } from '@base-ui/react/toggle';
|
|
2
|
+
import { ToggleGroup as BaseToggleGroup } from '@base-ui/react/toggle-group';
|
|
3
|
+
import type { ReactElement, ReactNode, RefAttributes } from 'react';
|
|
4
|
+
export interface ToggleGroupRootProps<Value extends string = string> extends BaseToggleGroup.Props<Value> {
|
|
5
|
+
size?: 'sm' | 'md' | 'lg';
|
|
6
|
+
}
|
|
7
|
+
type RootComponent = <Value extends string = string>(props: ToggleGroupRootProps<Value> & RefAttributes<HTMLDivElement>) => ReactElement;
|
|
8
|
+
export interface ToggleGroupItemProps<Value extends string = string> extends BaseToggle.Props<Value> {
|
|
9
|
+
startIcon?: ReactNode;
|
|
10
|
+
endIcon?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
type ItemComponent = <Value extends string = string>(props: ToggleGroupItemProps<Value> & RefAttributes<HTMLButtonElement>) => ReactElement;
|
|
13
|
+
export declare const ToggleGroup: {
|
|
14
|
+
Root: RootComponent;
|
|
15
|
+
Item: ItemComponent;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DirectionProvider } from '@base-ui/react/direction-provider';
|
|
3
|
+
import { Toggle as BaseToggle } from '@base-ui/react/toggle';
|
|
4
|
+
import { ToggleGroup as BaseToggleGroup } from '@base-ui/react/toggle-group';
|
|
5
|
+
import { createContext, forwardRef, useContext } from 'react';
|
|
6
|
+
import { PencilLine } from '../PencilLine.js';
|
|
7
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
8
|
+
import { PencilText } from '../PencilText.js';
|
|
9
|
+
function classes(base, className) {
|
|
10
|
+
if (typeof className === 'function') {
|
|
11
|
+
return (state) => [base, className(state)].filter(Boolean).join(' ');
|
|
12
|
+
}
|
|
13
|
+
return [base, className].filter(Boolean).join(' ');
|
|
14
|
+
}
|
|
15
|
+
const OrientationContext = createContext('horizontal');
|
|
16
|
+
const RootImplementation = forwardRef(function ToggleGroupRoot({ children, className, dir, orientation = 'horizontal', size = 'md', ...props }, ref) {
|
|
17
|
+
const direction = dir === 'rtl' ? 'rtl' : 'ltr';
|
|
18
|
+
return (_jsx(DirectionProvider, { direction: direction, children: _jsx(OrientationContext.Provider, { value: orientation, children: _jsxs(BaseToggleGroup, { ...props, className: classes('inkra-toggle-group', className), "data-size": size, dir: dir, orientation: orientation, ref: ref, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-toggle-group__outline", seed: "toggle-group-outline", variant: "outline" }), children] }) }) }));
|
|
19
|
+
});
|
|
20
|
+
const ItemImplementation = forwardRef(function ToggleGroupItem({ children, className, endIcon, startIcon, value, ...props }, ref) {
|
|
21
|
+
const orientation = useContext(OrientationContext);
|
|
22
|
+
return (_jsxs(BaseToggle, { ...props, className: classes('inkra-toggle-group__item', className), ref: ref, value: value, children: [_jsx(PencilLine, { className: "inkra-toggle-group__divider", orientation: orientation === 'horizontal' ? 'vertical' : 'horizontal', seed: `toggle-${value ?? 'item'}-divider` }), _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-toggle-group__selection", seed: `toggle-${value ?? 'item'}`, variant: "selection" }), _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-toggle-group__focus", seed: `toggle-${value ?? 'item'}-focus`, variant: "outline" }), startIcon != null && _jsx("span", { "aria-hidden": "true", className: "inkra-toggle-group__icon", children: startIcon }), _jsx("span", { className: "inkra-toggle-group__label", children: typeof children === 'string' ? _jsx(PencilText, { children: children }) : children }), endIcon != null && _jsx("span", { "aria-hidden": "true", className: "inkra-toggle-group__icon", children: endIcon })] }));
|
|
23
|
+
});
|
|
24
|
+
const Root = RootImplementation;
|
|
25
|
+
const Item = ItemImplementation;
|
|
26
|
+
export const ToggleGroup = { Root, Item };
|
package/dist/tokens.css
CHANGED
|
@@ -165,11 +165,11 @@
|
|
|
165
165
|
--inkra-z-modal: 30;
|
|
166
166
|
--inkra-z-tooltip: 40;
|
|
167
167
|
|
|
168
|
-
--inkra-stroke-width:
|
|
168
|
+
--inkra-stroke-width: 2px;
|
|
169
169
|
--inkra-stroke-variation: 0.82;
|
|
170
170
|
--inkra-stroke-grain: 0.7;
|
|
171
171
|
--inkra-stroke-breaks: 0.2;
|
|
172
|
-
--inkra-stroke-amplitude:
|
|
172
|
+
--inkra-stroke-amplitude: 1.5px;
|
|
173
173
|
--inkra-stroke-step: 0.8px;
|
|
174
174
|
--inkra-stroke-passes: 3;
|
|
175
175
|
--inkra-stroke-gain: 2.1;
|
|
@@ -181,19 +181,20 @@
|
|
|
181
181
|
--inkra-badge-stroke-passes: 2;
|
|
182
182
|
|
|
183
183
|
--inkra-wash-opacity: 0.48;
|
|
184
|
-
--inkra-wash-strength: 0.
|
|
185
|
-
--inkra-wash-spacing:
|
|
184
|
+
--inkra-wash-strength: 0.8;
|
|
185
|
+
--inkra-wash-spacing: 2px;
|
|
186
186
|
--inkra-wash-roughness: 3;
|
|
187
|
+
--inkra-wash-angle: -45deg;
|
|
187
188
|
--inkra-wash-inset: 2px;
|
|
188
189
|
--inkra-wash-edge-reach: 0px;
|
|
189
190
|
--inkra-quiet-wash-strength: 0.18;
|
|
190
191
|
--inkra-badge-wash-strength: 0.25;
|
|
191
192
|
|
|
192
|
-
--inkra-letter-variation: 0.
|
|
193
|
-
--inkra-letter-breaks: 0.
|
|
194
|
-
--inkra-letter-outline: 0.
|
|
195
|
-
--inkra-letter-fill: 0.
|
|
196
|
-
--inkra-letter-grain: 0.
|
|
193
|
+
--inkra-letter-variation: 0.2;
|
|
194
|
+
--inkra-letter-breaks: 0.5;
|
|
195
|
+
--inkra-letter-outline: 0.9;
|
|
196
|
+
--inkra-letter-fill: 0.9;
|
|
197
|
+
--inkra-letter-grain: 0.6;
|
|
197
198
|
--inkra-paper-tile-size: 384px;
|
|
198
199
|
--inkra-render-dpr-cap: 2;
|
|
199
200
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import type { TransferDirection, TransferSide } from './model.js';
|
|
3
|
+
export interface TransferItem {
|
|
4
|
+
id: string;
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
keywords?: readonly string[];
|
|
8
|
+
searchText?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface TransferLabels {
|
|
12
|
+
sourceTitle?: ReactNode;
|
|
13
|
+
targetTitle?: ReactNode;
|
|
14
|
+
sourceSearchLabel?: string;
|
|
15
|
+
targetSearchLabel?: string;
|
|
16
|
+
sourceClearSearch?: string;
|
|
17
|
+
targetClearSearch?: string;
|
|
18
|
+
sourceSearchPlaceholder?: string;
|
|
19
|
+
targetSearchPlaceholder?: string;
|
|
20
|
+
selectAllSource?: string;
|
|
21
|
+
selectAllTarget?: string;
|
|
22
|
+
sourceEmpty?: ReactNode;
|
|
23
|
+
targetEmpty?: ReactNode;
|
|
24
|
+
sourceNoResults?: ReactNode;
|
|
25
|
+
targetNoResults?: ReactNode;
|
|
26
|
+
moveToTarget?: string;
|
|
27
|
+
moveToSource?: string;
|
|
28
|
+
moveSelectedToTarget?: string;
|
|
29
|
+
moveSelectedToSource?: string;
|
|
30
|
+
batchActions?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface TransferCountDetails {
|
|
33
|
+
side: TransferSide;
|
|
34
|
+
visible: number;
|
|
35
|
+
total: number;
|
|
36
|
+
selected: number;
|
|
37
|
+
}
|
|
38
|
+
export interface TransferItemRenderState {
|
|
39
|
+
side: TransferSide;
|
|
40
|
+
selected: boolean;
|
|
41
|
+
disabled: boolean;
|
|
42
|
+
query: string;
|
|
43
|
+
}
|
|
44
|
+
export interface TransferChangeDetails {
|
|
45
|
+
direction: TransferDirection;
|
|
46
|
+
action: 'single' | 'batch';
|
|
47
|
+
movedKeys: string[];
|
|
48
|
+
movedItems: TransferItem[];
|
|
49
|
+
}
|
|
50
|
+
export interface TransferProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
51
|
+
items: readonly TransferItem[];
|
|
52
|
+
targetKeys?: readonly string[];
|
|
53
|
+
defaultTargetKeys?: readonly string[];
|
|
54
|
+
onTargetKeysChange?: (targetKeys: string[], details: TransferChangeDetails) => void;
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
searchable?: boolean;
|
|
57
|
+
labels?: TransferLabels;
|
|
58
|
+
filterItem?: (item: TransferItem, query: string, side: TransferSide) => boolean;
|
|
59
|
+
renderItem?: (item: TransferItem, state: TransferItemRenderState) => ReactNode;
|
|
60
|
+
formatCount?: (details: TransferCountDetails) => ReactNode;
|
|
61
|
+
}
|
|
62
|
+
export declare const Transfer: import("react").ForwardRefExoticComponent<TransferProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
63
|
+
export { moveTransferKeys, normalizeTargetKeys, splitTransferItems, uniqueTransferItems } from './model.js';
|
|
64
|
+
export type { TransferDirection, TransferSide, TransferIdentity } from './model.js';
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Button } from '../Button.js';
|
|
4
|
+
import { Input } from '../form/Input.js';
|
|
5
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
6
|
+
import { PencilLine } from '../PencilLine.js';
|
|
7
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
8
|
+
import { PencilText } from '../PencilText.js';
|
|
9
|
+
import { Checkbox } from '../selection/Checkbox.js';
|
|
10
|
+
import { moveTransferKeys, normalizeTargetKeys, splitTransferItems, uniqueTransferItems, } from './model.js';
|
|
11
|
+
const searchPaths = [
|
|
12
|
+
{ d: 'M 11 4 A 7 7 0 1 0 11 18 A 7 7 0 1 0 11 4 M 16 16 L 21 21' },
|
|
13
|
+
];
|
|
14
|
+
const rightPaths = [{ d: 'M 5 12 L 19 12 M 13 6 L 19 12 L 13 18' }];
|
|
15
|
+
const leftPaths = [{ d: 'M 19 12 L 5 12 M 11 6 L 5 12 L 11 18' }];
|
|
16
|
+
const defaultLabels = {
|
|
17
|
+
sourceTitle: 'Available',
|
|
18
|
+
targetTitle: 'Selected',
|
|
19
|
+
sourceSearchLabel: 'Search available items',
|
|
20
|
+
targetSearchLabel: 'Search selected items',
|
|
21
|
+
sourceClearSearch: 'Clear available item search',
|
|
22
|
+
targetClearSearch: 'Clear selected item search',
|
|
23
|
+
sourceSearchPlaceholder: 'Search available',
|
|
24
|
+
targetSearchPlaceholder: 'Search selected',
|
|
25
|
+
selectAllSource: 'Select all visible available items',
|
|
26
|
+
selectAllTarget: 'Select all visible selected items',
|
|
27
|
+
sourceEmpty: 'No available items',
|
|
28
|
+
targetEmpty: 'No selected items',
|
|
29
|
+
sourceNoResults: 'No available items match this search',
|
|
30
|
+
targetNoResults: 'No selected items match this search',
|
|
31
|
+
moveToTarget: 'Move to selected',
|
|
32
|
+
moveToSource: 'Move to available',
|
|
33
|
+
moveSelectedToTarget: 'Move selected visible items to selected',
|
|
34
|
+
moveSelectedToSource: 'Move selected visible items to available',
|
|
35
|
+
batchActions: 'Transfer selected visible items',
|
|
36
|
+
};
|
|
37
|
+
function searchableText(item) {
|
|
38
|
+
const parts = [
|
|
39
|
+
item.searchText,
|
|
40
|
+
typeof item.label === 'string' || typeof item.label === 'number' ? String(item.label) : undefined,
|
|
41
|
+
typeof item.description === 'string' || typeof item.description === 'number' ? String(item.description) : undefined,
|
|
42
|
+
...(item.keywords ?? []),
|
|
43
|
+
];
|
|
44
|
+
return parts.filter(Boolean).join(' ').toLocaleLowerCase();
|
|
45
|
+
}
|
|
46
|
+
function defaultFilter(item, query) {
|
|
47
|
+
return searchableText(item).includes(query.trim().toLocaleLowerCase());
|
|
48
|
+
}
|
|
49
|
+
function defaultCount({ visible, total, selected }) {
|
|
50
|
+
return selected > 0 ? `${selected} selected · ${visible}/${total}` : `${visible}/${total}`;
|
|
51
|
+
}
|
|
52
|
+
function itemCopy(item) {
|
|
53
|
+
return _jsxs("span", { className: "inkra-transfer__item-copy", children: [_jsx("span", { className: "inkra-transfer__item-label", children: typeof item.label === 'string' || typeof item.label === 'number'
|
|
54
|
+
? _jsx(PencilText, { children: String(item.label) })
|
|
55
|
+
: item.label }), item.description == null ? null : _jsx("span", { className: "inkra-transfer__item-description", children: item.description })] });
|
|
56
|
+
}
|
|
57
|
+
export const Transfer = forwardRef(function Transfer({ className, defaultTargetKeys = [], disabled = false, filterItem, formatCount = defaultCount, items, labels: labelOverrides, onTargetKeysChange, renderItem, searchable = true, targetKeys, ...props }, ref) {
|
|
58
|
+
const generatedId = useId();
|
|
59
|
+
const controlled = targetKeys !== undefined;
|
|
60
|
+
const uniqueItems = useMemo(() => uniqueTransferItems(items), [items]);
|
|
61
|
+
const [uncontrolledTargetKeys, setUncontrolledTargetKeys] = useState(() => normalizeTargetKeys(items, defaultTargetKeys));
|
|
62
|
+
const currentTargetKeys = normalizeTargetKeys(uniqueItems, controlled ? targetKeys : uncontrolledTargetKeys);
|
|
63
|
+
const panels = splitTransferItems(uniqueItems, currentTargetKeys);
|
|
64
|
+
const labels = { ...defaultLabels, ...labelOverrides };
|
|
65
|
+
const [queries, setQueries] = useState({ source: '', target: '' });
|
|
66
|
+
const [selected, setSelected] = useState(() => ({ source: new Set(), target: new Set() }));
|
|
67
|
+
const [pendingFocus, setPendingFocus] = useState(null);
|
|
68
|
+
const itemRefs = useRef(new Map());
|
|
69
|
+
const panelRefs = useRef({ source: null, target: null });
|
|
70
|
+
const visible = (side) => panels[side].filter(item => {
|
|
71
|
+
const query = queries[side];
|
|
72
|
+
return query.length === 0 || (filterItem ? filterItem(item, query, side) : defaultFilter(item, query));
|
|
73
|
+
});
|
|
74
|
+
const visibleItems = { source: visible('source'), target: visible('target') };
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const valid = new Set(uniqueItems.map(item => item.id));
|
|
77
|
+
setSelected(current => {
|
|
78
|
+
const source = new Set([...current.source].filter(key => valid.has(key) && !currentTargetKeys.includes(key)));
|
|
79
|
+
const target = new Set([...current.target].filter(key => valid.has(key) && currentTargetKeys.includes(key)));
|
|
80
|
+
if (source.size === current.source.size && target.size === current.target.size)
|
|
81
|
+
return current;
|
|
82
|
+
return { source, target };
|
|
83
|
+
});
|
|
84
|
+
}, [items, currentTargetKeys.join('\u0000')]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!pendingFocus)
|
|
87
|
+
return;
|
|
88
|
+
const targetContainsItem = pendingFocus.side === 'target'
|
|
89
|
+
? currentTargetKeys.includes(pendingFocus.id)
|
|
90
|
+
: !currentTargetKeys.includes(pendingFocus.id);
|
|
91
|
+
const control = targetContainsItem ? itemRefs.current.get(`${pendingFocus.side}:${pendingFocus.id}`) : null;
|
|
92
|
+
(control ?? panelRefs.current[pendingFocus.side])?.focus({ preventScroll: true });
|
|
93
|
+
setPendingFocus(null);
|
|
94
|
+
}, [pendingFocus, currentTargetKeys.join('\u0000')]);
|
|
95
|
+
const setItemSelected = (side, id, checked) => {
|
|
96
|
+
setSelected(current => {
|
|
97
|
+
const next = new Set(current[side]);
|
|
98
|
+
if (checked)
|
|
99
|
+
next.add(id);
|
|
100
|
+
else
|
|
101
|
+
next.delete(id);
|
|
102
|
+
return { ...current, [side]: next };
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
const selectableVisible = (side) => visibleItems[side].filter(item => !item.disabled && !disabled);
|
|
106
|
+
const toggleAll = (side, checked) => {
|
|
107
|
+
const ids = selectableVisible(side).map(item => item.id);
|
|
108
|
+
setSelected(current => {
|
|
109
|
+
const next = new Set(current[side]);
|
|
110
|
+
ids.forEach(id => checked ? next.add(id) : next.delete(id));
|
|
111
|
+
return { ...current, [side]: next };
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
const move = (keys, direction, action) => {
|
|
115
|
+
const sourceSide = direction === 'to-target' ? 'source' : 'target';
|
|
116
|
+
const destinationSide = direction === 'to-target' ? 'target' : 'source';
|
|
117
|
+
const allowed = new Set(panels[sourceSide].filter(item => !item.disabled && !disabled).map(item => item.id));
|
|
118
|
+
const movedKeys = keys.filter((key, index) => allowed.has(key) && keys.indexOf(key) === index);
|
|
119
|
+
if (movedKeys.length === 0)
|
|
120
|
+
return;
|
|
121
|
+
const next = moveTransferKeys(uniqueItems, currentTargetKeys, movedKeys, direction);
|
|
122
|
+
if (!controlled)
|
|
123
|
+
setUncontrolledTargetKeys(next);
|
|
124
|
+
setSelected(current => {
|
|
125
|
+
const source = new Set(current[sourceSide]);
|
|
126
|
+
movedKeys.forEach(key => source.delete(key));
|
|
127
|
+
return { ...current, [sourceSide]: source };
|
|
128
|
+
});
|
|
129
|
+
setPendingFocus({ id: movedKeys[0], side: destinationSide });
|
|
130
|
+
onTargetKeysChange?.(next, {
|
|
131
|
+
action,
|
|
132
|
+
direction,
|
|
133
|
+
movedKeys,
|
|
134
|
+
movedItems: movedKeys.map(key => uniqueItems.find(item => item.id === key)).filter((item) => item !== undefined),
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
const renderPanel = (side) => {
|
|
138
|
+
const source = side === 'source';
|
|
139
|
+
const panelItems = panels[side];
|
|
140
|
+
const shown = visibleItems[side];
|
|
141
|
+
const selectable = selectableVisible(side);
|
|
142
|
+
const selectedVisible = selectable.filter(item => selected[side].has(item.id));
|
|
143
|
+
const allVisibleSelected = selectable.length > 0 && selectedVisible.length === selectable.length;
|
|
144
|
+
const someVisibleSelected = selectedVisible.length > 0 && !allVisibleSelected;
|
|
145
|
+
const titleId = `${generatedId}-${side}-title`;
|
|
146
|
+
const panelTitle = source ? labels.sourceTitle : labels.targetTitle;
|
|
147
|
+
const count = formatCount({ side, visible: shown.length, total: panelItems.length, selected: selectedVisible.length });
|
|
148
|
+
const empty = panelItems.length === 0
|
|
149
|
+
? source ? labels.sourceEmpty : labels.targetEmpty
|
|
150
|
+
: source ? labels.sourceNoResults : labels.targetNoResults;
|
|
151
|
+
const searchLabel = source ? labels.sourceSearchLabel : labels.targetSearchLabel;
|
|
152
|
+
const clearSearchLabel = source ? labels.sourceClearSearch : labels.targetClearSearch;
|
|
153
|
+
const searchPlaceholder = source ? labels.sourceSearchPlaceholder : labels.targetSearchPlaceholder;
|
|
154
|
+
const selectAllLabel = source ? labels.selectAllSource : labels.selectAllTarget;
|
|
155
|
+
const direction = source ? 'to-target' : 'to-source';
|
|
156
|
+
const moveLabel = source ? labels.moveToTarget : labels.moveToSource;
|
|
157
|
+
return _jsxs(PencilSurface, { "aria-labelledby": titleId, as: "section", className: "inkra-transfer__panel", "data-searchable": searchable || undefined, "data-side": side, ref: element => { panelRefs.current[side] = element; }, seed: `inkra-transfer-${generatedId}-${side}`, tabIndex: -1, variant: "paper", children: [_jsxs("header", { className: "inkra-transfer__header", children: [_jsx("span", { className: "inkra-transfer__title", id: titleId, children: typeof panelTitle === 'string' ? _jsx(PencilText, { children: panelTitle }) : panelTitle }), _jsx("span", { "aria-live": "polite", className: "inkra-transfer__count", children: count })] }), _jsx(PencilLine, { "aria-hidden": "true", className: "inkra-transfer__divider", seed: `inkra-transfer-${generatedId}-${side}-header` }), searchable && _jsx(Input, { "aria-label": searchLabel, clearable: true, clearButtonLabel: clearSearchLabel, disabled: disabled, onValueChange: value => setQueries(current => ({ ...current, [side]: value })), placeholder: searchPlaceholder, startIcon: _jsx(PencilIcon, { decorative: true, paths: searchPaths, size: 18, sourceSize: 24 }), type: "search", value: queries[side], variant: "outline" }), _jsx("div", { className: "inkra-transfer__selection-bar", children: _jsx(Checkbox, { checked: allVisibleSelected, disabled: disabled || selectable.length === 0, indeterminate: someVisibleSelected, label: selectAllLabel, onCheckedChange: checked => toggleAll(side, checked), seed: `inkra-transfer-${generatedId}-${side}-all` }) }), _jsx("ul", { className: "inkra-transfer__list", "aria-labelledby": titleId, children: shown.length === 0 ? _jsx("li", { className: "inkra-transfer__empty", children: empty }) : shown.map(item => {
|
|
158
|
+
const itemDisabled = disabled || item.disabled === true;
|
|
159
|
+
const itemSelected = selected[side].has(item.id);
|
|
160
|
+
const copy = renderItem?.(item, { side, selected: itemSelected, disabled: itemDisabled, query: queries[side] }) ?? itemCopy(item);
|
|
161
|
+
return _jsxs("li", { className: "inkra-transfer__item", "data-disabled": itemDisabled || undefined, "data-transfer-item-id": item.id, children: [_jsx(Checkbox, { checked: itemSelected, disabled: itemDisabled, label: copy, onCheckedChange: checked => setItemSelected(side, item.id, checked), ref: element => {
|
|
162
|
+
const key = `${side}:${item.id}`;
|
|
163
|
+
if (element)
|
|
164
|
+
itemRefs.current.set(key, element);
|
|
165
|
+
else
|
|
166
|
+
itemRefs.current.delete(key);
|
|
167
|
+
}, seed: `inkra-transfer-${generatedId}-${side}-${item.id}` }), _jsx(Button, { "aria-label": `${moveLabel}: ${typeof item.label === 'string' || typeof item.label === 'number' ? String(item.label) : item.searchText ?? item.id}`, disabled: itemDisabled, onClick: () => move([item.id], direction, 'single'), seed: `inkra-transfer-${generatedId}-${side}-${item.id}-move`, size: "icon-sm", title: moveLabel, variant: "ghost", children: _jsx(PencilIcon, { className: "inkra-transfer__direction-icon", decorative: true, paths: source ? rightPaths : leftPaths, size: 18, sourceSize: 24 }) })] }, item.id);
|
|
168
|
+
}) })] });
|
|
169
|
+
};
|
|
170
|
+
const sourceBatchKeys = selectableVisible('source').filter(item => selected.source.has(item.id)).map(item => item.id);
|
|
171
|
+
const targetBatchKeys = selectableVisible('target').filter(item => selected.target.has(item.id)).map(item => item.id);
|
|
172
|
+
return _jsxs("div", { ...props, "aria-disabled": disabled || undefined, className: ['inkra-transfer', className].filter(Boolean).join(' '), "data-disabled": disabled || undefined, ref: ref, children: [renderPanel('source'), _jsxs("div", { className: "inkra-transfer__actions", role: "group", "aria-label": labels.batchActions, children: [_jsx(Button, { "aria-label": labels.moveSelectedToTarget, disabled: disabled || sourceBatchKeys.length === 0, onClick: () => move(sourceBatchKeys, 'to-target', 'batch'), seed: `inkra-transfer-${generatedId}-batch-target`, size: "icon", title: labels.moveSelectedToTarget, variant: "outline", children: _jsx(PencilIcon, { className: "inkra-transfer__direction-icon", decorative: true, paths: rightPaths, size: 20, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels.moveSelectedToSource, disabled: disabled || targetBatchKeys.length === 0, onClick: () => move(targetBatchKeys, 'to-source', 'batch'), seed: `inkra-transfer-${generatedId}-batch-source`, size: "icon", title: labels.moveSelectedToSource, variant: "outline", children: _jsx(PencilIcon, { className: "inkra-transfer__direction-icon", decorative: true, paths: leftPaths, size: 20, sourceSize: 24 }) })] }), renderPanel('target')] });
|
|
173
|
+
});
|
|
174
|
+
export { moveTransferKeys, normalizeTargetKeys, splitTransferItems, uniqueTransferItems } from './model.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type TransferDirection = 'to-target' | 'to-source';
|
|
2
|
+
export type TransferSide = 'source' | 'target';
|
|
3
|
+
export interface TransferIdentity {
|
|
4
|
+
id: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function uniqueTransferItems<Item extends TransferIdentity>(items: readonly Item[]): Item[];
|
|
7
|
+
export declare function normalizeTargetKeys<Item extends TransferIdentity>(items: readonly Item[], targetKeys: readonly string[]): string[];
|
|
8
|
+
export declare function splitTransferItems<Item extends TransferIdentity>(items: readonly Item[], targetKeys: readonly string[]): {
|
|
9
|
+
source: Item[];
|
|
10
|
+
target: Item[];
|
|
11
|
+
};
|
|
12
|
+
export declare function moveTransferKeys<Item extends TransferIdentity>(items: readonly Item[], targetKeys: readonly string[], movedKeys: readonly string[], direction: TransferDirection): string[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function uniqueTransferItems(items) {
|
|
2
|
+
const seen = new Set();
|
|
3
|
+
return items.filter(item => {
|
|
4
|
+
if (seen.has(item.id))
|
|
5
|
+
return false;
|
|
6
|
+
seen.add(item.id);
|
|
7
|
+
return true;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function normalizeTargetKeys(items, targetKeys) {
|
|
11
|
+
const uniqueItems = uniqueTransferItems(items);
|
|
12
|
+
const requested = new Set(targetKeys);
|
|
13
|
+
const knownIds = new Set(uniqueItems.map(item => item.id));
|
|
14
|
+
const orderedKnown = uniqueItems.filter(item => requested.has(item.id)).map(item => item.id);
|
|
15
|
+
const unknown = targetKeys.filter((key, index) => !knownIds.has(key) && targetKeys.indexOf(key) === index);
|
|
16
|
+
return [...orderedKnown, ...unknown];
|
|
17
|
+
}
|
|
18
|
+
export function splitTransferItems(items, targetKeys) {
|
|
19
|
+
const target = new Set(targetKeys);
|
|
20
|
+
const uniqueItems = uniqueTransferItems(items);
|
|
21
|
+
return {
|
|
22
|
+
source: uniqueItems.filter(item => !target.has(item.id)),
|
|
23
|
+
target: uniqueItems.filter(item => target.has(item.id)),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function moveTransferKeys(items, targetKeys, movedKeys, direction) {
|
|
27
|
+
const next = new Set(targetKeys);
|
|
28
|
+
if (direction === 'to-target')
|
|
29
|
+
movedKeys.forEach(key => next.add(key));
|
|
30
|
+
else
|
|
31
|
+
movedKeys.forEach(key => next.delete(key));
|
|
32
|
+
return normalizeTargetKeys(items, [...next]);
|
|
33
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Key, Selection, TreeItemProps as AriaTreeItemProps, TreeProps as AriaTreeProps } from 'react-aria-components/Tree';
|
|
2
|
+
import type { CSSProperties, HTMLAttributes, ReactElement, ReactNode, RefAttributes } from 'react';
|
|
3
|
+
export type TreeKey = Key;
|
|
4
|
+
export type TreeSelection = Selection;
|
|
5
|
+
export type TreeRootProps<T extends object = object> = Omit<AriaTreeProps<T>, 'className' | 'style' | 'renderEmptyState'> & {
|
|
6
|
+
className?: string;
|
|
7
|
+
style?: CSSProperties;
|
|
8
|
+
emptyState?: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export type TreeItemProps<T = object> = Omit<AriaTreeItemProps<T>, 'className'> & {
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
export interface TreeItemContentState {
|
|
14
|
+
isExpanded: boolean;
|
|
15
|
+
hasChildItems: boolean;
|
|
16
|
+
isSelected: boolean;
|
|
17
|
+
isDisabled: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface TreeItemContentProps {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
icon?: ReactNode | ((state: TreeItemContentState) => ReactNode);
|
|
23
|
+
disclosureIcon?: ReactNode | ((state: TreeItemContentState) => ReactNode);
|
|
24
|
+
expandLabel?: string;
|
|
25
|
+
collapseLabel?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface TreeItemTitleProps extends HTMLAttributes<HTMLSpanElement> {
|
|
28
|
+
}
|
|
29
|
+
export interface TreeItemDescriptionProps extends HTMLAttributes<HTMLSpanElement> {
|
|
30
|
+
}
|
|
31
|
+
export declare const Tree: {
|
|
32
|
+
Root: <T extends object = object>(props: TreeRootProps<T> & RefAttributes<HTMLDivElement>) => ReactElement | null;
|
|
33
|
+
Item: import("react").ForwardRefExoticComponent<Omit<AriaTreeItemProps<object>, "className"> & {
|
|
34
|
+
className?: string;
|
|
35
|
+
} & RefAttributes<HTMLDivElement>>;
|
|
36
|
+
ItemContent: import("react").ForwardRefExoticComponent<TreeItemContentProps & RefAttributes<Element>>;
|
|
37
|
+
ItemTitle: import("react").ForwardRefExoticComponent<TreeItemTitleProps & RefAttributes<HTMLSpanElement>>;
|
|
38
|
+
ItemDescription: import("react").ForwardRefExoticComponent<TreeItemDescriptionProps & RefAttributes<HTMLSpanElement>>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Tree as AriaTree, TreeItem as AriaTreeItem, TreeItemContent as AriaTreeItemContent, } from 'react-aria-components/Tree';
|
|
3
|
+
import { Button as AriaButton } from 'react-aria-components/Button';
|
|
4
|
+
import { forwardRef } from 'react';
|
|
5
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
6
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
7
|
+
import { PencilText } from '../PencilText.js';
|
|
8
|
+
const chevronIcon = [{ d: 'M 7 10 L 12 15 L 17 10' }];
|
|
9
|
+
function TreeRootInner({ className, emptyState = 'No items', selectionMode = 'single', selectionBehavior = 'toggle', ...props }, ref) {
|
|
10
|
+
return _jsx(AriaTree, { ...props, ref: ref, className: ['inkra-tree', className].filter(Boolean).join(' '), selectionMode: selectionMode, selectionBehavior: selectionBehavior, renderEmptyState: () => _jsx("div", { className: "inkra-tree__empty", children: emptyState }) });
|
|
11
|
+
}
|
|
12
|
+
const TreeRoot = forwardRef(TreeRootInner);
|
|
13
|
+
const TreeItem = forwardRef(function TreeItem({ className, ...props }, ref) {
|
|
14
|
+
return _jsx(AriaTreeItem, { ...props, ref: ref, className: ['inkra-tree__item', className].filter(Boolean).join(' ') });
|
|
15
|
+
});
|
|
16
|
+
const TreeItemContent = forwardRef(function TreeItemContent({ children, className, icon, disclosureIcon, expandLabel = 'Expand item', collapseLabel = 'Collapse item', }, ref) {
|
|
17
|
+
return _jsx(AriaTreeItemContent, { ref: ref, children: ({ hasChildItems, isExpanded, isSelected, isFocusVisible, isDisabled, level, id, }) => {
|
|
18
|
+
const state = { isExpanded, hasChildItems, isSelected, isDisabled };
|
|
19
|
+
const resolvedIcon = typeof icon === 'function' ? icon(state) : icon;
|
|
20
|
+
const resolvedDisclosure = typeof disclosureIcon === 'function' ? disclosureIcon(state) : disclosureIcon;
|
|
21
|
+
return _jsxs("div", { className: ['inkra-tree__item-content', className].filter(Boolean).join(' '), "data-expanded": isExpanded || undefined, "data-selected": isSelected || undefined, "data-disabled": isDisabled || undefined, "data-has-icon": resolvedIcon != null || undefined, style: { '--inkra-tree-level': level }, children: [isSelected && _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-tree__selection", variant: "selection", seed: `tree-${String(id)}-selection` }), isFocusVisible && _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-tree__focus", variant: "outline", seed: `tree-${String(id)}-focus` }), hasChildItems ? _jsx(AriaButton, { slot: "chevron", className: "inkra-tree__disclosure", "aria-label": isExpanded ? collapseLabel : expandLabel, children: ({ isFocusVisible: disclosureFocused }) => _jsxs(_Fragment, { children: [disclosureFocused && _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-tree__disclosure-focus", variant: "outline" }), _jsx("span", { className: "inkra-tree__disclosure-icon", "aria-hidden": "true", children: disclosureIcon === undefined ? _jsx(PencilIcon, { className: "inkra-tree__chevron", paths: chevronIcon, size: 18, sourceSize: 24 }) : resolvedDisclosure })] }) }) : _jsx("span", { className: "inkra-tree__disclosure-spacer", "aria-hidden": "true" }), resolvedIcon != null && _jsx("span", { className: "inkra-tree__icon", "aria-hidden": "true", children: resolvedIcon }), _jsx("span", { className: "inkra-tree__copy", children: children })] });
|
|
22
|
+
} });
|
|
23
|
+
});
|
|
24
|
+
const TreeItemTitle = forwardRef(function TreeItemTitle({ className, children, ...props }, ref) {
|
|
25
|
+
return _jsx("span", { ...props, ref: ref, className: ['inkra-tree__title', className].filter(Boolean).join(' '), children: typeof children === 'string' ? _jsx(PencilText, { children: children }) : children });
|
|
26
|
+
});
|
|
27
|
+
const TreeItemDescription = forwardRef(function TreeItemDescription({ className, children, ...props }, ref) {
|
|
28
|
+
return _jsx("span", { ...props, ref: ref, className: ['inkra-tree__description', className].filter(Boolean).join(' '), children: children });
|
|
29
|
+
});
|
|
30
|
+
export const Tree = {
|
|
31
|
+
Root: TreeRoot,
|
|
32
|
+
Item: TreeItem,
|
|
33
|
+
ItemContent: TreeItemContent,
|
|
34
|
+
ItemTitle: TreeItemTitle,
|
|
35
|
+
ItemDescription: TreeItemDescription,
|
|
36
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import type { TreeSelectBranchSelection, TreeSelectNode, TreeSelectSelectionPropagation } from './model.js';
|
|
3
|
+
export type { TreeSelectBranchSelection, TreeSelectNode, TreeSelectSelectionPropagation, } from './model.js';
|
|
4
|
+
export interface TreeSelectRenderLabelState {
|
|
5
|
+
branch: boolean;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
selected: boolean;
|
|
8
|
+
query: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TreeSelectChangeDetails {
|
|
11
|
+
id?: string;
|
|
12
|
+
node?: TreeSelectNode;
|
|
13
|
+
reason: 'select' | 'clear';
|
|
14
|
+
selected?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface TreeSelectCommonProps extends Omit<HTMLAttributes<HTMLDivElement>, 'defaultValue' | 'onChange' | 'children'> {
|
|
17
|
+
items: readonly TreeSelectNode[];
|
|
18
|
+
branchSelection?: TreeSelectBranchSelection;
|
|
19
|
+
selectionPropagation?: TreeSelectSelectionPropagation;
|
|
20
|
+
renderLabel?: (node: TreeSelectNode, state: TreeSelectRenderLabelState) => ReactNode;
|
|
21
|
+
renderValue?: (nodes: readonly TreeSelectNode[], ids: readonly string[]) => ReactNode;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
searchPlaceholder?: string;
|
|
24
|
+
emptyContent?: ReactNode;
|
|
25
|
+
clearable?: boolean;
|
|
26
|
+
clearLabel?: string;
|
|
27
|
+
treeLabel?: string;
|
|
28
|
+
expandLabel?: string;
|
|
29
|
+
collapseLabel?: string;
|
|
30
|
+
size?: 'sm' | 'md' | 'lg';
|
|
31
|
+
name?: string;
|
|
32
|
+
form?: string;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
readOnly?: boolean;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
invalid?: boolean;
|
|
37
|
+
open?: boolean;
|
|
38
|
+
defaultOpen?: boolean;
|
|
39
|
+
onOpenChange?: (open: boolean) => void;
|
|
40
|
+
expandedIds?: readonly string[];
|
|
41
|
+
defaultExpandedIds?: readonly string[];
|
|
42
|
+
onExpandedIdsChange?: (ids: string[]) => void;
|
|
43
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
44
|
+
align?: 'start' | 'center' | 'end';
|
|
45
|
+
sideOffset?: number;
|
|
46
|
+
collisionPadding?: number;
|
|
47
|
+
}
|
|
48
|
+
export type TreeSelectProps = TreeSelectCommonProps & ({
|
|
49
|
+
multiple?: false;
|
|
50
|
+
value?: string | null;
|
|
51
|
+
defaultValue?: string | null;
|
|
52
|
+
onValueChange?: (value: string | null, details: TreeSelectChangeDetails) => void;
|
|
53
|
+
} | {
|
|
54
|
+
multiple: true;
|
|
55
|
+
value?: readonly string[];
|
|
56
|
+
defaultValue?: readonly string[];
|
|
57
|
+
onValueChange?: (value: string[], details: TreeSelectChangeDetails) => void;
|
|
58
|
+
});
|
|
59
|
+
export declare const TreeSelect: import("react").ForwardRefExoticComponent<TreeSelectProps & import("react").RefAttributes<HTMLDivElement>>;
|