@timmbr/ui 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/dist/components/Breadcrumb/Breadcrumb.d.ts +9 -0
  2. package/dist/components/Breadcrumb/Breadcrumb.d.ts.map +1 -0
  3. package/dist/components/Breadcrumb/Breadcrumb.js +32 -0
  4. package/dist/components/Breadcrumb/Breadcrumb.styles.d.ts +8 -0
  5. package/dist/components/Breadcrumb/Breadcrumb.styles.d.ts.map +1 -0
  6. package/dist/components/Breadcrumb/Breadcrumb.styles.js +20 -0
  7. package/dist/components/Breadcrumb/Breadcrumb.types.d.ts +27 -0
  8. package/dist/components/Breadcrumb/Breadcrumb.types.d.ts.map +1 -0
  9. package/dist/components/Breadcrumb/index.d.ts +4 -0
  10. package/dist/components/Breadcrumb/index.d.ts.map +1 -0
  11. package/dist/components/Chip/Chip.d.ts.map +1 -1
  12. package/dist/components/Chip/Chip.js +13 -3
  13. package/dist/components/Chip/Chip.types.d.ts +5 -0
  14. package/dist/components/Chip/Chip.types.d.ts.map +1 -1
  15. package/dist/components/Drawer/Drawer.d.ts.map +1 -1
  16. package/dist/components/Drawer/Drawer.js +4 -4
  17. package/dist/components/Drawer/Drawer.styles.d.ts +1 -0
  18. package/dist/components/Drawer/Drawer.styles.d.ts.map +1 -1
  19. package/dist/components/Drawer/Drawer.styles.js +7 -1
  20. package/dist/components/Drawer/Drawer.types.d.ts +2 -0
  21. package/dist/components/Drawer/Drawer.types.d.ts.map +1 -1
  22. package/dist/components/FilterChips/FilterChips.d.ts +8 -0
  23. package/dist/components/FilterChips/FilterChips.d.ts.map +1 -0
  24. package/dist/components/FilterChips/FilterChips.js +39 -0
  25. package/dist/components/FilterChips/FilterChips.types.d.ts +17 -0
  26. package/dist/components/FilterChips/FilterChips.types.d.ts.map +1 -0
  27. package/dist/components/FilterChips/index.d.ts +3 -0
  28. package/dist/components/FilterChips/index.d.ts.map +1 -0
  29. package/dist/components/FilterDrawer/FilterDrawer.d.ts +12 -0
  30. package/dist/components/FilterDrawer/FilterDrawer.d.ts.map +1 -0
  31. package/dist/components/FilterDrawer/FilterDrawer.js +157 -0
  32. package/dist/components/FilterDrawer/FilterDrawer.styles.d.ts +12 -0
  33. package/dist/components/FilterDrawer/FilterDrawer.styles.d.ts.map +1 -0
  34. package/dist/components/FilterDrawer/FilterDrawer.styles.js +30 -0
  35. package/dist/components/FilterDrawer/FilterDrawer.types.d.ts +28 -0
  36. package/dist/components/FilterDrawer/FilterDrawer.types.d.ts.map +1 -0
  37. package/dist/components/FilterDrawer/index.d.ts +4 -0
  38. package/dist/components/FilterDrawer/index.d.ts.map +1 -0
  39. package/dist/components/FilterPanel/FilterPanel.d.ts +33 -0
  40. package/dist/components/FilterPanel/FilterPanel.d.ts.map +1 -0
  41. package/dist/components/FilterPanel/FilterPanel.js +239 -0
  42. package/dist/components/FilterPanel/FilterPanel.styles.d.ts +16 -0
  43. package/dist/components/FilterPanel/FilterPanel.styles.d.ts.map +1 -0
  44. package/dist/components/FilterPanel/FilterPanel.styles.js +31 -0
  45. package/dist/components/FilterPanel/FilterPanel.types.d.ts +80 -0
  46. package/dist/components/FilterPanel/FilterPanel.types.d.ts.map +1 -0
  47. package/dist/components/FilterPanel/FilterPanel.types.js +3 -0
  48. package/dist/components/FilterPanel/index.d.ts +4 -0
  49. package/dist/components/FilterPanel/index.d.ts.map +1 -0
  50. package/dist/components/ImageUpload/ImageUpload.styles.d.ts +3 -3
  51. package/dist/components/MediaCard/MediaCard.d.ts.map +1 -1
  52. package/dist/components/MediaCard/MediaCard.js +3 -1
  53. package/dist/components/NavHeader/NavHeader.styles.d.ts +7 -0
  54. package/dist/components/NavHeader/NavHeader.styles.d.ts.map +1 -1
  55. package/dist/components/NavHeader/NavHeader.styles.js +20 -2
  56. package/dist/components/NavHeader/NavHeader.types.d.ts +49 -1
  57. package/dist/components/NavHeader/NavHeader.types.d.ts.map +1 -1
  58. package/dist/components/NavHeader/index.d.ts +3 -2
  59. package/dist/components/NavHeader/index.d.ts.map +1 -1
  60. package/dist/components/NavHeader/subcomponents/NavMobileSearchDrawer.d.ts.map +1 -1
  61. package/dist/components/NavHeader/subcomponents/NavMobileSearchDrawer.js +15 -33
  62. package/dist/components/NavHeader/subcomponents/NavSearch.d.ts.map +1 -1
  63. package/dist/components/NavHeader/subcomponents/NavSearch.js +37 -83
  64. package/dist/components/NavHeader/subcomponents/NavSearchPanel.d.ts +63 -0
  65. package/dist/components/NavHeader/subcomponents/NavSearchPanel.d.ts.map +1 -0
  66. package/dist/components/NavHeader/subcomponents/NavSearchPanel.js +289 -0
  67. package/dist/components/NavMenuColumns/NavMenuColumns.d.ts +8 -1
  68. package/dist/components/NavMenuColumns/NavMenuColumns.d.ts.map +1 -1
  69. package/dist/components/NavMenuColumns/NavMenuColumns.js +27 -1
  70. package/dist/components/NavMenuColumns/NavMenuColumns.styles.js +1 -1
  71. package/dist/components/NavMenuColumns/NavMenuColumns.types.d.ts +18 -0
  72. package/dist/components/NavMenuColumns/NavMenuColumns.types.d.ts.map +1 -1
  73. package/dist/components/OptionPicker/OptionPicker.d.ts +10 -0
  74. package/dist/components/OptionPicker/OptionPicker.d.ts.map +1 -0
  75. package/dist/components/OptionPicker/OptionPicker.js +116 -0
  76. package/dist/components/OptionPicker/OptionPicker.styles.d.ts +20 -0
  77. package/dist/components/OptionPicker/OptionPicker.styles.d.ts.map +1 -0
  78. package/dist/components/OptionPicker/OptionPicker.styles.js +60 -0
  79. package/dist/components/OptionPicker/OptionPicker.types.d.ts +39 -0
  80. package/dist/components/OptionPicker/OptionPicker.types.d.ts.map +1 -0
  81. package/dist/components/OptionPicker/index.d.ts +4 -0
  82. package/dist/components/OptionPicker/index.d.ts.map +1 -0
  83. package/dist/components/Price/Price.styles.d.ts.map +1 -1
  84. package/dist/components/Price/Price.styles.js +3 -2
  85. package/dist/components/ProductGallery/ProductGallery.d.ts +10 -0
  86. package/dist/components/ProductGallery/ProductGallery.d.ts.map +1 -0
  87. package/dist/components/ProductGallery/ProductGallery.js +306 -0
  88. package/dist/components/ProductGallery/ProductGallery.styles.d.ts +32 -0
  89. package/dist/components/ProductGallery/ProductGallery.styles.d.ts.map +1 -0
  90. package/dist/components/ProductGallery/ProductGallery.styles.js +86 -0
  91. package/dist/components/ProductGallery/ProductGallery.types.d.ts +55 -0
  92. package/dist/components/ProductGallery/ProductGallery.types.d.ts.map +1 -0
  93. package/dist/components/ProductGallery/ProductGalleryLightbox.d.ts +20 -0
  94. package/dist/components/ProductGallery/ProductGalleryLightbox.d.ts.map +1 -0
  95. package/dist/components/ProductGallery/ProductGalleryLightbox.js +210 -0
  96. package/dist/components/ProductGallery/index.d.ts +4 -0
  97. package/dist/components/ProductGallery/index.d.ts.map +1 -0
  98. package/dist/components/QuantityStepper/QuantityStepper.d.ts +8 -0
  99. package/dist/components/QuantityStepper/QuantityStepper.d.ts.map +1 -0
  100. package/dist/components/QuantityStepper/QuantityStepper.js +65 -0
  101. package/dist/components/QuantityStepper/QuantityStepper.styles.d.ts +8 -0
  102. package/dist/components/QuantityStepper/QuantityStepper.styles.d.ts.map +1 -0
  103. package/dist/components/QuantityStepper/QuantityStepper.styles.js +24 -0
  104. package/dist/components/QuantityStepper/QuantityStepper.types.d.ts +20 -0
  105. package/dist/components/QuantityStepper/QuantityStepper.types.d.ts.map +1 -0
  106. package/dist/components/QuantityStepper/index.d.ts +4 -0
  107. package/dist/components/QuantityStepper/index.d.ts.map +1 -0
  108. package/dist/components/RangeSlider/RangeSlider.d.ts.map +1 -1
  109. package/dist/components/RangeSlider/RangeSlider.js +5 -3
  110. package/dist/components/RangeSlider/RangeSlider.types.d.ts +7 -0
  111. package/dist/components/RangeSlider/RangeSlider.types.d.ts.map +1 -1
  112. package/dist/components/Select/Select.d.ts.map +1 -1
  113. package/dist/components/Select/Select.js +14 -1
  114. package/dist/components/Select/Select.types.d.ts +4 -0
  115. package/dist/components/Select/Select.types.d.ts.map +1 -1
  116. package/dist/components/Snackbar/Snackbar.d.ts +9 -0
  117. package/dist/components/Snackbar/Snackbar.d.ts.map +1 -0
  118. package/dist/components/Snackbar/Snackbar.js +108 -0
  119. package/dist/components/Snackbar/Snackbar.styles.d.ts +17 -0
  120. package/dist/components/Snackbar/Snackbar.styles.d.ts.map +1 -0
  121. package/dist/components/Snackbar/Snackbar.styles.js +37 -0
  122. package/dist/components/Snackbar/Snackbar.types.d.ts +36 -0
  123. package/dist/components/Snackbar/Snackbar.types.d.ts.map +1 -0
  124. package/dist/components/Snackbar/SnackbarHost.d.ts +4 -0
  125. package/dist/components/Snackbar/SnackbarHost.d.ts.map +1 -0
  126. package/dist/components/Snackbar/SnackbarHost.js +24 -0
  127. package/dist/components/Snackbar/index.d.ts +6 -0
  128. package/dist/components/Snackbar/index.d.ts.map +1 -0
  129. package/dist/components/Snackbar/snackbarStore.d.ts +42 -0
  130. package/dist/components/Snackbar/snackbarStore.d.ts.map +1 -0
  131. package/dist/components/Snackbar/snackbarStore.js +87 -0
  132. package/dist/components/StockMeter/StockMeter.d.ts +8 -0
  133. package/dist/components/StockMeter/StockMeter.d.ts.map +1 -0
  134. package/dist/components/StockMeter/StockMeter.js +19 -0
  135. package/dist/components/StockMeter/StockMeter.styles.d.ts +4 -0
  136. package/dist/components/StockMeter/StockMeter.styles.d.ts.map +1 -0
  137. package/dist/components/StockMeter/StockMeter.styles.js +7 -0
  138. package/dist/components/StockMeter/StockMeter.types.d.ts +10 -0
  139. package/dist/components/StockMeter/StockMeter.types.d.ts.map +1 -0
  140. package/dist/components/StockMeter/index.d.ts +4 -0
  141. package/dist/components/StockMeter/index.d.ts.map +1 -0
  142. package/dist/components/Swatch/Swatch.d.ts +11 -0
  143. package/dist/components/Swatch/Swatch.d.ts.map +1 -0
  144. package/dist/components/Swatch/Swatch.js +40 -0
  145. package/dist/components/Swatch/Swatch.styles.d.ts +8 -0
  146. package/dist/components/Swatch/Swatch.styles.d.ts.map +1 -0
  147. package/dist/components/Swatch/Swatch.styles.js +26 -0
  148. package/dist/components/Swatch/Swatch.types.d.ts +11 -0
  149. package/dist/components/Swatch/Swatch.types.d.ts.map +1 -0
  150. package/dist/components/Swatch/index.d.ts +4 -0
  151. package/dist/components/Swatch/index.d.ts.map +1 -0
  152. package/dist/components/Toast/Toast.d.ts.map +1 -1
  153. package/dist/components/Toast/Toast.js +49 -42
  154. package/dist/components/Toast/Toast.types.d.ts +7 -0
  155. package/dist/components/Toast/Toast.types.d.ts.map +1 -1
  156. package/dist/components/Toast/ToastContainer.d.ts.map +1 -1
  157. package/dist/components/Toast/ToastContainer.js +5 -0
  158. package/dist/components/Toast/toastStore.d.ts +11 -0
  159. package/dist/components/Toast/toastStore.d.ts.map +1 -1
  160. package/dist/components/Toast/toastStore.js +70 -30
  161. package/dist/index.d.ts +10 -0
  162. package/dist/index.d.ts.map +1 -1
  163. package/dist/index.js +25 -2
  164. package/dist/providers/TimmbrConfigProvider.d.ts.map +1 -1
  165. package/dist/providers/TimmbrConfigProvider.js +5 -9
  166. package/package.json +6 -6
