@dreamtree-org/twreact-ui 2.0.8 → 2.1.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/DESIGN.md +3 -0
- package/README.md +9 -4
- package/ai-skills/dreamtree-ui.md +12 -8
- package/dist/ai-schema.json +314 -70
- package/dist/components/blocks/AppShell.d.ts.map +1 -1
- package/dist/components/blocks/ProductCard.d.ts +4 -0
- package/dist/components/blocks/ProductCard.d.ts.map +1 -0
- package/dist/components/blocks/ProductCardParts.d.ts +19 -0
- package/dist/components/blocks/ProductCardParts.d.ts.map +1 -0
- package/dist/components/blocks/StatCard.d.ts.map +1 -1
- package/dist/components/core/Button.d.ts.map +1 -1
- package/dist/components/core/OptionCard.d.ts +14 -0
- package/dist/components/core/OptionCard.d.ts.map +1 -0
- package/dist/components/core/OptionCardGroup.d.ts +2 -0
- package/dist/components/core/OptionCardGroup.d.ts.map +1 -0
- package/dist/components/core/QuantityStepper.d.ts +8 -0
- package/dist/components/core/QuantityStepper.d.ts.map +1 -0
- package/dist/components/utility/ImageGallery.d.ts +4 -0
- package/dist/components/utility/ImageGallery.d.ts.map +1 -0
- package/dist/components/utility/imageGalleryParts.d.ts +55 -0
- package/dist/components/utility/imageGalleryParts.d.ts.map +1 -0
- package/dist/esm/components/blocks/AppShell.js +17 -6
- package/dist/esm/components/blocks/ProductCard.js +122 -0
- package/dist/esm/components/blocks/ProductCardParts.js +128 -0
- package/dist/esm/components/blocks/StatCard.js +20 -5
- package/dist/esm/components/core/Button.js +17 -2
- package/dist/esm/components/core/OptionCard.js +340 -0
- package/dist/esm/components/core/QuantityStepper.js +194 -0
- package/dist/esm/components/utility/ImageGallery.js +150 -0
- package/dist/esm/components/utility/imageGalleryParts.js +380 -0
- package/dist/esm/index.js +4 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +1843 -502
- package/dist/index.js +1847 -501
- package/dist/tokens/tailwind3-preset.cjs +4 -1
- package/dist/tokens/theme.css +3 -0
- package/dist/tokens/tokens.json +7 -0
- package/dist/tokens/vars.css +2 -0
- package/dist/types/blocks.d.ts +61 -1
- package/dist/types/blocks.d.ts.map +1 -1
- package/dist/types/index.d.ts +102 -1
- package/dist/types/index.d.ts.map +1 -1
- package/doc/README.md +5 -1
- package/mcp/catalog.snapshot.json +479 -8
- package/package.json +1 -1
- package/tokens/brand.schema.json +22 -0
- package/tokens/lib.cjs +26 -0
- package/tokens/tokens.json +2 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { forwardRef, useMemo, useRef, useState, useId, useCallback, useContext, useLayoutEffect, useEffect, createContext } from 'react';
|
|
5
|
+
import { Check } from 'lucide-react';
|
|
6
|
+
import { cn } from '../../utils/cn.js';
|
|
7
|
+
import { setRefs } from '../../utils/position.js';
|
|
8
|
+
import { useControllableState } from '../../hooks/useControllableState.js';
|
|
9
|
+
import { PRESS_FEEDBACK } from '../../utils/touchTarget.js';
|
|
10
|
+
|
|
11
|
+
var _excluded = ["value", "defaultValue", "onChange", "multiple", "columns", "label", "name", "disabled", "className", "children"],
|
|
12
|
+
_excluded2 = ["value", "title", "description", "media", "badge", "selected", "onSelect", "disabled", "className", "children", "onClick", "onKeyDown", "onFocus"];
|
|
13
|
+
// useLayoutEffect warns during SSR; fall back to useEffect on the server.
|
|
14
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
15
|
+
var OptionCardGroupContext = /*#__PURE__*/createContext(null);
|
|
16
|
+
// One column on phones, growing with the breakpoint up to `columns`.
|
|
17
|
+
var COLUMN_CLASSES = {
|
|
18
|
+
1: "grid-cols-1",
|
|
19
|
+
2: "grid-cols-1 sm:grid-cols-2",
|
|
20
|
+
3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
21
|
+
4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
|
|
22
|
+
};
|
|
23
|
+
var NEXT_KEYS = {
|
|
24
|
+
ArrowRight: 1,
|
|
25
|
+
ArrowDown: 1,
|
|
26
|
+
ArrowLeft: -1,
|
|
27
|
+
ArrowUp: -1
|
|
28
|
+
};
|
|
29
|
+
function isRtl(el) {
|
|
30
|
+
var dirEl = el.closest("[dir]");
|
|
31
|
+
if (dirEl) return dirEl.getAttribute("dir") === "rtl";
|
|
32
|
+
return typeof window !== "undefined" && window.getComputedStyle(el).direction === "rtl";
|
|
33
|
+
}
|
|
34
|
+
function toArray(value) {
|
|
35
|
+
if (Array.isArray(value)) return value;
|
|
36
|
+
return value === undefined || value === null ? [] : [value];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* OptionCardGroup: lays out `OptionCard`s in a responsive grid and gives them
|
|
40
|
+
* radiogroup semantics (or checkbox-group semantics with `multiple`).
|
|
41
|
+
*/
|
|
42
|
+
var OptionCardGroup = /*#__PURE__*/forwardRef(function OptionCardGroup(_ref, _ref2) {
|
|
43
|
+
var valueProp = _ref.value,
|
|
44
|
+
defaultValue = _ref.defaultValue,
|
|
45
|
+
onChange = _ref.onChange,
|
|
46
|
+
_ref$multiple = _ref.multiple,
|
|
47
|
+
multiple = _ref$multiple === void 0 ? false : _ref$multiple,
|
|
48
|
+
_ref$columns = _ref.columns,
|
|
49
|
+
columns = _ref$columns === void 0 ? 3 : _ref$columns,
|
|
50
|
+
label = _ref.label,
|
|
51
|
+
name = _ref.name,
|
|
52
|
+
_ref$disabled = _ref.disabled,
|
|
53
|
+
disabled = _ref$disabled === void 0 ? false : _ref$disabled,
|
|
54
|
+
className = _ref.className,
|
|
55
|
+
children = _ref.children,
|
|
56
|
+
rest = _objectWithoutProperties(_ref, _excluded);
|
|
57
|
+
var _useControllableState = useControllableState({
|
|
58
|
+
value: valueProp,
|
|
59
|
+
defaultValue: defaultValue,
|
|
60
|
+
onChange: onChange
|
|
61
|
+
}),
|
|
62
|
+
_useControllableState2 = _slicedToArray(_useControllableState, 2),
|
|
63
|
+
value = _useControllableState2[0],
|
|
64
|
+
setValue = _useControllableState2[1];
|
|
65
|
+
var selected = useMemo(function () {
|
|
66
|
+
return multiple ? toArray(value) : value;
|
|
67
|
+
}, [multiple, value]);
|
|
68
|
+
var rootRef = useRef(null);
|
|
69
|
+
// DOM node -> { value, disabled } for every card in this group.
|
|
70
|
+
var itemsRef = useRef(new Map());
|
|
71
|
+
var _useState = useState(undefined),
|
|
72
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
73
|
+
activeValue = _useState2[0],
|
|
74
|
+
setActiveValue = _useState2[1];
|
|
75
|
+
var _useState3 = useState({
|
|
76
|
+
ready: false,
|
|
77
|
+
value: undefined
|
|
78
|
+
}),
|
|
79
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
80
|
+
tabStop = _useState4[0],
|
|
81
|
+
setTabStop = _useState4[1];
|
|
82
|
+
var labelId = useId();
|
|
83
|
+
var isSelected = useCallback(function (v) {
|
|
84
|
+
return multiple ? selected.some(function (s) {
|
|
85
|
+
return Object.is(s, v);
|
|
86
|
+
}) : Object.is(selected, v);
|
|
87
|
+
}, [multiple, selected]);
|
|
88
|
+
// Cards in DOM order, excluding cards of nested groups.
|
|
89
|
+
var getItems = useCallback(function () {
|
|
90
|
+
var root = rootRef.current;
|
|
91
|
+
if (!root) return [];
|
|
92
|
+
return Array.from(root.querySelectorAll("[data-option-card]")).filter(function (node) {
|
|
93
|
+
return node.closest("[data-option-card-group]") === root;
|
|
94
|
+
}).map(function (node) {
|
|
95
|
+
return _objectSpread2({
|
|
96
|
+
node: node
|
|
97
|
+
}, itemsRef.current.get(node) || {});
|
|
98
|
+
}).filter(function (item) {
|
|
99
|
+
return item.node && "value" in item;
|
|
100
|
+
});
|
|
101
|
+
}, []);
|
|
102
|
+
// Roving tabindex: the last focused card, else the first selected card, else
|
|
103
|
+
// the first enabled card. Recomputed after every render (cards may change).
|
|
104
|
+
useIsomorphicLayoutEffect(function () {
|
|
105
|
+
var enabled = getItems().filter(function (item) {
|
|
106
|
+
return !item.disabled;
|
|
107
|
+
});
|
|
108
|
+
var pick = enabled.find(function (item) {
|
|
109
|
+
return activeValue !== undefined && Object.is(item.value, activeValue);
|
|
110
|
+
}) || enabled.find(function (item) {
|
|
111
|
+
return isSelected(item.value);
|
|
112
|
+
}) || enabled[0];
|
|
113
|
+
var next = pick ? pick.value : undefined;
|
|
114
|
+
setTabStop(function (prev) {
|
|
115
|
+
return prev.ready && Object.is(prev.value, next) ? prev : {
|
|
116
|
+
ready: true,
|
|
117
|
+
value: next
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
var toggle = useCallback(function (v) {
|
|
122
|
+
if (disabled) return;
|
|
123
|
+
if (multiple) {
|
|
124
|
+
var next = isSelected(v) ? selected.filter(function (s) {
|
|
125
|
+
return !Object.is(s, v);
|
|
126
|
+
}) : [].concat(_toConsumableArray(selected), [v]);
|
|
127
|
+
setValue(next);
|
|
128
|
+
} else if (!Object.is(selected, v)) {
|
|
129
|
+
setValue(v);
|
|
130
|
+
}
|
|
131
|
+
}, [disabled, multiple, isSelected, selected, setValue]);
|
|
132
|
+
var onItemKeyDown = useCallback(function (event, v) {
|
|
133
|
+
var key = event.key;
|
|
134
|
+
var items = getItems().filter(function (item) {
|
|
135
|
+
return !item.disabled;
|
|
136
|
+
});
|
|
137
|
+
if (!items.length) return false;
|
|
138
|
+
var index = items.findIndex(function (item) {
|
|
139
|
+
return Object.is(item.value, v);
|
|
140
|
+
});
|
|
141
|
+
var target = -1;
|
|
142
|
+
if (key in NEXT_KEYS) {
|
|
143
|
+
var step = NEXT_KEYS[key];
|
|
144
|
+
if ((key === "ArrowLeft" || key === "ArrowRight") && isRtl(event.currentTarget)) step = -step;
|
|
145
|
+
var from = index < 0 ? step > 0 ? -1 : items.length : index;
|
|
146
|
+
target = ((from + step) % items.length + items.length) % items.length;
|
|
147
|
+
} else if (key === "Home") {
|
|
148
|
+
target = 0;
|
|
149
|
+
} else if (key === "End") {
|
|
150
|
+
target = items.length - 1;
|
|
151
|
+
} else {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
var item = items[target];
|
|
156
|
+
item.node.focus();
|
|
157
|
+
setActiveValue(item.value);
|
|
158
|
+
// Radios: selection follows focus. Checkboxes: arrows only move focus.
|
|
159
|
+
if (!multiple) toggle(item.value);
|
|
160
|
+
return true;
|
|
161
|
+
}, [getItems, multiple, toggle]);
|
|
162
|
+
var ctx = useMemo(function () {
|
|
163
|
+
return {
|
|
164
|
+
multiple: multiple,
|
|
165
|
+
groupDisabled: disabled,
|
|
166
|
+
isSelected: isSelected,
|
|
167
|
+
toggle: toggle,
|
|
168
|
+
itemsRef: itemsRef,
|
|
169
|
+
tabStop: tabStop,
|
|
170
|
+
setActiveValue: setActiveValue,
|
|
171
|
+
onItemKeyDown: onItemKeyDown
|
|
172
|
+
};
|
|
173
|
+
}, [multiple, disabled, isSelected, toggle, tabStop, onItemKeyDown]);
|
|
174
|
+
var hasLabel = label !== undefined && label !== null && label !== false;
|
|
175
|
+
var hiddenValues = multiple ? selected : toArray(selected);
|
|
176
|
+
return jsxs("div", _objectSpread2(_objectSpread2({
|
|
177
|
+
ref: function ref(node) {
|
|
178
|
+
return setRefs(node, rootRef, _ref2);
|
|
179
|
+
},
|
|
180
|
+
role: multiple ? "group" : "radiogroup",
|
|
181
|
+
"aria-labelledby": hasLabel && !rest["aria-label"] ? labelId : undefined,
|
|
182
|
+
"aria-disabled": disabled || undefined,
|
|
183
|
+
"data-option-card-group": "",
|
|
184
|
+
className: cn("flex flex-col gap-2", disabled && "cursor-not-allowed opacity-50", className)
|
|
185
|
+
}, rest), {}, {
|
|
186
|
+
children: [hasLabel && jsx("div", {
|
|
187
|
+
id: labelId,
|
|
188
|
+
className: "text-sm font-medium text-foreground",
|
|
189
|
+
children: label
|
|
190
|
+
}), jsx("div", {
|
|
191
|
+
className: cn("grid gap-3", COLUMN_CLASSES[columns] || COLUMN_CLASSES[3]),
|
|
192
|
+
children: jsx(OptionCardGroupContext.Provider, {
|
|
193
|
+
value: ctx,
|
|
194
|
+
children: children
|
|
195
|
+
})
|
|
196
|
+
}), name && hiddenValues.map(function (v) {
|
|
197
|
+
return jsx("input", {
|
|
198
|
+
type: "hidden",
|
|
199
|
+
name: name,
|
|
200
|
+
value: v !== null && v !== void 0 ? v : ""
|
|
201
|
+
}, String(v));
|
|
202
|
+
})]
|
|
203
|
+
}));
|
|
204
|
+
});
|
|
205
|
+
OptionCardGroup.displayName = "OptionCardGroup";
|
|
206
|
+
/**
|
|
207
|
+
* OptionCard: a selectable choice tile (title, description, optional media and
|
|
208
|
+
* badge). Inside an `OptionCardGroup` it is a radio (or a checkbox with
|
|
209
|
+
* `multiple`); on its own it is a toggle button with `aria-pressed`.
|
|
210
|
+
*/
|
|
211
|
+
var OptionCard = /*#__PURE__*/forwardRef(function OptionCard(_ref3, _ref4) {
|
|
212
|
+
var value = _ref3.value,
|
|
213
|
+
title = _ref3.title,
|
|
214
|
+
description = _ref3.description,
|
|
215
|
+
media = _ref3.media,
|
|
216
|
+
badge = _ref3.badge,
|
|
217
|
+
selectedProp = _ref3.selected,
|
|
218
|
+
onSelect = _ref3.onSelect,
|
|
219
|
+
_ref3$disabled = _ref3.disabled,
|
|
220
|
+
disabledProp = _ref3$disabled === void 0 ? false : _ref3$disabled,
|
|
221
|
+
className = _ref3.className,
|
|
222
|
+
children = _ref3.children,
|
|
223
|
+
onClick = _ref3.onClick,
|
|
224
|
+
onKeyDown = _ref3.onKeyDown,
|
|
225
|
+
onFocus = _ref3.onFocus,
|
|
226
|
+
rest = _objectWithoutProperties(_ref3, _excluded2);
|
|
227
|
+
var group = useContext(OptionCardGroupContext);
|
|
228
|
+
var nodeRef = useRef(null);
|
|
229
|
+
var baseId = useId();
|
|
230
|
+
var titleId = "".concat(baseId, "-title");
|
|
231
|
+
var descId = "".concat(baseId, "-desc");
|
|
232
|
+
var disabled = Boolean(disabledProp) || Boolean(group === null || group === void 0 ? void 0 : group.groupDisabled);
|
|
233
|
+
var selected = group ? group.isSelected(value) : Boolean(selectedProp);
|
|
234
|
+
// Register this card with its group so keyboard navigation can map DOM order to values.
|
|
235
|
+
useIsomorphicLayoutEffect(function () {
|
|
236
|
+
if (!group) return undefined;
|
|
237
|
+
var node = nodeRef.current;
|
|
238
|
+
var items = group.itemsRef.current;
|
|
239
|
+
if (node) items.set(node, {
|
|
240
|
+
value: value,
|
|
241
|
+
disabled: disabled
|
|
242
|
+
});
|
|
243
|
+
return function () {
|
|
244
|
+
if (node) items["delete"](node);
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
var role = "button";
|
|
248
|
+
var tabIndex = disabled ? -1 : 0;
|
|
249
|
+
if (group) {
|
|
250
|
+
role = group.multiple ? "checkbox" : "radio";
|
|
251
|
+
if (disabled) tabIndex = -1;else if (group.tabStop.ready) tabIndex = Object.is(group.tabStop.value, value) ? 0 : -1;
|
|
252
|
+
// Before the first layout pass (SSR): selected cards are the tab stop.
|
|
253
|
+
else tabIndex = selected ? 0 : -1;
|
|
254
|
+
}
|
|
255
|
+
var activate = function activate(event) {
|
|
256
|
+
if (disabled) return;
|
|
257
|
+
if (group) {
|
|
258
|
+
group.setActiveValue(value);
|
|
259
|
+
group.toggle(value);
|
|
260
|
+
}
|
|
261
|
+
onSelect === null || onSelect === void 0 || onSelect(value, event);
|
|
262
|
+
};
|
|
263
|
+
var handleClick = function handleClick(event) {
|
|
264
|
+
onClick === null || onClick === void 0 || onClick(event);
|
|
265
|
+
if (event.defaultPrevented) return;
|
|
266
|
+
activate(event);
|
|
267
|
+
};
|
|
268
|
+
var handleKeyDown = function handleKeyDown(event) {
|
|
269
|
+
onKeyDown === null || onKeyDown === void 0 || onKeyDown(event);
|
|
270
|
+
if (event.defaultPrevented || event.target !== event.currentTarget || disabled) return;
|
|
271
|
+
if (event.key === " " || event.key === "Enter") {
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
activate(event);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (group) group.onItemKeyDown(event, value);
|
|
277
|
+
};
|
|
278
|
+
var handleFocus = function handleFocus(event) {
|
|
279
|
+
onFocus === null || onFocus === void 0 || onFocus(event);
|
|
280
|
+
if (group && !disabled && event.target === event.currentTarget) group.setActiveValue(value);
|
|
281
|
+
};
|
|
282
|
+
var ariaState = role === "button" ? {
|
|
283
|
+
"aria-pressed": selected
|
|
284
|
+
} : {
|
|
285
|
+
"aria-checked": selected
|
|
286
|
+
};
|
|
287
|
+
var isCheckbox = group === null || group === void 0 ? void 0 : group.multiple;
|
|
288
|
+
return jsxs("div", _objectSpread2(_objectSpread2(_objectSpread2({
|
|
289
|
+
ref: function ref(node) {
|
|
290
|
+
return setRefs(node, nodeRef, _ref4);
|
|
291
|
+
},
|
|
292
|
+
role: role,
|
|
293
|
+
tabIndex: tabIndex
|
|
294
|
+
}, ariaState), {}, {
|
|
295
|
+
"aria-disabled": disabled || undefined,
|
|
296
|
+
"aria-labelledby": title ? titleId : undefined,
|
|
297
|
+
"aria-describedby": description ? descId : undefined,
|
|
298
|
+
"data-option-card": "",
|
|
299
|
+
"data-state": selected ? "checked" : "unchecked",
|
|
300
|
+
"data-disabled": disabled ? "" : undefined,
|
|
301
|
+
onClick: handleClick,
|
|
302
|
+
onKeyDown: handleKeyDown,
|
|
303
|
+
onFocus: handleFocus,
|
|
304
|
+
className: cn("relative flex min-h-11 w-full flex-col gap-3 rounded-card border bg-surface p-4 text-left outline-none select-none", "focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-background", PRESS_FEEDBACK, "transition-[border-color,box-shadow,background-color,transform] duration-fast ease-standard motion-reduce:transition-none", selected ? "border-primary-600 bg-primary-50 shadow-sm ring-1 ring-primary-600 dark:border-primary-400 dark:bg-primary-900/20 dark:ring-primary-400" : "border-line", disabled ? "cursor-not-allowed opacity-50 active:scale-100" : cn("cursor-pointer hover:shadow-md", !selected && "hover:border-primary-300 dark:hover:border-primary-700"), className)
|
|
305
|
+
}, rest), {}, {
|
|
306
|
+
children: [jsx("span", {
|
|
307
|
+
"aria-hidden": "true",
|
|
308
|
+
"data-option-card-indicator": "",
|
|
309
|
+
className: cn("absolute right-3 top-3 inline-flex h-5 w-5 items-center justify-center border", isCheckbox ? "rounded-md" : "rounded-full", "transition-colors duration-fast motion-reduce:transition-none", selected ? "border-primary-600 bg-primary-600 text-white dark:border-primary-500 dark:bg-primary-500" : "border-muted/40 bg-surface"),
|
|
310
|
+
children: selected && jsx(Check, {
|
|
311
|
+
className: "h-3.5 w-3.5",
|
|
312
|
+
strokeWidth: 3
|
|
313
|
+
})
|
|
314
|
+
}), media && jsx("div", {
|
|
315
|
+
className: "overflow-hidden rounded-md",
|
|
316
|
+
children: media
|
|
317
|
+
}), jsxs("div", {
|
|
318
|
+
className: "flex flex-col gap-1 pr-7",
|
|
319
|
+
children: [jsxs("div", {
|
|
320
|
+
className: "flex flex-wrap items-center gap-2",
|
|
321
|
+
children: [title && jsx("span", {
|
|
322
|
+
id: titleId,
|
|
323
|
+
className: "text-sm font-semibold text-foreground",
|
|
324
|
+
children: title
|
|
325
|
+
}), badge !== undefined && badge !== null && badge !== false && jsx("span", {
|
|
326
|
+
className: "inline-flex items-center rounded-full bg-primary-50 px-2 py-0.5 text-xs font-medium text-primary-700 dark:bg-primary-900/40 dark:text-primary-300",
|
|
327
|
+
children: badge
|
|
328
|
+
})]
|
|
329
|
+
}), description && jsx("span", {
|
|
330
|
+
id: descId,
|
|
331
|
+
className: "text-sm text-muted",
|
|
332
|
+
children: description
|
|
333
|
+
})]
|
|
334
|
+
}), children]
|
|
335
|
+
}));
|
|
336
|
+
});
|
|
337
|
+
OptionCard.displayName = "OptionCard";
|
|
338
|
+
OptionCard.Group = OptionCardGroup;
|
|
339
|
+
|
|
340
|
+
export { OptionCardGroup, OptionCard as default };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { forwardRef, useId } from 'react';
|
|
5
|
+
import { Minus, Loader2, Plus } from 'lucide-react';
|
|
6
|
+
import { cn } from '../../utils/cn.js';
|
|
7
|
+
import { useControllableState } from '../../hooks/useControllableState.js';
|
|
8
|
+
import { PRESS_FEEDBACK, TOUCH_TARGET } from '../../utils/touchTarget.js';
|
|
9
|
+
|
|
10
|
+
var _excluded = ["value", "defaultValue", "onChange", "min", "max", "step", "size", "disabled", "loading", "label", "hideLabel", "formatValue", "decrementLabel", "incrementLabel", "name", "className", "aria-label"];
|
|
11
|
+
// Button is square; the value cell is at least as wide as a button.
|
|
12
|
+
var SIZE_CLASSES = {
|
|
13
|
+
sm: {
|
|
14
|
+
button: "h-8 w-8",
|
|
15
|
+
value: "h-8 min-w-8 px-1 text-sm",
|
|
16
|
+
icon: "h-3.5 w-3.5"
|
|
17
|
+
},
|
|
18
|
+
md: {
|
|
19
|
+
button: "h-10 w-10",
|
|
20
|
+
value: "h-10 min-w-10 px-2 text-sm",
|
|
21
|
+
icon: "h-4 w-4"
|
|
22
|
+
},
|
|
23
|
+
lg: {
|
|
24
|
+
button: "h-12 w-12",
|
|
25
|
+
value: "h-12 min-w-12 px-3 text-base",
|
|
26
|
+
icon: "h-5 w-5"
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
// PageUp / PageDown move this many steps.
|
|
30
|
+
var PAGE_STEPS = 10;
|
|
31
|
+
// Decimal places of a number, so 0.1 + 0.2 lands on 0.3 instead of 0.30000000000000004.
|
|
32
|
+
function decimals(n) {
|
|
33
|
+
var s = String(n);
|
|
34
|
+
var i = s.indexOf(".");
|
|
35
|
+
return i < 0 ? 0 : s.length - i - 1;
|
|
36
|
+
}
|
|
37
|
+
function clamp(n, min, max) {
|
|
38
|
+
return Math.min(max, Math.max(min, n));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* QuantityStepper: a − / value / + control for cart lines and buy boxes.
|
|
42
|
+
* The value is a `role="spinbutton"`; the buttons step it and disable at the bounds.
|
|
43
|
+
*/
|
|
44
|
+
var QuantityStepper = /*#__PURE__*/forwardRef(function QuantityStepper(_ref, ref) {
|
|
45
|
+
var valueProp = _ref.value,
|
|
46
|
+
defaultValue = _ref.defaultValue,
|
|
47
|
+
onChange = _ref.onChange,
|
|
48
|
+
_ref$min = _ref.min,
|
|
49
|
+
min = _ref$min === void 0 ? 0 : _ref$min,
|
|
50
|
+
_ref$max = _ref.max,
|
|
51
|
+
max = _ref$max === void 0 ? Infinity : _ref$max,
|
|
52
|
+
_ref$step = _ref.step,
|
|
53
|
+
step = _ref$step === void 0 ? 1 : _ref$step,
|
|
54
|
+
_ref$size = _ref.size,
|
|
55
|
+
size = _ref$size === void 0 ? "md" : _ref$size,
|
|
56
|
+
_ref$disabled = _ref.disabled,
|
|
57
|
+
disabled = _ref$disabled === void 0 ? false : _ref$disabled,
|
|
58
|
+
_ref$loading = _ref.loading,
|
|
59
|
+
loading = _ref$loading === void 0 ? false : _ref$loading,
|
|
60
|
+
label = _ref.label,
|
|
61
|
+
_ref$hideLabel = _ref.hideLabel,
|
|
62
|
+
hideLabel = _ref$hideLabel === void 0 ? false : _ref$hideLabel,
|
|
63
|
+
formatValue = _ref.formatValue,
|
|
64
|
+
_ref$decrementLabel = _ref.decrementLabel,
|
|
65
|
+
decrementLabel = _ref$decrementLabel === void 0 ? "Decrease quantity" : _ref$decrementLabel,
|
|
66
|
+
_ref$incrementLabel = _ref.incrementLabel,
|
|
67
|
+
incrementLabel = _ref$incrementLabel === void 0 ? "Increase quantity" : _ref$incrementLabel,
|
|
68
|
+
name = _ref.name,
|
|
69
|
+
className = _ref.className,
|
|
70
|
+
ariaLabel = _ref["aria-label"],
|
|
71
|
+
rest = _objectWithoutProperties(_ref, _excluded);
|
|
72
|
+
var labelId = "quantity-stepper-label-".concat(useId());
|
|
73
|
+
var safeStep = step > 0 ? step : 1;
|
|
74
|
+
var precision = Math.max(decimals(safeStep), decimals(Number.isFinite(min) ? min : 0));
|
|
75
|
+
var initial = defaultValue !== null && defaultValue !== void 0 ? defaultValue : Number.isFinite(min) ? min : 0;
|
|
76
|
+
var _useControllableState = useControllableState({
|
|
77
|
+
value: valueProp,
|
|
78
|
+
defaultValue: initial,
|
|
79
|
+
onChange: onChange
|
|
80
|
+
}),
|
|
81
|
+
_useControllableState2 = _slicedToArray(_useControllableState, 2),
|
|
82
|
+
rawValue = _useControllableState2[0],
|
|
83
|
+
setValue = _useControllableState2[1];
|
|
84
|
+
var value = clamp(Number(rawValue) || 0, min, max);
|
|
85
|
+
var inactive = disabled || loading;
|
|
86
|
+
var atMin = value <= min;
|
|
87
|
+
var atMax = value >= max;
|
|
88
|
+
var commit = function commit(next) {
|
|
89
|
+
if (inactive) return;
|
|
90
|
+
var rounded = Number(clamp(next, min, max).toFixed(precision));
|
|
91
|
+
if (rounded !== value) setValue(rounded);
|
|
92
|
+
};
|
|
93
|
+
var onKeyDown = function onKeyDown(event) {
|
|
94
|
+
var next;
|
|
95
|
+
switch (event.key) {
|
|
96
|
+
case "ArrowUp":
|
|
97
|
+
next = value + safeStep;
|
|
98
|
+
break;
|
|
99
|
+
case "ArrowDown":
|
|
100
|
+
next = value - safeStep;
|
|
101
|
+
break;
|
|
102
|
+
case "PageUp":
|
|
103
|
+
next = value + safeStep * PAGE_STEPS;
|
|
104
|
+
break;
|
|
105
|
+
case "PageDown":
|
|
106
|
+
next = value - safeStep * PAGE_STEPS;
|
|
107
|
+
break;
|
|
108
|
+
case "Home":
|
|
109
|
+
if (!Number.isFinite(min)) return;
|
|
110
|
+
next = min;
|
|
111
|
+
break;
|
|
112
|
+
case "End":
|
|
113
|
+
if (!Number.isFinite(max)) return;
|
|
114
|
+
next = max;
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
commit(next);
|
|
121
|
+
};
|
|
122
|
+
var sizes = SIZE_CLASSES[size] || SIZE_CLASSES.md;
|
|
123
|
+
var display = formatValue ? formatValue(value) : value;
|
|
124
|
+
var valueText = formatValue && (typeof display === "string" || typeof display === "number") ? String(display) : undefined;
|
|
125
|
+
var buttonClass = cn("inline-flex shrink-0 items-center justify-center text-foreground outline-none select-none", "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500", "enabled:hover:bg-line disabled:cursor-not-allowed disabled:text-muted", "motion-reduce:transition-none", TOUCH_TARGET, PRESS_FEEDBACK, sizes.button);
|
|
126
|
+
return jsxs("div", _objectSpread2(_objectSpread2({
|
|
127
|
+
ref: ref,
|
|
128
|
+
className: cn("inline-flex flex-col gap-1", disabled && "opacity-50", className)
|
|
129
|
+
}, rest), {}, {
|
|
130
|
+
children: [label && jsx("span", {
|
|
131
|
+
id: labelId,
|
|
132
|
+
className: cn("text-sm font-medium text-foreground", hideLabel && "sr-only"),
|
|
133
|
+
children: label
|
|
134
|
+
}), jsxs("div", {
|
|
135
|
+
className: cn("inline-flex items-stretch overflow-hidden rounded-lg border border-line bg-surface", disabled && "cursor-not-allowed"),
|
|
136
|
+
"aria-busy": loading || undefined,
|
|
137
|
+
children: [jsx("button", {
|
|
138
|
+
type: "button",
|
|
139
|
+
"aria-label": decrementLabel,
|
|
140
|
+
disabled: inactive || atMin,
|
|
141
|
+
tabIndex: -1,
|
|
142
|
+
onClick: function onClick() {
|
|
143
|
+
return commit(value - safeStep);
|
|
144
|
+
},
|
|
145
|
+
className: cn(buttonClass, "rounded-l-lg"),
|
|
146
|
+
children: jsx(Minus, {
|
|
147
|
+
"aria-hidden": "true",
|
|
148
|
+
className: sizes.icon
|
|
149
|
+
})
|
|
150
|
+
}), jsxs("div", {
|
|
151
|
+
role: "spinbutton",
|
|
152
|
+
tabIndex: disabled ? -1 : 0,
|
|
153
|
+
"aria-valuenow": value,
|
|
154
|
+
"aria-valuemin": Number.isFinite(min) ? min : undefined,
|
|
155
|
+
"aria-valuemax": Number.isFinite(max) ? max : undefined,
|
|
156
|
+
"aria-valuetext": valueText,
|
|
157
|
+
"aria-disabled": disabled || undefined,
|
|
158
|
+
"aria-readonly": loading || undefined,
|
|
159
|
+
"aria-labelledby": label ? labelId : undefined,
|
|
160
|
+
"aria-label": label ? undefined : ariaLabel,
|
|
161
|
+
onKeyDown: onKeyDown,
|
|
162
|
+
className: cn("relative inline-flex items-center justify-center border-x border-line font-medium tabular-nums text-foreground outline-none", "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500", "[@media(pointer:coarse)]:min-h-11", sizes.value),
|
|
163
|
+
children: [jsx("span", {
|
|
164
|
+
className: cn(loading && "invisible"),
|
|
165
|
+
children: display
|
|
166
|
+
}), loading && jsx(Loader2, {
|
|
167
|
+
"aria-hidden": "true",
|
|
168
|
+
"data-quantity-stepper-spinner": "",
|
|
169
|
+
className: cn("absolute animate-spin text-muted motion-reduce:animate-none", sizes.icon)
|
|
170
|
+
})]
|
|
171
|
+
}), jsx("button", {
|
|
172
|
+
type: "button",
|
|
173
|
+
"aria-label": incrementLabel,
|
|
174
|
+
disabled: inactive || atMax,
|
|
175
|
+
tabIndex: -1,
|
|
176
|
+
onClick: function onClick() {
|
|
177
|
+
return commit(value + safeStep);
|
|
178
|
+
},
|
|
179
|
+
className: cn(buttonClass, "rounded-r-lg"),
|
|
180
|
+
children: jsx(Plus, {
|
|
181
|
+
"aria-hidden": "true",
|
|
182
|
+
className: sizes.icon
|
|
183
|
+
})
|
|
184
|
+
})]
|
|
185
|
+
}), name && jsx("input", {
|
|
186
|
+
type: "hidden",
|
|
187
|
+
name: name,
|
|
188
|
+
value: value
|
|
189
|
+
})]
|
|
190
|
+
}));
|
|
191
|
+
});
|
|
192
|
+
QuantityStepper.displayName = "QuantityStepper";
|
|
193
|
+
|
|
194
|
+
export { QuantityStepper as default };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { cn } from '../../utils/cn.js';
|
|
6
|
+
import { useControllableState } from '../../hooks/useControllableState.js';
|
|
7
|
+
import Dialog from '../feedback/Dialog.js';
|
|
8
|
+
import { PlaceholderBox, ratioClass, Viewer, Mosaic, Thumbnails } from './imageGalleryParts.js';
|
|
9
|
+
|
|
10
|
+
var _excluded = ["images", "layout", "ratio", "index", "defaultIndex", "onIndexChange", "zoom", "lightbox", "placeholder", "showCounter", "className", "aria-label"];
|
|
11
|
+
var clampIndex = function clampIndex(i, count) {
|
|
12
|
+
return Math.min(Math.max(Number(i) || 0, 0), Math.max(count - 1, 0));
|
|
13
|
+
};
|
|
14
|
+
var ImageGallery = /*#__PURE__*/React.forwardRef(function ImageGallery(props, ref) {
|
|
15
|
+
var _props$images = props.images,
|
|
16
|
+
images = _props$images === void 0 ? [] : _props$images,
|
|
17
|
+
_props$layout = props.layout,
|
|
18
|
+
layout = _props$layout === void 0 ? "thumbs-below" : _props$layout,
|
|
19
|
+
_props$ratio = props.ratio,
|
|
20
|
+
ratio = _props$ratio === void 0 ? "4:3" : _props$ratio,
|
|
21
|
+
indexProp = props.index,
|
|
22
|
+
_props$defaultIndex = props.defaultIndex,
|
|
23
|
+
defaultIndex = _props$defaultIndex === void 0 ? 0 : _props$defaultIndex,
|
|
24
|
+
onIndexChange = props.onIndexChange,
|
|
25
|
+
_props$zoom = props.zoom,
|
|
26
|
+
zoom = _props$zoom === void 0 ? false : _props$zoom,
|
|
27
|
+
_props$lightbox = props.lightbox,
|
|
28
|
+
lightbox = _props$lightbox === void 0 ? false : _props$lightbox,
|
|
29
|
+
placeholder = props.placeholder,
|
|
30
|
+
_props$showCounter = props.showCounter,
|
|
31
|
+
showCounter = _props$showCounter === void 0 ? true : _props$showCounter,
|
|
32
|
+
className = props.className,
|
|
33
|
+
_props$ariaLabel = props["aria-label"],
|
|
34
|
+
ariaLabel = _props$ariaLabel === void 0 ? "Image gallery" : _props$ariaLabel,
|
|
35
|
+
rest = _objectWithoutProperties(props, _excluded);
|
|
36
|
+
var list = Array.isArray(images) ? images : [];
|
|
37
|
+
var count = list.length;
|
|
38
|
+
var _useControllableState = useControllableState({
|
|
39
|
+
value: indexProp,
|
|
40
|
+
defaultValue: defaultIndex,
|
|
41
|
+
onChange: onIndexChange
|
|
42
|
+
}),
|
|
43
|
+
_useControllableState2 = _slicedToArray(_useControllableState, 2),
|
|
44
|
+
rawIndex = _useControllableState2[0],
|
|
45
|
+
setIndex = _useControllableState2[1];
|
|
46
|
+
var index = clampIndex(rawIndex, count);
|
|
47
|
+
var _React$useState = React.useState(false),
|
|
48
|
+
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
49
|
+
open = _React$useState2[0],
|
|
50
|
+
setOpen = _React$useState2[1];
|
|
51
|
+
// Wraps around at both ends; a no-op when the target is already current.
|
|
52
|
+
var select = React.useCallback(function (next) {
|
|
53
|
+
if (count === 0) return;
|
|
54
|
+
var target = (next % count + count) % count;
|
|
55
|
+
if (target !== index) setIndex(target);
|
|
56
|
+
}, [count, index, setIndex]);
|
|
57
|
+
if (count === 0) {
|
|
58
|
+
return jsx("div", _objectSpread2(_objectSpread2({
|
|
59
|
+
ref: ref,
|
|
60
|
+
className: cn("w-full", className),
|
|
61
|
+
"data-layout": layout
|
|
62
|
+
}, rest), {}, {
|
|
63
|
+
children: jsx("div", {
|
|
64
|
+
role: "img",
|
|
65
|
+
"aria-label": "No images",
|
|
66
|
+
className: cn("overflow-hidden rounded-xl border border-line", ratioClass(ratio)),
|
|
67
|
+
children: jsx(PlaceholderBox, {
|
|
68
|
+
children: placeholder
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
var shared = {
|
|
74
|
+
images: list,
|
|
75
|
+
index: index,
|
|
76
|
+
onSelect: select,
|
|
77
|
+
placeholder: placeholder
|
|
78
|
+
};
|
|
79
|
+
var viewer = jsx(Viewer, _objectSpread2(_objectSpread2({}, shared), {}, {
|
|
80
|
+
ratio: ratio,
|
|
81
|
+
zoom: zoom,
|
|
82
|
+
showCounter: showCounter,
|
|
83
|
+
label: ariaLabel,
|
|
84
|
+
onExpand: lightbox ? function () {
|
|
85
|
+
return setOpen(true);
|
|
86
|
+
} : undefined
|
|
87
|
+
}));
|
|
88
|
+
var body;
|
|
89
|
+
if (layout === "mosaic") {
|
|
90
|
+
var activate = function activate(i) {
|
|
91
|
+
select(i);
|
|
92
|
+
if (lightbox) setOpen(true);
|
|
93
|
+
};
|
|
94
|
+
body = jsx(Mosaic, {
|
|
95
|
+
images: list,
|
|
96
|
+
index: index,
|
|
97
|
+
onActivate: activate,
|
|
98
|
+
ratio: ratio,
|
|
99
|
+
placeholder: placeholder
|
|
100
|
+
});
|
|
101
|
+
} else if (layout === "thumbs-side") {
|
|
102
|
+
// Thumbnails sit left of the photo from `sm` up and below it on phones.
|
|
103
|
+
body = jsxs("div", {
|
|
104
|
+
className: "flex flex-col gap-3 sm:flex-row",
|
|
105
|
+
children: [jsx("div", {
|
|
106
|
+
className: "min-w-0 sm:flex-1",
|
|
107
|
+
children: viewer
|
|
108
|
+
}), count > 1 && jsx("div", {
|
|
109
|
+
className: "relative shrink-0 sm:order-first sm:w-20",
|
|
110
|
+
children: jsx(Thumbnails, _objectSpread2(_objectSpread2({}, shared), {}, {
|
|
111
|
+
side: true
|
|
112
|
+
}))
|
|
113
|
+
})]
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
body = jsxs("div", {
|
|
117
|
+
className: "flex flex-col gap-3",
|
|
118
|
+
children: [viewer, count > 1 && jsx(Thumbnails, _objectSpread2({}, shared))]
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return jsxs("div", _objectSpread2(_objectSpread2({
|
|
122
|
+
ref: ref,
|
|
123
|
+
className: cn("w-full", className),
|
|
124
|
+
"data-layout": layout,
|
|
125
|
+
"aria-label": layout === "mosaic" ? ariaLabel : undefined,
|
|
126
|
+
role: layout === "mosaic" ? "region" : undefined
|
|
127
|
+
}, rest), {}, {
|
|
128
|
+
children: [body, lightbox && jsx(Dialog, {
|
|
129
|
+
isOpen: open,
|
|
130
|
+
onClose: function onClose() {
|
|
131
|
+
return setOpen(false);
|
|
132
|
+
},
|
|
133
|
+
size: "full",
|
|
134
|
+
presentation: "popover",
|
|
135
|
+
title: "Image ".concat(index + 1, " of ").concat(count),
|
|
136
|
+
className: "max-w-5xl",
|
|
137
|
+
"data-gallery-lightbox": "",
|
|
138
|
+
children: jsx(Viewer, _objectSpread2(_objectSpread2({}, shared), {}, {
|
|
139
|
+
ratio: ratio,
|
|
140
|
+
zoom: zoom,
|
|
141
|
+
showCounter: showCounter,
|
|
142
|
+
label: "".concat(ariaLabel, ", full-screen"),
|
|
143
|
+
fit: "contain"
|
|
144
|
+
}))
|
|
145
|
+
})]
|
|
146
|
+
}));
|
|
147
|
+
});
|
|
148
|
+
ImageGallery.displayName = "ImageGallery";
|
|
149
|
+
|
|
150
|
+
export { ImageGallery, ImageGallery as default };
|