@plastic-js/tsumiki 0.1.38 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BottomSheet.js +12 -55
- package/dist/components/Dialog.js +26 -21
- package/dist/components/Select.js +8 -2
- package/docs/api.md +23 -7
- package/package.json +1 -1
|
@@ -3,10 +3,9 @@ import { insert, jsx, mergeProps, setProp, template } from "@plastic-js/plastic/
|
|
|
3
3
|
import { css } from "@emotion/css";
|
|
4
4
|
import { createEffect, createSignal, mergeProps as mergeProps$1, splitProps } from "@plastic-js/plastic";
|
|
5
5
|
//#region src/components/BottomSheet.jsx
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var _tmpl = template("<div></div>");
|
|
6
|
+
var _tmpl3 = template("<div data-part=\"grabber\"><div></div></div>");
|
|
7
|
+
var _tmpl2 = template("<div role=\"dialog\" aria-modal=\"true\" aria-label=\"BottomSheet\"><!><div data-part=\"content\"></div></div>");
|
|
8
|
+
var _tmpl = template("<div data-part=\"backdrop\"></div>");
|
|
10
9
|
var CLOSE_DISTANCE_RATIO = .3;
|
|
11
10
|
var CLOSE_VELOCITY = .5;
|
|
12
11
|
var RUBBER_BAND_FACTOR = 3;
|
|
@@ -37,6 +36,7 @@ var sheetClass = css({
|
|
|
37
36
|
borderRadius: "var(--tsu-radius-l3-md) var(--tsu-radius-l3-md) 0 0",
|
|
38
37
|
boxShadow: "var(--tsu-shadow-lg)",
|
|
39
38
|
maxHeight: "70vh",
|
|
39
|
+
"@supports (height: 100dvh)": { maxHeight: "min(70dvh, 70vh)" },
|
|
40
40
|
display: "flex",
|
|
41
41
|
flexDirection: "column",
|
|
42
42
|
transform: "translateY(100%)",
|
|
@@ -62,7 +62,7 @@ var grabberAreaClass = css({
|
|
|
62
62
|
display: "flex",
|
|
63
63
|
alignItems: "center",
|
|
64
64
|
justifyContent: "center",
|
|
65
|
-
|
|
65
|
+
height: "24px",
|
|
66
66
|
flexShrink: 0,
|
|
67
67
|
cursor: "grab",
|
|
68
68
|
touchAction: "none",
|
|
@@ -74,35 +74,12 @@ var grabberClass = css({
|
|
|
74
74
|
borderRadius: "2.5px",
|
|
75
75
|
background: "var(--tsu-neutral-alpha-6)"
|
|
76
76
|
});
|
|
77
|
-
var
|
|
77
|
+
var contentClass = css({
|
|
78
78
|
display: "flex",
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
padding: "var(--tsu-container-padding-lg)",
|
|
82
|
-
borderBottom: "1px solid var(--tsu-neutral-7)",
|
|
83
|
-
flexShrink: 0
|
|
84
|
-
});
|
|
85
|
-
var titleClass = css({
|
|
86
|
-
margin: 0,
|
|
87
|
-
fontSize: "var(--tsu-text-title-size)",
|
|
88
|
-
fontWeight: "var(--tsu-text-title-weight)",
|
|
89
|
-
lineHeight: "var(--tsu-text-title-line-height)",
|
|
90
|
-
color: "var(--tsu-fg)",
|
|
91
|
-
textAlign: "center"
|
|
92
|
-
});
|
|
93
|
-
var bodyClass = css({
|
|
94
|
-
overflowY: "auto",
|
|
95
|
-
padding: "var(--tsu-container-padding-lg)",
|
|
79
|
+
flexDirection: "column",
|
|
80
|
+
padding: "0 var(--tsu-container-padding-lg)",
|
|
96
81
|
flex: 1,
|
|
97
|
-
|
|
98
|
-
});
|
|
99
|
-
var bodyNoHeaderClass = css({ paddingTop: "calc(var(--tsu-container-padding-lg) * 2)" });
|
|
100
|
-
var footerClass = css({
|
|
101
|
-
display: "flex",
|
|
102
|
-
gap: "var(--tsu-spacing-sm)",
|
|
103
|
-
padding: "0 var(--tsu-container-padding-lg) var(--tsu-container-padding-lg)",
|
|
104
|
-
flexShrink: 0,
|
|
105
|
-
paddingTop: "var(--tsu-spacing-sm)"
|
|
82
|
+
minHeight: 0
|
|
106
83
|
});
|
|
107
84
|
var read = (v) => typeof v === "function" ? v() : v;
|
|
108
85
|
var BottomSheet = (props) => {
|
|
@@ -115,15 +92,12 @@ var BottomSheet = (props) => {
|
|
|
115
92
|
"onOpen",
|
|
116
93
|
"onClose",
|
|
117
94
|
"onOpenChange",
|
|
118
|
-
"title",
|
|
119
95
|
"dismissible",
|
|
120
96
|
"draggable",
|
|
121
97
|
"backdropBlur",
|
|
122
98
|
"backdropClassName",
|
|
123
99
|
"backdropStyle",
|
|
124
|
-
"
|
|
125
|
-
"footer",
|
|
126
|
-
"footerClass",
|
|
100
|
+
"contentClassName",
|
|
127
101
|
"class",
|
|
128
102
|
"children"
|
|
129
103
|
]);
|
|
@@ -197,8 +171,6 @@ var BottomSheet = (props) => {
|
|
|
197
171
|
const _el0 = _tmpl2.cloneNode(true);
|
|
198
172
|
const _el1 = _el0.firstChild;
|
|
199
173
|
const _el2 = _el0.firstChild.nextSibling;
|
|
200
|
-
const _el3 = _el0.firstChild.nextSibling.nextSibling;
|
|
201
|
-
const _el4 = _el0.firstChild.nextSibling.nextSibling.nextSibling;
|
|
202
174
|
insert(_el0, () => showGrabber && (() => {
|
|
203
175
|
const _el0 = _tmpl3.cloneNode(true);
|
|
204
176
|
const _el1 = _el0.firstChild;
|
|
@@ -210,22 +182,8 @@ var BottomSheet = (props) => {
|
|
|
210
182
|
setProp(_el0, "onPointerCancel", () => onGrabberPointerCancel);
|
|
211
183
|
return _el0;
|
|
212
184
|
})(), _el1);
|
|
213
|
-
insert(
|
|
214
|
-
|
|
215
|
-
const _el1 = _el0.firstChild;
|
|
216
|
-
insert(_el1, () => local.title);
|
|
217
|
-
setProp(_el1, "className", () => titleClass);
|
|
218
|
-
setProp(_el0, "className", () => headerClass);
|
|
219
|
-
return _el0;
|
|
220
|
-
}), _el2);
|
|
221
|
-
insert(_el3, () => local.children);
|
|
222
|
-
setProp(_el3, "className", () => [!local.title && !showGrabber ? `${bodyClass} ${bodyNoHeaderClass}` : bodyClass, local.bodyClassName].filter(Boolean).join(" "));
|
|
223
|
-
insert(_el0, () => local.footer && (() => {
|
|
224
|
-
const _el0 = _tmpl.cloneNode(true);
|
|
225
|
-
insert(_el0, () => local.footer);
|
|
226
|
-
setProp(_el0, "className", () => local.footerClass ? `${footerClass} ${local.footerClass}` : footerClass);
|
|
227
|
-
return _el0;
|
|
228
|
-
}), _el4);
|
|
185
|
+
insert(_el2, () => local.children);
|
|
186
|
+
setProp(_el2, "className", () => [contentClass, local.contentClassName].filter(Boolean).join(" "));
|
|
229
187
|
setProp(_el0, "ref", (el) => {
|
|
230
188
|
sheetEl = el;
|
|
231
189
|
});
|
|
@@ -235,7 +193,6 @@ var BottomSheet = (props) => {
|
|
|
235
193
|
local.class
|
|
236
194
|
].filter(Boolean).join(" "));
|
|
237
195
|
setProp(_el0, "data-open", () => read(local.open) ? "" : void 0);
|
|
238
|
-
setProp(_el0, "aria-label", () => local.title || "BottomSheet");
|
|
239
196
|
return _el0;
|
|
240
197
|
}] }));
|
|
241
198
|
};
|
|
@@ -5,9 +5,11 @@ import { css, keyframes } from "@emotion/css";
|
|
|
5
5
|
import { Dialog } from "@plastic-js/ark";
|
|
6
6
|
import { mergeProps as mergeProps$2, splitProps } from "@plastic-js/plastic";
|
|
7
7
|
//#region src/components/Dialog.jsx
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
8
|
+
var _tmpl6 = template("<div data-scope=\"dialog\" data-part=\"footer\"></div>");
|
|
9
|
+
var _tmpl5 = template("<div data-scope=\"dialog\" data-part=\"body\"></div>");
|
|
10
|
+
var _tmpl4 = template("<div data-scope=\"dialog\" data-part=\"header\"><div><!><!></div><!></div>");
|
|
11
|
+
var _tmpl3 = template("<button data-part=\"positive-trigger\" type=\"button\"></button>");
|
|
12
|
+
var _tmpl2 = template("<button data-part=\"negative-trigger\" type=\"button\"></button>");
|
|
11
13
|
var _tmpl = template("<div><!><!></div>");
|
|
12
14
|
var backdropModalClass = css({
|
|
13
15
|
position: "fixed",
|
|
@@ -58,12 +60,12 @@ var contentModalClass = css({
|
|
|
58
60
|
borderRadius: "var(--tsu-radius-l3-md)",
|
|
59
61
|
width: "100%",
|
|
60
62
|
maxWidth: "440px",
|
|
61
|
-
maxHeight: "calc(100vh - var(--tsu-spacing-lg) * 2)",
|
|
63
|
+
maxHeight: "min(calc(100vh - var(--tsu-spacing-lg) * 2), 560px)",
|
|
64
|
+
"@supports (height: 100dvh)": { maxHeight: "min(calc(100dvh - var(--tsu-spacing-lg) * 2), 560px)" },
|
|
62
65
|
display: "flex",
|
|
63
66
|
flexDirection: "column",
|
|
64
67
|
color: "var(--tsu-fg)",
|
|
65
68
|
boxShadow: "var(--tsu-shadow-lg)",
|
|
66
|
-
overflowY: "auto",
|
|
67
69
|
animation: `${modalContentIn} 200ms ease`
|
|
68
70
|
});
|
|
69
71
|
var contentSheetClass = css({
|
|
@@ -75,14 +77,14 @@ var contentSheetClass = css({
|
|
|
75
77
|
flexDirection: "column",
|
|
76
78
|
color: "var(--tsu-fg)",
|
|
77
79
|
boxShadow: "var(--tsu-shadow-lg)",
|
|
78
|
-
overflowY: "auto",
|
|
79
80
|
animation: `${sheetContentIn} 250ms ease`
|
|
80
81
|
});
|
|
81
82
|
var headerClass = css({
|
|
82
83
|
display: "flex",
|
|
83
84
|
alignItems: "center",
|
|
84
85
|
justifyContent: "space-between",
|
|
85
|
-
padding: "18px var(--tsu-container-padding-lg)",
|
|
86
|
+
padding: "18px var(--tsu-container-padding-lg) var(--tsu-spacing-lg)",
|
|
87
|
+
marginBottom: "var(--tsu-spacing-sm)",
|
|
86
88
|
borderBottom: "1px solid var(--tsu-neutral-7)",
|
|
87
89
|
flexShrink: 0
|
|
88
90
|
});
|
|
@@ -117,16 +119,16 @@ var closeBtnClass = css({
|
|
|
117
119
|
flexShrink: 0
|
|
118
120
|
});
|
|
119
121
|
var bodyClass = css({
|
|
120
|
-
padding: "var(--tsu-container-padding-lg)",
|
|
122
|
+
padding: "0 var(--tsu-container-padding-lg)",
|
|
121
123
|
flex: 1,
|
|
124
|
+
minHeight: 0,
|
|
122
125
|
overflowY: "auto",
|
|
123
126
|
WebkitOverflowScrolling: "touch"
|
|
124
127
|
});
|
|
125
|
-
var bodySheetNoHeaderClass = css({ paddingTop: "calc(var(--tsu-container-padding-lg) * 2)" });
|
|
126
128
|
var footerClass = css({
|
|
127
129
|
display: "flex",
|
|
128
130
|
gap: "var(--tsu-spacing-md)",
|
|
129
|
-
padding: "
|
|
131
|
+
padding: "var(--tsu-container-padding-lg)",
|
|
130
132
|
flexShrink: 0
|
|
131
133
|
});
|
|
132
134
|
var footerBtnClass = css({
|
|
@@ -167,6 +169,7 @@ var btnDangerClass = css({
|
|
|
167
169
|
}
|
|
168
170
|
});
|
|
169
171
|
var read = (v) => typeof v === "function" ? v() : v;
|
|
172
|
+
var warnedSheetNoTitle = false;
|
|
170
173
|
var Dialog$1 = (props) => {
|
|
171
174
|
const [local, rest] = splitProps(mergeProps$2({
|
|
172
175
|
mode: "sheet",
|
|
@@ -240,7 +243,7 @@ var Dialog$1 = (props) => {
|
|
|
240
243
|
return _el0;
|
|
241
244
|
}), _el1);
|
|
242
245
|
insert(_el0, () => local.showConfirm && (() => {
|
|
243
|
-
const _el0 =
|
|
246
|
+
const _el0 = _tmpl3.cloneNode(true);
|
|
244
247
|
insert(_el0, () => read(local.loading) ? "Loading…" : local.confirmText);
|
|
245
248
|
setProp(_el0, "className", () => `${btnClass} ${local.danger ? btnDangerClass : btnPrimaryClass}`);
|
|
246
249
|
setProp(_el0, "disabled", () => read(local.loading));
|
|
@@ -250,8 +253,10 @@ var Dialog$1 = (props) => {
|
|
|
250
253
|
setProp(_el0, "className", () => onlyOneBtn ? `${footerBtnClass} tsu-dialog-footer--center` : footerBtnClass);
|
|
251
254
|
return _el0;
|
|
252
255
|
} : void 0;
|
|
253
|
-
|
|
254
|
-
|
|
256
|
+
if (isSheet && !local.title && !warnedSheetNoTitle) {
|
|
257
|
+
warnedSheetNoTitle = true;
|
|
258
|
+
console.warn("[Dialog] sheet mode requires a title; use BottomSheet for header-less content.");
|
|
259
|
+
}
|
|
255
260
|
return jsx(Dialog.Root, mergeProps({
|
|
256
261
|
get open() {
|
|
257
262
|
return read(local.open);
|
|
@@ -280,8 +285,8 @@ var Dialog$1 = (props) => {
|
|
|
280
285
|
return local.class ? `${isSheet ? contentSheetClass : contentModalClass} ${local.class}` : isSheet ? contentSheetClass : contentModalClass;
|
|
281
286
|
},
|
|
282
287
|
children: [
|
|
283
|
-
() =>
|
|
284
|
-
const _el0 =
|
|
288
|
+
() => {
|
|
289
|
+
const _el0 = _tmpl4.cloneNode(true);
|
|
285
290
|
const _el1 = _el0.firstChild;
|
|
286
291
|
const _el2 = _el1.firstChild;
|
|
287
292
|
const _el3 = _el1.firstChild.nextSibling;
|
|
@@ -308,19 +313,19 @@ var Dialog$1 = (props) => {
|
|
|
308
313
|
})), _el4);
|
|
309
314
|
setProp(_el0, "className", () => headerClass);
|
|
310
315
|
return _el0;
|
|
311
|
-
}
|
|
316
|
+
},
|
|
312
317
|
() => {
|
|
313
|
-
const _el0 =
|
|
318
|
+
const _el0 = _tmpl5.cloneNode(true);
|
|
314
319
|
insert(_el0, () => local.children);
|
|
315
|
-
setProp(_el0, "className", () =>
|
|
320
|
+
setProp(_el0, "className", () => bodyClass);
|
|
316
321
|
return _el0;
|
|
317
322
|
},
|
|
318
|
-
() =>
|
|
319
|
-
const _el0 =
|
|
323
|
+
() => {
|
|
324
|
+
const _el0 = _tmpl6.cloneNode(true);
|
|
320
325
|
insert(_el0, () => footerContent);
|
|
321
326
|
setProp(_el0, "className", () => footerClass);
|
|
322
327
|
return _el0;
|
|
323
|
-
}
|
|
328
|
+
}
|
|
324
329
|
]
|
|
325
330
|
}))
|
|
326
331
|
}))] })) }));
|
|
@@ -59,7 +59,13 @@ var triggerIndicatorClass = css({
|
|
|
59
59
|
}
|
|
60
60
|
});
|
|
61
61
|
var listBodyClass = css({ padding: 0 });
|
|
62
|
-
var listClass = css({
|
|
62
|
+
var listClass = css({
|
|
63
|
+
flex: 1,
|
|
64
|
+
minHeight: 0,
|
|
65
|
+
overflowY: "auto",
|
|
66
|
+
WebkitOverflowScrolling: "touch",
|
|
67
|
+
padding: "0 8px calc(8px + env(safe-area-inset-bottom, 0px))"
|
|
68
|
+
});
|
|
63
69
|
var itemClass = css({
|
|
64
70
|
width: "100%",
|
|
65
71
|
display: "flex",
|
|
@@ -308,7 +314,7 @@ var Content = () => {
|
|
|
308
314
|
get backdropStyle() {
|
|
309
315
|
return ctx.backdropStyle ?? { background: "rgba(0,0,0,0.5)" };
|
|
310
316
|
},
|
|
311
|
-
|
|
317
|
+
contentClassName: listBodyClass,
|
|
312
318
|
children: jsx("div", mergeProps(() => part("list"), {
|
|
313
319
|
className: listClass,
|
|
314
320
|
children: [() => ctx.filter && (() => {
|
package/docs/api.md
CHANGED
|
@@ -639,16 +639,19 @@ Passthrough: Ark `Root` props (`value`, `min`, `max`, …).
|
|
|
639
639
|
|
|
640
640
|
**Exports:** `Dialog` · Escape hatch: `Dialog.origin.{Root, Trigger, Backdrop, Positioner, Content, Title, Description, CloseTrigger}`
|
|
641
641
|
|
|
642
|
+
**Rendered DOM parts:** content is `[data-scope="dialog"][data-part="content"]`; tsumiki adds `data-part="header"`, `data-part="body"`, `data-part="footer"`, and the action buttons `data-part="negative-trigger"` / `data-part="positive-trigger"`. `title`, `description`, and `closeTrigger` carry `data-part` from Ark.
|
|
643
|
+
|
|
644
|
+
> **Note:** Dialog never renders a grabber / drag handle in either mode (`modal` or `sheet`). Drag-to-dismiss and the grabber are exclusive to [`BottomSheet`](#bottomsheet) — this is a deliberate difference between the two components.
|
|
645
|
+
|
|
642
646
|
| Prop | Type | Default | Description |
|
|
643
647
|
|---|---|---|---|
|
|
644
|
-
| `mode` | `string` | `'
|
|
648
|
+
| `mode` | `string` | `'sheet'` | `'modal'` (centered) or `'sheet'` (bottom sheet). |
|
|
645
649
|
| `open` | `boolean \| (() => boolean)` | — | **Required** controlled open state. |
|
|
646
|
-
| `onOpenChange` | `(
|
|
650
|
+
| `onOpenChange` | `(open: boolean) => void` | — | Fired on every open/close with the new `open` boolean; sync your state here. |
|
|
647
651
|
| `onOpen` | `() => void` | — | Fired when opened. |
|
|
648
652
|
| `onClose` | `() => void` | — | Fired when closed. |
|
|
649
653
|
| `title` | `string` | — | Dialog title. |
|
|
650
654
|
| `icon` | `node` | — | Optional icon above the title. |
|
|
651
|
-
| `message` | `string` | — | Body text. |
|
|
652
655
|
| `showCancel` | `boolean` | `true` | Show the cancel button. |
|
|
653
656
|
| `showConfirm` | `boolean` | `true` | Show the confirm button. |
|
|
654
657
|
| `cancelText` | `string` | `'Cancel'` | Cancel button label. |
|
|
@@ -665,6 +668,9 @@ Passthrough: Ark `Root` props (`value`, `min`, `max`, …).
|
|
|
665
668
|
| `class` | `string` | — | Content class. |
|
|
666
669
|
| `children` | `node` | — | Custom body content. |
|
|
667
670
|
|
|
671
|
+
**Known issues:**
|
|
672
|
+
- Pressing <kbd>Escape</kbd> closes both the Dialog and any nested overlay (e.g. `DateInput`, `Select`) that is open inside it. There is currently no event coordination between the two — both react to the same key press.
|
|
673
|
+
|
|
668
674
|
### ConfirmDialog
|
|
669
675
|
|
|
670
676
|
Imperative + declarative confirm dialog built on `Dialog`.
|
|
@@ -743,16 +749,26 @@ Passthrough: Ark `Root` props (`open`, `onOpenChange`, `positioning`, …).
|
|
|
743
749
|
|
|
744
750
|
### BottomSheet
|
|
745
751
|
|
|
746
|
-
|
|
752
|
+
A free-form presentation layer: a backdrop, an optional drag grabber, and a scrollable content area (`children`). The content is fully consumer-owned — headers, titles, and footers are just regular content.
|
|
747
753
|
|
|
748
754
|
**Exports:** `BottomSheet` · no `.origin`
|
|
749
755
|
|
|
756
|
+
**Rendered DOM parts:** exactly three — `data-part="backdrop"`, `data-part="grabber"` (only when `draggable`), and `data-part="content"`. BottomSheet has no focus trap or Escape-to-close — use [`Dialog`](#dialog) when those are required.
|
|
757
|
+
|
|
758
|
+
> **Design decision:** `content` is a pure container — `display: flex; flex-direction: column` with `padding: 0 24px` only. It has **no vertical padding, no margin, and no scrolling**. Spacing and scrolling are entirely consumer-owned: build your own layout inside `children` (the sheet caps height at `70vh`, so a scrollable section can be a `flex: 1; min-height: 0; overflow-y: auto` child).
|
|
759
|
+
|
|
750
760
|
| Prop | Type | Default | Description |
|
|
751
761
|
|---|---|---|---|
|
|
752
|
-
| `
|
|
762
|
+
| `open` | `boolean \| (() => boolean)` | — | **Required** controlled open state. |
|
|
763
|
+
| `onOpenChange` | `({ open }) => void` | — | Fired on open/close; sync your state here. |
|
|
764
|
+
| `dismissible` | `boolean` | `true` | Allow dismiss via backdrop / drag. |
|
|
765
|
+
| `draggable` | `boolean` | `true` | Show the grabber and enable drag-to-dismiss. |
|
|
753
766
|
| `backdropBlur` | `boolean` | `true` | Blur the backdrop. |
|
|
754
|
-
|
|
755
|
-
|
|
767
|
+
| `backdropClassName` | `string` | — | Extra class for the backdrop. |
|
|
768
|
+
| `backdropStyle` | `object` | — | Extra style for the backdrop. |
|
|
769
|
+
| `contentClassName` | `string` | — | Extra class for the content area. |
|
|
770
|
+
| `class` | `string` | — | Extra class for the sheet. |
|
|
771
|
+
| `children` | `node` | — | **Required** body content. |
|
|
756
772
|
|
|
757
773
|
---
|
|
758
774
|
|