@@ -0,0 +1,210 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
5
+ import { ZoomOut, ZoomIn, X, ChevronLeft, ChevronRight } from '@timmbr/icons';
6
+ import { productGalleryLightboxButtonVariants, productGalleryLightboxVariants } from './ProductGallery.styles.js';
7
+
8
+ const MAX_SCALE = 3;
9
+ const ZOOM_STEP = 2;
10
+ const SWIPE_PX = 50;
11
+ const TAP_SLOP_PX = 10;
12
+ const FIT = { scale: 1, x: 0, y: 0 };
13
+ function ProductGalleryLightbox({
14
+ open,
15
+ onOpenChange,
16
+ images,
17
+ index,
18
+ onIndexChange,
19
+ renderImage,
20
+ labels,
21
+ onCloseAutoFocus
22
+ }) {
23
+ const [view, setView] = React.useState(FIT);
24
+ const pointers = React.useRef(/* @__PURE__ */ new Map());
25
+ const pinch = React.useRef(null);
26
+ const lastTap = React.useRef(0);
27
+ const lastPointerType = React.useRef("mouse");
28
+ const swipe = React.useRef(null);
29
+ const [dragX, setDragX] = React.useState(0);
30
+ const total = images.length;
31
+ const image = images[index];
32
+ const [shown, setShown] = React.useState({ index, open });
33
+ if (shown.index !== index || shown.open !== open) {
34
+ setShown({ index, open });
35
+ setView(FIT);
36
+ }
37
+ const handleOpenChange = (next) => {
38
+ if (!next) {
39
+ pointers.current.clear();
40
+ pinch.current = null;
41
+ swipe.current = null;
42
+ lastTap.current = 0;
43
+ setDragX(0);
44
+ }
45
+ onOpenChange(next);
46
+ };
47
+ const go = (delta) => {
48
+ const next = index + delta;
49
+ if (next >= 0 && next < total) onIndexChange(next);
50
+ };
51
+ const zoomTo = (scale) => setView((current) => {
52
+ const clamped = Math.min(MAX_SCALE, Math.max(1, scale));
53
+ return clamped === 1 ? FIT : { ...current, scale: clamped };
54
+ });
55
+ const toggleZoom = () => zoomTo(view.scale > 1 ? 1 : ZOOM_STEP);
56
+ const onKeyDown = (event) => {
57
+ if (event.key === "ArrowRight") go(1);
58
+ else if (event.key === "ArrowLeft") go(-1);
59
+ else if (event.key === "+" || event.key === "=") zoomTo(view.scale + 0.5);
60
+ else if (event.key === "-") zoomTo(view.scale - 0.5);
61
+ };
62
+ const distance = () => {
63
+ const [a, b] = [...pointers.current.values()];
64
+ return Math.hypot(a.x - b.x, a.y - b.y);
65
+ };
66
+ const onPointerDown = (event) => {
67
+ lastPointerType.current = event.pointerType;
68
+ event.currentTarget.setPointerCapture(event.pointerId);
69
+ pointers.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
70
+ if (pointers.current.size === 1) {
71
+ swipe.current = { x: event.clientX, y: event.clientY, multi: false };
72
+ } else {
73
+ if (swipe.current) swipe.current.multi = true;
74
+ setDragX(0);
75
+ }
76
+ if (pointers.current.size === 2) pinch.current = { distance: distance(), scale: view.scale };
77
+ };
78
+ const onPointerMove = (event) => {
79
+ const previous = pointers.current.get(event.pointerId);
80
+ if (!previous) return;
81
+ pointers.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
82
+ if (pinch.current && pointers.current.size === 2) {
83
+ zoomTo(pinch.current.scale * distance() / pinch.current.distance);
84
+ } else if (view.scale > 1) {
85
+ const dx = event.clientX - previous.x;
86
+ const dy = event.clientY - previous.y;
87
+ setView((current) => ({ ...current, x: current.x + dx, y: current.y + dy }));
88
+ } else if (swipe.current && !swipe.current.multi && event.pointerType !== "mouse") {
89
+ setDragX(event.clientX - swipe.current.x);
90
+ }
91
+ };
92
+ const onPointerUp = (event) => {
93
+ const start = pointers.current.get(event.pointerId);
94
+ pointers.current.delete(event.pointerId);
95
+ if (pointers.current.size < 2) pinch.current = null;
96
+ const gesture = swipe.current;
97
+ if (pointers.current.size === 0) {
98
+ swipe.current = null;
99
+ setDragX(0);
100
+ }
101
+ if (!start || event.pointerType === "mouse") return;
102
+ if (gesture && !gesture.multi && view.scale === 1) {
103
+ const dx = event.clientX - gesture.x;
104
+ const dy = event.clientY - gesture.y;
105
+ if (Math.abs(dx) > SWIPE_PX && Math.abs(dx) > Math.abs(dy)) {
106
+ lastTap.current = 0;
107
+ go(dx < 0 ? 1 : -1);
108
+ return;
109
+ }
110
+ }
111
+ const still = gesture && !gesture.multi && Math.hypot(event.clientX - gesture.x, event.clientY - gesture.y) < TAP_SLOP_PX;
112
+ if (!still) {
113
+ lastTap.current = 0;
114
+ return;
115
+ }
116
+ const now = Date.now();
117
+ if (now - lastTap.current < 300) {
118
+ lastTap.current = 0;
119
+ toggleZoom();
120
+ } else {
121
+ lastTap.current = now;
122
+ }
123
+ };
124
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsx(DialogPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
125
+ DialogPrimitive.Content,
126
+ {
127
+ className: productGalleryLightboxVariants(),
128
+ onKeyDown,
129
+ onCloseAutoFocus,
130
+ "aria-describedby": void 0,
131
+ "data-slot": "product-gallery-lightbox",
132
+ children: [
133
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 p-3 sm:p-4", children: [
134
+ /* @__PURE__ */ jsxs(DialogPrimitive.Title, { className: "text-body-2 font-semibold tabular-nums", children: [
135
+ labels.position(index, total),
136
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: image ? ` — ${image.alt}` : "" })
137
+ ] }),
138
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
139
+ /* @__PURE__ */ jsx(
140
+ "button",
141
+ {
142
+ type: "button",
143
+ className: productGalleryLightboxButtonVariants(),
144
+ "aria-label": view.scale > 1 ? labels.zoomOut : labels.zoomIn,
145
+ onClick: toggleZoom,
146
+ children: view.scale > 1 ? /* @__PURE__ */ jsx(ZoomOut, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ZoomIn, { "aria-hidden": "true" })
147
+ }
148
+ ),
149
+ /* @__PURE__ */ jsx(DialogPrimitive.Close, { className: productGalleryLightboxButtonVariants(), "aria-label": labels.close, children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" }) })
150
+ ] })
151
+ ] }),
152
+ /* @__PURE__ */ jsxs(
153
+ "div",
154
+ {
155
+ className: "relative min-h-0 flex-1 touch-none select-none overflow-hidden",
156
+ onDoubleClick: () => lastPointerType.current === "mouse" && toggleZoom(),
157
+ onWheel: (event) => zoomTo(view.scale - event.deltaY * 2e-3),
158
+ onPointerDown,
159
+ onPointerMove,
160
+ onPointerUp,
161
+ onPointerCancel: onPointerUp,
162
+ style: { cursor: view.scale > 1 ? "grab" : "zoom-in" },
163
+ "data-slot": "product-gallery-lightbox-stage",
164
+ children: [
165
+ image && /* @__PURE__ */ jsx(
166
+ "div",
167
+ {
168
+ className: `absolute inset-0 ease-out motion-reduce:transition-none ${dragX ? "" : "transition-transform duration-150"}`,
169
+ style: { transform: `translate(${view.x + dragX}px, ${view.y}px) scale(${view.scale})` },
170
+ children: renderImage(image, {
171
+ priority: false,
172
+ sizes: "100vw",
173
+ slot: "lightbox",
174
+ className: "object-contain"
175
+ })
176
+ }
177
+ ),
178
+ /* @__PURE__ */ jsx(
179
+ "button",
180
+ {
181
+ type: "button",
182
+ className: `${productGalleryLightboxButtonVariants()} absolute left-3 top-1/2 hidden -translate-y-1/2 md:flex`,
183
+ "aria-label": labels.previous,
184
+ disabled: index === 0,
185
+ onClick: () => go(-1),
186
+ onPointerDown: (event) => event.stopPropagation(),
187
+ children: /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true" })
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsx(
191
+ "button",
192
+ {
193
+ type: "button",
194
+ className: `${productGalleryLightboxButtonVariants()} absolute right-3 top-1/2 hidden -translate-y-1/2 md:flex`,
195
+ "aria-label": labels.next,
196
+ disabled: index >= total - 1,
197
+ onClick: () => go(1),
198
+ onPointerDown: (event) => event.stopPropagation(),
199
+ children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
200
+ }
201
+ )
202
+ ]
203
+ }
204
+ )
205
+ ]
206
+ }
207
+ ) }) });
208
+ }
209
+
210
+ export { ProductGalleryLightbox };
@@ -0,0 +1,4 @@
1
+ export * from './ProductGallery';
2
+ export * from './ProductGallery.types';
3
+ export * from './ProductGallery.styles';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/ProductGallery/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import * as React from 'react';
2
+ import type { QuantityStepperProps } from './QuantityStepper.types';
3
+ /**
4
+ * − value + for a quantity. The buttons stop at `min` and `max` (e.g. the units in stock), and
5
+ * the value is announced when it changes. Arrow up/down on either button steps too.
6
+ */
7
+ export declare const QuantityStepper: React.ForwardRefExoticComponent<QuantityStepperProps & React.RefAttributes<HTMLDivElement>>;
8
+ //# sourceMappingURL=QuantityStepper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuantityStepper.d.ts","sourceRoot":"","sources":["../../../src/components/QuantityStepper/QuantityStepper.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAQ/B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,eAAe,6FAmD3B,CAAC"}
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { Minus, Plus } from '@timmbr/icons';
5
+ import { cn } from '@timmbr/utils';
6
+ import { quantityStepperButtonVariants, quantityStepperValueVariants, quantityStepperVariants } from './QuantityStepper.styles.js';
7
+
8
+ const QuantityStepper = React.forwardRef(
9
+ ({ value, onChange, min = 1, max = 99, size, disabled = false, labels, className, ...props }, ref) => {
10
+ const clamp = (next) => Math.min(Math.max(next, min), Math.max(min, max));
11
+ const step = (delta) => {
12
+ const next = clamp(value + delta);
13
+ if (next !== value) onChange(next);
14
+ };
15
+ const onKeyDown = (event) => {
16
+ if (event.key === "ArrowUp" || event.key === "ArrowRight") {
17
+ event.preventDefault();
18
+ step(1);
19
+ } else if (event.key === "ArrowDown" || event.key === "ArrowLeft") {
20
+ event.preventDefault();
21
+ step(-1);
22
+ }
23
+ };
24
+ return /* @__PURE__ */ jsxs(
25
+ "div",
26
+ {
27
+ ref,
28
+ role: "group",
29
+ "aria-label": labels?.group ?? "Quantity",
30
+ "data-slot": "quantity-stepper",
31
+ className: cn(quantityStepperVariants({ size, disabled }), className),
32
+ onKeyDown: disabled ? void 0 : onKeyDown,
33
+ ...props,
34
+ children: [
35
+ /* @__PURE__ */ jsx(
36
+ "button",
37
+ {
38
+ type: "button",
39
+ "aria-label": labels?.decrease ?? "Decrease quantity",
40
+ disabled: disabled || value <= min,
41
+ onClick: () => step(-1),
42
+ className: quantityStepperButtonVariants(),
43
+ children: /* @__PURE__ */ jsx(Minus, { "aria-hidden": "true", className: "size-4" })
44
+ }
45
+ ),
46
+ /* @__PURE__ */ jsx("output", { "aria-live": "polite", className: quantityStepperValueVariants(), "data-slot": "quantity-stepper-value", children: value }),
47
+ /* @__PURE__ */ jsx(
48
+ "button",
49
+ {
50
+ type: "button",
51
+ "aria-label": labels?.increase ?? "Increase quantity",
52
+ disabled: disabled || value >= max,
53
+ onClick: () => step(1),
54
+ className: quantityStepperButtonVariants(),
55
+ children: /* @__PURE__ */ jsx(Plus, { "aria-hidden": "true", className: "size-4" })
56
+ }
57
+ )
58
+ ]
59
+ }
60
+ );
61
+ }
62
+ );
63
+ QuantityStepper.displayName = "QuantityStepper";
64
+
65
+ export { QuantityStepper };
@@ -0,0 +1,8 @@
1
+ /** Bordered box: − value +, on a white surface with the 4px radius used by fields. */
2
+ export declare const quantityStepperVariants: (props?: ({
3
+ size?: "md" | "lg" | null | undefined;
4
+ disabled?: boolean | null | undefined;
5
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ export declare const quantityStepperButtonVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
7
+ export declare const quantityStepperValueVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
8
+ //# sourceMappingURL=QuantityStepper.styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuantityStepper.styles.d.ts","sourceRoot":"","sources":["../../../src/components/QuantityStepper/QuantityStepper.styles.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,eAAO,MAAM,uBAAuB;;;8EAenC,CAAC;AAEF,eAAO,MAAM,6BAA6B,oFAEzC,CAAC;AAEF,eAAO,MAAM,4BAA4B,oFAAoE,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { cva } from 'class-variance-authority';
2
+
3
+ const quantityStepperVariants = cva(
4
+ "inline-flex shrink-0 items-center rounded-sm border border-grey-300 bg-grey-50 font-sans text-grey-950",
5
+ {
6
+ variants: {
7
+ size: {
8
+ md: "h-11",
9
+ lg: "h-[52px]"
10
+ },
11
+ disabled: {
12
+ true: "opacity-50",
13
+ false: ""
14
+ }
15
+ },
16
+ defaultVariants: { size: "md", disabled: false }
17
+ }
18
+ );
19
+ const quantityStepperButtonVariants = cva(
20
+ "flex h-full w-10 items-center justify-center rounded-sm text-grey-500 transition-colors hover:text-grey-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:cursor-not-allowed disabled:text-grey-300"
21
+ );
22
+ const quantityStepperValueVariants = cva("min-w-6 text-center text-body-1 font-semibold tabular-nums");
23
+
24
+ export { quantityStepperButtonVariants, quantityStepperValueVariants, quantityStepperVariants };
@@ -0,0 +1,20 @@
1
+ import type * as React from 'react';
2
+ export interface QuantityStepperLabels {
3
+ /** Accessible name of the whole control. */
4
+ group?: string;
5
+ decrease?: string;
6
+ increase?: string;
7
+ }
8
+ export interface QuantityStepperProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
9
+ value: number;
10
+ onChange: (value: number) => void;
11
+ /** Defaults to 1. */
12
+ min?: number;
13
+ /** The + button disables here (e.g. units in stock). Defaults to 99. */
14
+ max?: number;
15
+ /** `md` matches the default button height (44px); `lg` matches a 52px buy button. */
16
+ size?: 'md' | 'lg';
17
+ disabled?: boolean;
18
+ labels?: QuantityStepperLabels;
19
+ }
20
+ //# sourceMappingURL=QuantityStepper.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuantityStepper.types.d.ts","sourceRoot":"","sources":["../../../src/components/QuantityStepper/QuantityStepper.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IAClG,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,qBAAqB;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC"}
@@ -0,0 +1,4 @@
1
+ export * from './QuantityStepper';
2
+ export * from './QuantityStepper.types';
3
+ export * from './QuantityStepper.styles';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/QuantityStepper/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"RangeSlider.d.ts","sourceRoot":"","sources":["../../../src/components/RangeSlider/RangeSlider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,eAAO,MAAM,WAAW,0FAyIvB,CAAC"}
1
+ {"version":3,"file":"RangeSlider.d.ts","sourceRoot":"","sources":["../../../src/components/RangeSlider/RangeSlider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,eAAO,MAAM,WAAW,0FA2IvB,CAAC"}
@@ -25,6 +25,8 @@ const RangeSlider = React.forwardRef(
25
25
  onValueCommit,
26
26
  onApply,
27
27
  onReset,
28
+ thumbLabels = ["Minimum value", "Maximum value"],
29
+ resetDisabled = false,
28
30
  disabled,
29
31
  ...props
30
32
  }, ref) => {
@@ -98,7 +100,7 @@ const RangeSlider = React.forwardRef(
98
100
  SliderPrimitive.Thumb,
99
101
  {
100
102
  className: rangeSliderThumbVariants(),
101
- "aria-label": index === 0 ? "Minimum value" : "Maximum value"
103
+ "aria-label": thumbLabels[index === 0 ? 0 : 1]
102
104
  },
103
105
  index
104
106
  ))
@@ -111,8 +113,8 @@ const RangeSlider = React.forwardRef(
111
113
  {
112
114
  type: "button",
113
115
  onClick: handleReset,
114
- disabled,
115
- className: "text-body-3 font-medium text-muted hover:text-foreground transition-colors cursor-pointer disabled:opacity-50",
116
+ disabled: disabled || resetDisabled,
117
+ className: "rounded-sm text-body-3 font-semibold text-foreground underline decoration-border underline-offset-4 transition-colors cursor-pointer hover:text-primary hover:decoration-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:no-underline disabled:opacity-40 disabled:hover:text-foreground",
116
118
  children: resetLabel
117
119
  }
118
120
  ),
@@ -64,5 +64,12 @@ export interface RangeSliderProps extends Omit<React.ComponentPropsWithoutRef<ty
64
64
  * Callback fired when the Reset button is clicked.
65
65
  */
66
66
  onReset?: () => void;
67
+ /**
68
+ * Accessible names of the two thumbs (translate these).
69
+ * @default ['Minimum value', 'Maximum value']
70
+ */
71
+ thumbLabels?: [string, string];
72
+ /** Disables Reset (e.g. nothing to reset). */
73
+ resetDisabled?: boolean;
67
74
  }
68
75
  //# sourceMappingURL=RangeSlider.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RangeSlider.types.d.ts","sourceRoot":"","sources":["../../../src/components/RangeSlider/RangeSlider.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,gBACf,SAAQ,IAAI,CACR,KAAK,CAAC,wBAAwB,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,EAC3D,OAAO,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,CAC7D,EACD,mBAAmB;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB"}
1
+ {"version":3,"file":"RangeSlider.types.d.ts","sourceRoot":"","sources":["../../../src/components/RangeSlider/RangeSlider.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,gBACf,SAAQ,IAAI,CACR,KAAK,CAAC,wBAAwB,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,EAC3D,OAAO,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,CAC7D,EACD,mBAAmB;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAU1D,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,gBAAgB,CAAC;AAEhE,eAAO,MAAM,MAAM,uFAwHlB,CAAC;AAKF,eAAO,MAAM,UAAU,uCAAuB,CAAC;AAC/C,eAAO,MAAM,aAAa,8GAA0B,CAAC;AACrD,eAAO,MAAM,WAAW,0GAAwB,CAAC;AACjD,eAAO,MAAM,aAAa,2GAA0B,CAAC;AACrD,eAAO,MAAM,UAAU,wGAAuB,CAAC;AAC/C,eAAO,MAAM,WAAW,yGAAwB,CAAC;AACjD,eAAO,MAAM,WAAW,yGAAwB,CAAC;AACjD,eAAO,MAAM,eAAe,6GAA4B,CAAC"}
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAU1D,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,gBAAgB,CAAC;AAEhE,eAAO,MAAM,MAAM,uFAiIlB,CAAC;AAKF,eAAO,MAAM,UAAU,uCAAuB,CAAC;AAC/C,eAAO,MAAM,aAAa,8GAA0B,CAAC;AACrD,eAAO,MAAM,WAAW,0GAAwB,CAAC;AACjD,eAAO,MAAM,aAAa,2GAA0B,CAAC;AACrD,eAAO,MAAM,UAAU,wGAAuB,CAAC;AAC/C,eAAO,MAAM,WAAW,yGAAwB,CAAC;AACjD,eAAO,MAAM,WAAW,yGAAwB,CAAC;AACjD,eAAO,MAAM,eAAe,6GAA4B,CAAC"}
@@ -26,8 +26,12 @@ const Select = React.forwardRef(
26
26
  onValueChange,
27
27
  disabled,
28
28
  children,
29
+ ariaLabel,
30
+ id: customId,
29
31
  ...props
30
32
  }, ref) => {
33
+ const generatedId = React.useId();
34
+ const triggerId = customId ?? generatedId;
31
35
  const isError = Boolean(error);
32
36
  const errorMessage = typeof error === "string" ? error : void 0;
33
37
  const globalMotion = useGlobalAnimation();
@@ -38,7 +42,14 @@ const Select = React.forwardRef(
38
42
  className: cn("flex flex-col gap-1.5 w-full font-sans", containerClassName),
39
43
  style,
40
44
  children: [
41
- label && /* @__PURE__ */ jsx("label", { className: "text-body-3 font-semibold text-foreground tracking-wide select-none", children: label }),
45
+ label && /* @__PURE__ */ jsx(
46
+ "label",
47
+ {
48
+ htmlFor: triggerId,
49
+ className: "text-body-3 font-semibold text-foreground tracking-wide select-none",
50
+ children: label
51
+ }
52
+ ),
42
53
  /* @__PURE__ */ jsxs(
43
54
  SelectPrimitive.Root,
44
55
  {
@@ -52,6 +63,8 @@ const Select = React.forwardRef(
52
63
  SelectPrimitive.Trigger,
53
64
  {
54
65
  ref,
66
+ id: triggerId,
67
+ "aria-label": ariaLabel,
55
68
  "data-slot": "select-trigger",
56
69
  "aria-invalid": isError ? "true" : void 0,
57
70
  className: cn(
@@ -13,6 +13,10 @@ export interface SelectProps extends Omit<SelectPrimitive.SelectProps, 'children
13
13
  * Field label displayed above the select trigger.
14
14
  */
15
15
  label?: string;
16
+ /** Accessible name when there is no visible `label` (e.g. a compact "Sort by" control). */
17
+ ariaLabel?: string;
18
+ /** Id of the trigger (generated when omitted); the visible label points at it. */
19
+ id?: string;
16
20
  /**
17
21
  * Placeholder text shown when no value is selected.
18
22
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Select.types.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,UAAU,CAAC,EACnD,qBAAqB;IACvB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B"}
1
+ {"version":3,"file":"Select.types.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,UAAU,CAAC,EACnD,qBAAqB;IACvB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B"}
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+ import type { SnackbarProps } from './Snackbar.types';
3
+ /**
4
+ * One snackbar: message (and an optional second line), one inline action, a small close button.
5
+ * It leaves on its own after `duration` (paused while hovered, focused or touched), when swiped
6
+ * sideways or down, or when its action is used.
7
+ */
8
+ export declare const Snackbar: React.ForwardRefExoticComponent<SnackbarProps & React.RefAttributes<HTMLDivElement>>;
9
+ //# sourceMappingURL=Snackbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Snackbar.d.ts","sourceRoot":"","sources":["../../../src/components/Snackbar/Snackbar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAatD;;;;GAIG;AACH,eAAO,MAAM,QAAQ,sFA+FpB,CAAC"}
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { AlertCircle, AlertTriangle, CheckCircle, Info, X } from '@timmbr/icons';
5
+ import { cn } from '@timmbr/utils';
6
+ import { snackbarIconVariants, snackbarActionVariants, snackbarCloseVariants, snackbarVariants } from './Snackbar.styles.js';
7
+
8
+ const ICONS = {
9
+ default: Info,
10
+ info: Info,
11
+ success: CheckCircle,
12
+ warning: AlertTriangle,
13
+ destructive: AlertCircle
14
+ };
15
+ const SWIPE_DISMISS_PX = 48;
16
+ const Snackbar = React.forwardRef(
17
+ ({ item, onDismiss, dismissLabel = "Dismiss", className, style, ...props }, ref) => {
18
+ const variant = item.variant ?? "default";
19
+ const Icon = ICONS[variant];
20
+ const duration = item.duration ?? (item.action ? 6e3 : 4e3);
21
+ const [paused, setPaused] = React.useState(false);
22
+ const remaining = React.useRef(duration);
23
+ React.useEffect(() => {
24
+ remaining.current = duration;
25
+ }, [item, duration]);
26
+ React.useEffect(() => {
27
+ if (!item.open || paused || duration === Infinity) return;
28
+ const started = Date.now();
29
+ const timer = setTimeout(onDismiss, remaining.current);
30
+ return () => {
31
+ clearTimeout(timer);
32
+ remaining.current -= Date.now() - started;
33
+ };
34
+ }, [item, paused, duration, onDismiss]);
35
+ const start = React.useRef(null);
36
+ const [drag, setDrag] = React.useState({ x: 0, y: 0 });
37
+ const onPointerDown = (event) => {
38
+ if (event.target.closest("button")) return;
39
+ start.current = { x: event.clientX, y: event.clientY };
40
+ event.currentTarget.setPointerCapture?.(event.pointerId);
41
+ setPaused(true);
42
+ };
43
+ const onPointerMove = (event) => {
44
+ if (!start.current) return;
45
+ setDrag({
46
+ x: event.clientX - start.current.x,
47
+ y: Math.max(0, event.clientY - start.current.y)
48
+ });
49
+ };
50
+ const onPointerEnd = () => {
51
+ if (!start.current) return;
52
+ start.current = null;
53
+ setPaused(false);
54
+ if (Math.abs(drag.x) > SWIPE_DISMISS_PX || drag.y > SWIPE_DISMISS_PX) onDismiss();
55
+ else setDrag({ x: 0, y: 0 });
56
+ };
57
+ const dragging = drag.x !== 0 || drag.y !== 0;
58
+ return /* @__PURE__ */ jsxs(
59
+ "div",
60
+ {
61
+ ref,
62
+ "data-slot": "snackbar",
63
+ "data-variant": variant,
64
+ "data-state": item.open ? "open" : "closed",
65
+ className: cn(snackbarVariants({ open: item.open }), dragging && "transition-none", className),
66
+ style: {
67
+ ...style,
68
+ ...dragging && {
69
+ transform: `translate(${drag.x}px, ${drag.y}px)`,
70
+ opacity: Math.max(0.3, 1 - Math.max(Math.abs(drag.x), drag.y) / 200)
71
+ }
72
+ },
73
+ onMouseEnter: () => setPaused(true),
74
+ onMouseLeave: () => setPaused(false),
75
+ onFocus: () => setPaused(true),
76
+ onBlur: () => setPaused(false),
77
+ onPointerDown,
78
+ onPointerMove,
79
+ onPointerUp: onPointerEnd,
80
+ onPointerCancel: onPointerEnd,
81
+ ...props,
82
+ children: [
83
+ /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true", className: snackbarIconVariants({ variant }) }),
84
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 py-1", children: [
85
+ /* @__PURE__ */ jsx("p", { className: "text-body-2 font-medium leading-snug", children: item.message }),
86
+ item.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-body-3 leading-snug text-grey-300", children: item.description })
87
+ ] }),
88
+ item.action && /* @__PURE__ */ jsx(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ className: snackbarActionVariants(),
93
+ onClick: (event) => {
94
+ item.action?.onClick(event);
95
+ onDismiss();
96
+ },
97
+ children: item.action.label
98
+ }
99
+ ),
100
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": dismissLabel, className: snackbarCloseVariants(), onClick: onDismiss, children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-4" }) })
101
+ ]
102
+ }
103
+ );
104
+ }
105
+ );
106
+ Snackbar.displayName = "Snackbar";
107
+
108
+ export { Snackbar };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Bottom of the screen, above anything pinned there (pages set --toast-offset-bottom to a bar's
3
+ * height) and the home indicator. Full width minus margins on phones; a compact bar centred on
4
+ * wider screens.
5
+ */
6
+ export declare const snackbarHostVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
7
+ /** Inverse surface (dark bar, light text), as on Android and most shopping apps. */
8
+ export declare const snackbarVariants: (props?: ({
9
+ open?: boolean | null | undefined;
10
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
11
+ export declare const snackbarIconVariants: (props?: ({
12
+ variant?: "default" | "destructive" | "success" | "warning" | "info" | null | undefined;
13
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
14
+ /** Text action: light primary on the dark bar (high contrast), no button chrome. */
15
+ export declare const snackbarActionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
16
+ export declare const snackbarCloseVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
17
+ //# sourceMappingURL=Snackbar.styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Snackbar.styles.d.ts","sourceRoot":"","sources":["../../../src/components/Snackbar/Snackbar.styles.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,oFAEhC,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,gBAAgB;;8EAW5B,CAAC;AAEF,eAAO,MAAM,oBAAoB;;8EAW/B,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,sBAAsB,oFAElC,CAAC;AAEF,eAAO,MAAM,qBAAqB,oFAEjC,CAAC"}