@ceebee/ui 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +15 -0
- package/dist/styles.css +53 -6
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -456,6 +456,7 @@ function AutoComplete({
|
|
|
456
456
|
const [loading, setLoading] = useState(false);
|
|
457
457
|
const [failed, setFailed] = useState(false);
|
|
458
458
|
const [chosen, setChosen] = useState(null);
|
|
459
|
+
const [open, setOpen] = useState(false);
|
|
459
460
|
const available = async ? loaded : items ?? [];
|
|
460
461
|
const optionFor = (candidate) => {
|
|
461
462
|
if (candidate == null) return null;
|
|
@@ -497,6 +498,17 @@ function AutoComplete({
|
|
|
497
498
|
asked.current += 1;
|
|
498
499
|
inFlight.current?.abort();
|
|
499
500
|
}, []);
|
|
501
|
+
useEffect(() => {
|
|
502
|
+
if (!open) return void 0;
|
|
503
|
+
const onScroll = (event) => {
|
|
504
|
+
const target = event.target;
|
|
505
|
+
if (target === document || target === document.documentElement || target === document.body) {
|
|
506
|
+
setOpen(false);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
window.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
|
510
|
+
return () => window.removeEventListener("scroll", onScroll, { capture: true });
|
|
511
|
+
}, [open]);
|
|
500
512
|
return /* @__PURE__ */ jsxs(
|
|
501
513
|
Combobox.Root,
|
|
502
514
|
{
|
|
@@ -510,6 +522,9 @@ function AutoComplete({
|
|
|
510
522
|
setChosen(option);
|
|
511
523
|
onValueChange?.(option?.value ?? null);
|
|
512
524
|
},
|
|
525
|
+
modal: true,
|
|
526
|
+
open,
|
|
527
|
+
onOpenChange: setOpen,
|
|
513
528
|
isItemEqualToValue: (a, b) => a?.value === b?.value,
|
|
514
529
|
itemToStringLabel: (item) => typeof item === "string" ? item : item.label,
|
|
515
530
|
disabled,
|
package/dist/styles.css
CHANGED
|
@@ -547,11 +547,13 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
|
|
|
547
547
|
not line up with the thing it was a list for. The tick moves to the trailing
|
|
548
548
|
edge — `order`, so the reading order is untouched — and the row's inline
|
|
549
549
|
padding matches the field's. */
|
|
550
|
+
/* Same shape and the same reasoning as the Select's popup — see select.css. */
|
|
550
551
|
.cb-autocomplete__popup {
|
|
551
552
|
box-sizing: border-box;
|
|
552
|
-
width:
|
|
553
|
+
width: max-content;
|
|
553
554
|
min-width: var(--anchor-width, 12rem);
|
|
554
|
-
max-
|
|
555
|
+
max-width: var(--available-width, 100vw);
|
|
556
|
+
max-height: min(18rem, var(--available-height, 18rem));
|
|
555
557
|
overflow-y: auto;
|
|
556
558
|
padding: var(--cb-space-1);
|
|
557
559
|
background: var(--cb-surface-raised);
|
|
@@ -1180,11 +1182,26 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
|
|
|
1180
1182
|
.cb-select[data-disabled] { opacity: 0.55; cursor: not-allowed; }
|
|
1181
1183
|
.cb-select__icon { display: inline-flex; color: var(--cb-fg-subtle); }
|
|
1182
1184
|
|
|
1185
|
+
/* As wide as its longest option, and never wider than the screen.
|
|
1186
|
+
*
|
|
1187
|
+
* Pinned to the anchor's width, a popup dropping from a field that is half a
|
|
1188
|
+
* filter row is 175px on a phone — and the options in it are place names.
|
|
1189
|
+
* "Kabupaten Tangerang Selatan (5)" wrapped to three lines there, the one below
|
|
1190
|
+
* it to two, and the list read as a cramped column of broken phrases rather
|
|
1191
|
+
* than as a set of choices.
|
|
1192
|
+
*
|
|
1193
|
+
* So the anchor's width becomes the floor rather than the value: still never
|
|
1194
|
+
* narrower than the field it belongs to, because a popup narrower than its
|
|
1195
|
+
* field reads as belonging to something else. `--available-width` is the room
|
|
1196
|
+
* the positioner measured between the anchor and the edge of the screen, so
|
|
1197
|
+
* the ceiling moves with the viewport instead of being a number written here.
|
|
1198
|
+
*/
|
|
1183
1199
|
.cb-select__popup {
|
|
1184
1200
|
box-sizing: border-box;
|
|
1185
|
-
width:
|
|
1201
|
+
width: max-content;
|
|
1186
1202
|
min-width: var(--anchor-width, 10rem);
|
|
1187
|
-
max-
|
|
1203
|
+
max-width: var(--available-width, 100vw);
|
|
1204
|
+
max-height: min(18rem, var(--available-height, 18rem));
|
|
1188
1205
|
overflow-y: auto;
|
|
1189
1206
|
background: var(--cb-surface-raised);
|
|
1190
1207
|
border: var(--cb-border-width) solid var(--cb-border);
|
|
@@ -1762,7 +1779,17 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
|
|
|
1762
1779
|
|
|
1763
1780
|
.cb-drawer__title { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-lg); font-weight: var(--cb-weight-semibold); }
|
|
1764
1781
|
.cb-drawer__description { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); color: var(--cb-fg-muted); }
|
|
1765
|
-
|
|
1782
|
+
/* Same scrollbar as the Modal's body, same answer — see modal.css. */
|
|
1783
|
+
.cb-drawer__body {
|
|
1784
|
+
min-height: 0;
|
|
1785
|
+
flex: 1;
|
|
1786
|
+
overflow-y: auto;
|
|
1787
|
+
scrollbar-gutter: stable;
|
|
1788
|
+
margin-inline: calc(-1 * var(--cb-space-5));
|
|
1789
|
+
padding-inline: var(--cb-space-5);
|
|
1790
|
+
font-family: var(--cb-font-sans);
|
|
1791
|
+
font-size: var(--cb-text-sm);
|
|
1792
|
+
}
|
|
1766
1793
|
.cb-drawer__footer { display: flex; justify-content: flex-end; gap: var(--cb-space-2); }
|
|
1767
1794
|
|
|
1768
1795
|
/* overlay/modal.css */
|
|
@@ -1818,7 +1845,27 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
|
|
|
1818
1845
|
|
|
1819
1846
|
.cb-modal__title { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-lg); font-weight: var(--cb-weight-semibold); }
|
|
1820
1847
|
.cb-modal__description { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); color: var(--cb-fg-muted); }
|
|
1821
|
-
|
|
1848
|
+
/* The body is the only scrolling part of the dialog, and a scroller draws its
|
|
1849
|
+
bar at its own inline edge — which, with the body running to the content edge,
|
|
1850
|
+
is the right edge of every field inside it. Platforms differ in how that looks
|
|
1851
|
+
and neither way is acceptable: macOS and Android float an overlay bar over the
|
|
1852
|
+
field, Windows reserves real width and squeezes it.
|
|
1853
|
+
|
|
1854
|
+
So both halves are needed. `scrollbar-gutter: stable` keeps the reserved bar
|
|
1855
|
+
out of the content box — always, not only while the content overflows, so a
|
|
1856
|
+
field appearing does not shift the rest sideways. The bleed-and-pad puts a
|
|
1857
|
+
space-5 margin between the content and the edge where an overlay bar is drawn,
|
|
1858
|
+
and hands the width back to the dialog so the padding costs nothing. It also
|
|
1859
|
+
stops a focused field's ring being clipped at the scroller's edge. */
|
|
1860
|
+
.cb-modal__body {
|
|
1861
|
+
min-height: 0;
|
|
1862
|
+
overflow-y: auto;
|
|
1863
|
+
scrollbar-gutter: stable;
|
|
1864
|
+
margin-inline: calc(-1 * var(--cb-space-5));
|
|
1865
|
+
padding-inline: var(--cb-space-5);
|
|
1866
|
+
font-family: var(--cb-font-sans);
|
|
1867
|
+
font-size: var(--cb-text-sm);
|
|
1868
|
+
}
|
|
1822
1869
|
.cb-modal__footer { flex: none; display: flex; justify-content: flex-end; gap: var(--cb-space-2); margin-top: var(--cb-space-2); }
|
|
1823
1870
|
|
|
1824
1871
|
/* overlay/popover.css */
|