@bridger-kr/react 0.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/README.md +62 -0
- package/dist/components/core/Badge.cjs +36 -0
- package/dist/components/core/Badge.cjs.map +1 -0
- package/dist/components/core/Badge.d.cts +18 -0
- package/dist/components/core/Badge.d.ts +18 -0
- package/dist/components/core/Badge.mjs +34 -0
- package/dist/components/core/Badge.mjs.map +1 -0
- package/dist/components/core/Button.cjs +57 -0
- package/dist/components/core/Button.cjs.map +1 -0
- package/dist/components/core/Button.d.cts +26 -0
- package/dist/components/core/Button.d.ts +26 -0
- package/dist/components/core/Button.mjs +55 -0
- package/dist/components/core/Button.mjs.map +1 -0
- package/dist/components/core/Card.cjs +57 -0
- package/dist/components/core/Card.cjs.map +1 -0
- package/dist/components/core/Card.d.cts +27 -0
- package/dist/components/core/Card.d.ts +27 -0
- package/dist/components/core/Card.mjs +54 -0
- package/dist/components/core/Card.mjs.map +1 -0
- package/dist/components/core/FilterChip.cjs +54 -0
- package/dist/components/core/FilterChip.cjs.map +1 -0
- package/dist/components/core/FilterChip.d.cts +23 -0
- package/dist/components/core/FilterChip.d.ts +23 -0
- package/dist/components/core/FilterChip.mjs +52 -0
- package/dist/components/core/FilterChip.mjs.map +1 -0
- package/dist/components/core/Input.cjs +67 -0
- package/dist/components/core/Input.cjs.map +1 -0
- package/dist/components/core/Input.d.cts +20 -0
- package/dist/components/core/Input.d.ts +20 -0
- package/dist/components/core/Input.mjs +65 -0
- package/dist/components/core/Input.mjs.map +1 -0
- package/dist/components/core/StatusPill.cjs +57 -0
- package/dist/components/core/StatusPill.cjs.map +1 -0
- package/dist/components/core/StatusPill.d.cts +19 -0
- package/dist/components/core/StatusPill.d.ts +19 -0
- package/dist/components/core/StatusPill.mjs +55 -0
- package/dist/components/core/StatusPill.mjs.map +1 -0
- package/dist/components/core/Surface.cjs +52 -0
- package/dist/components/core/Surface.cjs.map +1 -0
- package/dist/components/core/Surface.d.cts +24 -0
- package/dist/components/core/Surface.d.ts +24 -0
- package/dist/components/core/Surface.mjs +47 -0
- package/dist/components/core/Surface.mjs.map +1 -0
- package/dist/components/core/Tabs.cjs +64 -0
- package/dist/components/core/Tabs.cjs.map +1 -0
- package/dist/components/core/Tabs.d.cts +24 -0
- package/dist/components/core/Tabs.d.ts +24 -0
- package/dist/components/core/Tabs.mjs +62 -0
- package/dist/components/core/Tabs.mjs.map +1 -0
- package/dist/components/data/Avatar.cjs +40 -0
- package/dist/components/data/Avatar.cjs.map +1 -0
- package/dist/components/data/Avatar.d.cts +24 -0
- package/dist/components/data/Avatar.d.ts +24 -0
- package/dist/components/data/Avatar.mjs +38 -0
- package/dist/components/data/Avatar.mjs.map +1 -0
- package/dist/components/data/CodeBlock.cjs +92 -0
- package/dist/components/data/CodeBlock.cjs.map +1 -0
- package/dist/components/data/CodeBlock.d.cts +20 -0
- package/dist/components/data/CodeBlock.d.ts +20 -0
- package/dist/components/data/CodeBlock.mjs +90 -0
- package/dist/components/data/CodeBlock.mjs.map +1 -0
- package/dist/components/data/KeyValue.cjs +55 -0
- package/dist/components/data/KeyValue.cjs.map +1 -0
- package/dist/components/data/KeyValue.d.cts +24 -0
- package/dist/components/data/KeyValue.d.ts +24 -0
- package/dist/components/data/KeyValue.mjs +53 -0
- package/dist/components/data/KeyValue.mjs.map +1 -0
- package/dist/components/data/LogRow.cjs +55 -0
- package/dist/components/data/LogRow.cjs.map +1 -0
- package/dist/components/data/LogRow.d.cts +23 -0
- package/dist/components/data/LogRow.d.ts +23 -0
- package/dist/components/data/LogRow.mjs +53 -0
- package/dist/components/data/LogRow.mjs.map +1 -0
- package/dist/components/data/Pagination.cjs +44 -0
- package/dist/components/data/Pagination.cjs.map +1 -0
- package/dist/components/data/Pagination.d.cts +13 -0
- package/dist/components/data/Pagination.d.ts +13 -0
- package/dist/components/data/Pagination.mjs +42 -0
- package/dist/components/data/Pagination.mjs.map +1 -0
- package/dist/components/data/StatTile.cjs +20 -0
- package/dist/components/data/StatTile.cjs.map +1 -0
- package/dist/components/data/StatTile.d.cts +19 -0
- package/dist/components/data/StatTile.d.ts +19 -0
- package/dist/components/data/StatTile.mjs +18 -0
- package/dist/components/data/StatTile.mjs.map +1 -0
- package/dist/components/data/Table.cjs +45 -0
- package/dist/components/data/Table.cjs.map +1 -0
- package/dist/components/data/Table.d.cts +27 -0
- package/dist/components/data/Table.d.ts +27 -0
- package/dist/components/data/Table.mjs +43 -0
- package/dist/components/data/Table.mjs.map +1 -0
- package/dist/components/data/UsageMeter.cjs +28 -0
- package/dist/components/data/UsageMeter.cjs.map +1 -0
- package/dist/components/data/UsageMeter.d.cts +19 -0
- package/dist/components/data/UsageMeter.d.ts +19 -0
- package/dist/components/data/UsageMeter.mjs +26 -0
- package/dist/components/data/UsageMeter.mjs.map +1 -0
- package/dist/components/feedback/Alert.cjs +78 -0
- package/dist/components/feedback/Alert.cjs.map +1 -0
- package/dist/components/feedback/Alert.d.cts +29 -0
- package/dist/components/feedback/Alert.d.ts +29 -0
- package/dist/components/feedback/Alert.mjs +74 -0
- package/dist/components/feedback/Alert.mjs.map +1 -0
- package/dist/components/feedback/Dialog.cjs +62 -0
- package/dist/components/feedback/Dialog.cjs.map +1 -0
- package/dist/components/feedback/Dialog.d.cts +17 -0
- package/dist/components/feedback/Dialog.d.ts +17 -0
- package/dist/components/feedback/Dialog.mjs +60 -0
- package/dist/components/feedback/Dialog.mjs.map +1 -0
- package/dist/components/feedback/Drawer.cjs +58 -0
- package/dist/components/feedback/Drawer.cjs.map +1 -0
- package/dist/components/feedback/Drawer.d.cts +22 -0
- package/dist/components/feedback/Drawer.d.ts +22 -0
- package/dist/components/feedback/Drawer.mjs +56 -0
- package/dist/components/feedback/Drawer.mjs.map +1 -0
- package/dist/components/feedback/EmptyState.cjs +36 -0
- package/dist/components/feedback/EmptyState.cjs.map +1 -0
- package/dist/components/feedback/EmptyState.d.cts +14 -0
- package/dist/components/feedback/EmptyState.d.ts +14 -0
- package/dist/components/feedback/EmptyState.mjs +34 -0
- package/dist/components/feedback/EmptyState.mjs.map +1 -0
- package/dist/components/feedback/Skeleton.cjs +19 -0
- package/dist/components/feedback/Skeleton.cjs.map +1 -0
- package/dist/components/feedback/Skeleton.d.cts +12 -0
- package/dist/components/feedback/Skeleton.d.ts +12 -0
- package/dist/components/feedback/Skeleton.mjs +17 -0
- package/dist/components/feedback/Skeleton.mjs.map +1 -0
- package/dist/components/feedback/Spinner.cjs +17 -0
- package/dist/components/feedback/Spinner.cjs.map +1 -0
- package/dist/components/feedback/Spinner.d.cts +12 -0
- package/dist/components/feedback/Spinner.d.ts +12 -0
- package/dist/components/feedback/Spinner.mjs +15 -0
- package/dist/components/feedback/Spinner.mjs.map +1 -0
- package/dist/components/feedback/Toast.cjs +32 -0
- package/dist/components/feedback/Toast.cjs.map +1 -0
- package/dist/components/feedback/Toast.d.cts +20 -0
- package/dist/components/feedback/Toast.d.ts +20 -0
- package/dist/components/feedback/Toast.mjs +30 -0
- package/dist/components/feedback/Toast.mjs.map +1 -0
- package/dist/components/feedback/Tooltip.cjs +51 -0
- package/dist/components/feedback/Tooltip.cjs.map +1 -0
- package/dist/components/feedback/Tooltip.d.cts +11 -0
- package/dist/components/feedback/Tooltip.d.ts +11 -0
- package/dist/components/feedback/Tooltip.mjs +49 -0
- package/dist/components/feedback/Tooltip.mjs.map +1 -0
- package/dist/components/forms/Checkbox.cjs +74 -0
- package/dist/components/forms/Checkbox.cjs.map +1 -0
- package/dist/components/forms/Checkbox.d.cts +16 -0
- package/dist/components/forms/Checkbox.d.ts +16 -0
- package/dist/components/forms/Checkbox.mjs +72 -0
- package/dist/components/forms/Checkbox.mjs.map +1 -0
- package/dist/components/forms/Combobox.cjs +217 -0
- package/dist/components/forms/Combobox.cjs.map +1 -0
- package/dist/components/forms/Combobox.d.cts +27 -0
- package/dist/components/forms/Combobox.d.ts +27 -0
- package/dist/components/forms/Combobox.mjs +215 -0
- package/dist/components/forms/Combobox.mjs.map +1 -0
- package/dist/components/forms/FileUpload.cjs +187 -0
- package/dist/components/forms/FileUpload.cjs.map +1 -0
- package/dist/components/forms/FileUpload.d.cts +26 -0
- package/dist/components/forms/FileUpload.d.ts +26 -0
- package/dist/components/forms/FileUpload.mjs +185 -0
- package/dist/components/forms/FileUpload.mjs.map +1 -0
- package/dist/components/forms/RadioGroup.cjs +73 -0
- package/dist/components/forms/RadioGroup.cjs.map +1 -0
- package/dist/components/forms/RadioGroup.d.cts +21 -0
- package/dist/components/forms/RadioGroup.d.ts +21 -0
- package/dist/components/forms/RadioGroup.mjs +71 -0
- package/dist/components/forms/RadioGroup.mjs.map +1 -0
- package/dist/components/forms/SegmentedControl.cjs +67 -0
- package/dist/components/forms/SegmentedControl.cjs.map +1 -0
- package/dist/components/forms/SegmentedControl.d.cts +19 -0
- package/dist/components/forms/SegmentedControl.d.ts +19 -0
- package/dist/components/forms/SegmentedControl.mjs +65 -0
- package/dist/components/forms/SegmentedControl.mjs.map +1 -0
- package/dist/components/forms/Select.cjs +67 -0
- package/dist/components/forms/Select.cjs.map +1 -0
- package/dist/components/forms/Select.d.cts +23 -0
- package/dist/components/forms/Select.d.ts +23 -0
- package/dist/components/forms/Select.mjs +65 -0
- package/dist/components/forms/Select.mjs.map +1 -0
- package/dist/components/forms/Slider.cjs +129 -0
- package/dist/components/forms/Slider.cjs.map +1 -0
- package/dist/components/forms/Slider.d.cts +24 -0
- package/dist/components/forms/Slider.d.ts +24 -0
- package/dist/components/forms/Slider.mjs +127 -0
- package/dist/components/forms/Slider.mjs.map +1 -0
- package/dist/components/forms/Switch.cjs +101 -0
- package/dist/components/forms/Switch.cjs.map +1 -0
- package/dist/components/forms/Switch.d.cts +24 -0
- package/dist/components/forms/Switch.d.ts +24 -0
- package/dist/components/forms/Switch.mjs +98 -0
- package/dist/components/forms/Switch.mjs.map +1 -0
- package/dist/components/forms/Textarea.cjs +35 -0
- package/dist/components/forms/Textarea.cjs.map +1 -0
- package/dist/components/forms/Textarea.d.cts +15 -0
- package/dist/components/forms/Textarea.d.ts +15 -0
- package/dist/components/forms/Textarea.mjs +33 -0
- package/dist/components/forms/Textarea.mjs.map +1 -0
- package/dist/components/navigation/Breadcrumb.cjs +27 -0
- package/dist/components/navigation/Breadcrumb.cjs.map +1 -0
- package/dist/components/navigation/Breadcrumb.d.cts +15 -0
- package/dist/components/navigation/Breadcrumb.d.ts +15 -0
- package/dist/components/navigation/Breadcrumb.mjs +25 -0
- package/dist/components/navigation/Breadcrumb.mjs.map +1 -0
- package/dist/components/navigation/CommandPalette.cjs +136 -0
- package/dist/components/navigation/CommandPalette.cjs.map +1 -0
- package/dist/components/navigation/CommandPalette.d.cts +26 -0
- package/dist/components/navigation/CommandPalette.d.ts +26 -0
- package/dist/components/navigation/CommandPalette.mjs +134 -0
- package/dist/components/navigation/CommandPalette.mjs.map +1 -0
- package/dist/components/navigation/Menu.cjs +104 -0
- package/dist/components/navigation/Menu.cjs.map +1 -0
- package/dist/components/navigation/Menu.d.cts +20 -0
- package/dist/components/navigation/Menu.d.ts +20 -0
- package/dist/components/navigation/Menu.mjs +102 -0
- package/dist/components/navigation/Menu.mjs.map +1 -0
- package/dist/components/navigation/Sidebar.cjs +60 -0
- package/dist/components/navigation/Sidebar.cjs.map +1 -0
- package/dist/components/navigation/Sidebar.d.cts +30 -0
- package/dist/components/navigation/Sidebar.d.ts +30 -0
- package/dist/components/navigation/Sidebar.mjs +58 -0
- package/dist/components/navigation/Sidebar.mjs.map +1 -0
- package/dist/components/navigation/Stepper.cjs +55 -0
- package/dist/components/navigation/Stepper.cjs.map +1 -0
- package/dist/components/navigation/Stepper.d.cts +21 -0
- package/dist/components/navigation/Stepper.d.ts +21 -0
- package/dist/components/navigation/Stepper.mjs +53 -0
- package/dist/components/navigation/Stepper.mjs.map +1 -0
- package/dist/components/product/BrandLogo.cjs +159 -0
- package/dist/components/product/BrandLogo.cjs.map +1 -0
- package/dist/components/product/BrandLogo.d.cts +28 -0
- package/dist/components/product/BrandLogo.d.ts +28 -0
- package/dist/components/product/BrandLogo.mjs +156 -0
- package/dist/components/product/BrandLogo.mjs.map +1 -0
- package/dist/components/product/ProductActionPill.cjs +57 -0
- package/dist/components/product/ProductActionPill.cjs.map +1 -0
- package/dist/components/product/ProductActionPill.d.cts +31 -0
- package/dist/components/product/ProductActionPill.d.ts +31 -0
- package/dist/components/product/ProductActionPill.mjs +52 -0
- package/dist/components/product/ProductActionPill.mjs.map +1 -0
- package/dist/components/product/ProductCinematic.cjs +69 -0
- package/dist/components/product/ProductCinematic.cjs.map +1 -0
- package/dist/components/product/ProductCinematic.d.cts +33 -0
- package/dist/components/product/ProductCinematic.d.ts +33 -0
- package/dist/components/product/ProductCinematic.mjs +63 -0
- package/dist/components/product/ProductCinematic.mjs.map +1 -0
- package/dist/components/product/ProductPageHeader.cjs +35 -0
- package/dist/components/product/ProductPageHeader.cjs.map +1 -0
- package/dist/components/product/ProductPageHeader.d.cts +13 -0
- package/dist/components/product/ProductPageHeader.d.ts +13 -0
- package/dist/components/product/ProductPageHeader.mjs +33 -0
- package/dist/components/product/ProductPageHeader.mjs.map +1 -0
- package/dist/components/product/SectionCard.cjs +72 -0
- package/dist/components/product/SectionCard.cjs.map +1 -0
- package/dist/components/product/SectionCard.d.cts +20 -0
- package/dist/components/product/SectionCard.d.ts +20 -0
- package/dist/components/product/SectionCard.mjs +70 -0
- package/dist/components/product/SectionCard.mjs.map +1 -0
- package/dist/components/product/ToolCard.cjs +70 -0
- package/dist/components/product/ToolCard.cjs.map +1 -0
- package/dist/components/product/ToolCard.d.cts +22 -0
- package/dist/components/product/ToolCard.d.ts +22 -0
- package/dist/components/product/ToolCard.mjs +68 -0
- package/dist/components/product/ToolCard.mjs.map +1 -0
- package/dist/index.cjs +2593 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +49 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.mjs +2532 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +463 -0
- package/package.json +50 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/components/forms/Combobox.tsx
|
|
7
|
+
function Combobox({
|
|
8
|
+
label,
|
|
9
|
+
hint,
|
|
10
|
+
options = [],
|
|
11
|
+
value,
|
|
12
|
+
onChange,
|
|
13
|
+
placeholder = "\uAC80\uC0C9\u2026",
|
|
14
|
+
emptyText = "\uACB0\uACFC \uC5C6\uC74C",
|
|
15
|
+
id,
|
|
16
|
+
style
|
|
17
|
+
}) {
|
|
18
|
+
const [open, setOpen] = react.useState(false);
|
|
19
|
+
const [query, setQuery] = react.useState("");
|
|
20
|
+
const [active, setActive] = react.useState(0);
|
|
21
|
+
const rootRef = react.useRef(null);
|
|
22
|
+
const cbId = id || (label ? `cb-${label.replace(/\s+/g, "-")}` : void 0);
|
|
23
|
+
const selected = options.find((o) => o.value === value) || null;
|
|
24
|
+
react.useEffect(() => {
|
|
25
|
+
const onDoc = (e) => {
|
|
26
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
|
|
27
|
+
};
|
|
28
|
+
document.addEventListener("mousedown", onDoc);
|
|
29
|
+
return () => document.removeEventListener("mousedown", onDoc);
|
|
30
|
+
}, []);
|
|
31
|
+
const q = query.trim().toLowerCase();
|
|
32
|
+
const filtered = q ? options.filter((o) => (o.label + " " + (o.meta || "")).toLowerCase().includes(q)) : options;
|
|
33
|
+
const commit = (o) => {
|
|
34
|
+
onChange?.(o.value);
|
|
35
|
+
setOpen(false);
|
|
36
|
+
setQuery("");
|
|
37
|
+
};
|
|
38
|
+
const onKey = (e) => {
|
|
39
|
+
if (e.key === "ArrowDown") {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
setOpen(true);
|
|
42
|
+
setActive((i) => Math.min(i + 1, filtered.length - 1));
|
|
43
|
+
} else if (e.key === "ArrowUp") {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
setActive((i) => Math.max(i - 1, 0));
|
|
46
|
+
} else if (e.key === "Enter" && open && filtered[active]) {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
commit(filtered[active]);
|
|
49
|
+
} else if (e.key === "Escape") {
|
|
50
|
+
setOpen(false);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, style: { display: "grid", gap: 7, position: "relative", ...style }, children: [
|
|
54
|
+
label ? /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: cbId, style: { fontSize: 13, fontWeight: 600, color: "var(--dt-muted-strong)" }, children: label }) : null,
|
|
55
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
56
|
+
"div",
|
|
57
|
+
{
|
|
58
|
+
className: "dt-field",
|
|
59
|
+
style: {
|
|
60
|
+
display: "flex",
|
|
61
|
+
alignItems: "center",
|
|
62
|
+
gap: 9,
|
|
63
|
+
height: 44,
|
|
64
|
+
padding: "0 12px",
|
|
65
|
+
boxShadow: open ? "var(--dt-shadow-focus)" : void 0,
|
|
66
|
+
background: open ? "var(--dt-surface)" : "var(--dt-surface-sunken)"
|
|
67
|
+
},
|
|
68
|
+
onClick: () => setOpen(true),
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
71
|
+
"svg",
|
|
72
|
+
{
|
|
73
|
+
width: "16",
|
|
74
|
+
height: "16",
|
|
75
|
+
viewBox: "0 0 24 24",
|
|
76
|
+
fill: "none",
|
|
77
|
+
"aria-hidden": "true",
|
|
78
|
+
style: { color: "var(--dt-muted)", flex: "0 0 auto" },
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7", stroke: "currentColor", strokeWidth: "2" }),
|
|
81
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 21l-4-4", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
86
|
+
"input",
|
|
87
|
+
{
|
|
88
|
+
id: cbId,
|
|
89
|
+
value: open ? query : selected ? selected.label : "",
|
|
90
|
+
placeholder: selected && !open ? selected.label : placeholder,
|
|
91
|
+
onChange: (e) => {
|
|
92
|
+
setQuery(e.target.value);
|
|
93
|
+
setOpen(true);
|
|
94
|
+
setActive(0);
|
|
95
|
+
},
|
|
96
|
+
onFocus: () => setOpen(true),
|
|
97
|
+
onKeyDown: onKey,
|
|
98
|
+
style: {
|
|
99
|
+
flex: 1,
|
|
100
|
+
minWidth: 0,
|
|
101
|
+
border: "none",
|
|
102
|
+
outline: "none",
|
|
103
|
+
background: "transparent",
|
|
104
|
+
fontSize: 14,
|
|
105
|
+
fontFamily: "inherit",
|
|
106
|
+
color: "var(--dt-ink-strong)"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
),
|
|
110
|
+
selected && !open ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "var(--dt-font-mono)", fontSize: 11, color: "var(--dt-muted)" }, children: selected.meta }) : null
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
open ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
115
|
+
"div",
|
|
116
|
+
{
|
|
117
|
+
role: "listbox",
|
|
118
|
+
style: {
|
|
119
|
+
position: "absolute",
|
|
120
|
+
top: "calc(100% + 6px)",
|
|
121
|
+
left: 0,
|
|
122
|
+
right: 0,
|
|
123
|
+
zIndex: 20,
|
|
124
|
+
background: "var(--dt-surface)",
|
|
125
|
+
border: "1px solid var(--dt-border-strong)",
|
|
126
|
+
borderRadius: "var(--dt-radius-lg)",
|
|
127
|
+
boxShadow: "var(--dt-shadow-md)",
|
|
128
|
+
maxHeight: 240,
|
|
129
|
+
overflowY: "auto",
|
|
130
|
+
padding: 4
|
|
131
|
+
},
|
|
132
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "12px 12px", fontSize: 13, color: "var(--dt-muted)" }, children: emptyText }) : filtered.map((o, i) => {
|
|
133
|
+
const isActive = i === active;
|
|
134
|
+
const isSel = o.value === value;
|
|
135
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
136
|
+
"div",
|
|
137
|
+
{
|
|
138
|
+
role: "option",
|
|
139
|
+
"aria-selected": isSel,
|
|
140
|
+
onMouseEnter: () => setActive(i),
|
|
141
|
+
onMouseDown: (e) => {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
commit(o);
|
|
144
|
+
},
|
|
145
|
+
style: {
|
|
146
|
+
display: "flex",
|
|
147
|
+
alignItems: "center",
|
|
148
|
+
gap: 10,
|
|
149
|
+
padding: "9px 10px",
|
|
150
|
+
borderRadius: "var(--dt-radius-md)",
|
|
151
|
+
cursor: "pointer",
|
|
152
|
+
background: isActive ? "var(--dt-surface-sunken)" : "transparent"
|
|
153
|
+
},
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
156
|
+
"span",
|
|
157
|
+
{
|
|
158
|
+
style: {
|
|
159
|
+
flex: 1,
|
|
160
|
+
minWidth: 0,
|
|
161
|
+
fontSize: 13.5,
|
|
162
|
+
fontWeight: isSel ? 600 : 500,
|
|
163
|
+
color: "var(--dt-ink-strong)",
|
|
164
|
+
overflow: "hidden",
|
|
165
|
+
textOverflow: "ellipsis",
|
|
166
|
+
whiteSpace: "nowrap"
|
|
167
|
+
},
|
|
168
|
+
children: o.label
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
o.meta ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
172
|
+
"span",
|
|
173
|
+
{
|
|
174
|
+
style: {
|
|
175
|
+
fontFamily: "var(--dt-font-mono)",
|
|
176
|
+
fontSize: 11,
|
|
177
|
+
color: "var(--dt-muted)",
|
|
178
|
+
flex: "0 0 auto"
|
|
179
|
+
},
|
|
180
|
+
children: o.meta
|
|
181
|
+
}
|
|
182
|
+
) : null,
|
|
183
|
+
isSel ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
184
|
+
"svg",
|
|
185
|
+
{
|
|
186
|
+
width: "15",
|
|
187
|
+
height: "15",
|
|
188
|
+
viewBox: "0 0 24 24",
|
|
189
|
+
fill: "none",
|
|
190
|
+
"aria-hidden": "true",
|
|
191
|
+
style: { color: "var(--dt-accent)", flex: "0 0 auto" },
|
|
192
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
193
|
+
"path",
|
|
194
|
+
{
|
|
195
|
+
d: "M20 6L9 17l-5-5",
|
|
196
|
+
stroke: "currentColor",
|
|
197
|
+
strokeWidth: "2.2",
|
|
198
|
+
strokeLinecap: "round",
|
|
199
|
+
strokeLinejoin: "round"
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
) : null
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
o.value
|
|
207
|
+
);
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
) : null,
|
|
211
|
+
hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "var(--dt-muted)" }, children: hint }) : null
|
|
212
|
+
] });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
exports.Combobox = Combobox;
|
|
216
|
+
//# sourceMappingURL=Combobox.cjs.map
|
|
217
|
+
//# sourceMappingURL=Combobox.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/forms/Combobox.tsx"],"names":["useState","useRef","useEffect","jsxs","jsx"],"mappings":"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","file":"Combobox.cjs","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type { CSSProperties, HTMLAttributes, KeyboardEvent, MouseEvent } from 'react';\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n meta?: string;\n}\n\nexport interface ComboboxProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id' | 'onChange' | 'style'> {\n label?: string;\n hint?: string;\n options?: ComboboxOption[];\n value?: string;\n onChange?: (value: string) => void;\n placeholder?: string;\n emptyText?: string;\n id?: string;\n style?: CSSProperties;\n}\n\n/**\n * Searchable select for large option sets (the 230+ public-data API catalog).\n * Hairline field; the listbox is a bordered plane. Filters on label + meta.\n * @startingPoint section=\"Forms\" subtitle=\"Searchable select over a large catalog\" viewport=\"460x320\"\n */\nexport function Combobox({\n label,\n hint,\n options = [],\n value,\n onChange,\n placeholder = '검색…',\n emptyText = '결과 없음',\n id,\n style,\n}: ComboboxProps) {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const [active, setActive] = useState(0);\n const rootRef = useRef<HTMLDivElement>(null);\n const cbId = id || (label ? `cb-${label.replace(/\\s+/g, '-')}` : undefined);\n\n const selected = options.find((o) => o.value === value) || null;\n\n useEffect(() => {\n const onDoc = (e: globalThis.MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n };\n document.addEventListener('mousedown', onDoc);\n return () => document.removeEventListener('mousedown', onDoc);\n }, []);\n\n const q = query.trim().toLowerCase();\n const filtered = q\n ? options.filter((o) => (o.label + ' ' + (o.meta || '')).toLowerCase().includes(q))\n : options;\n\n const commit = (o: ComboboxOption) => {\n onChange?.(o.value);\n setOpen(false);\n setQuery('');\n };\n\n const onKey = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setOpen(true);\n setActive((i) => Math.min(i + 1, filtered.length - 1));\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setActive((i) => Math.max(i - 1, 0));\n } else if (e.key === 'Enter' && open && filtered[active]) {\n e.preventDefault();\n commit(filtered[active]);\n } else if (e.key === 'Escape') {\n setOpen(false);\n }\n };\n\n return (\n <div ref={rootRef} style={{ display: 'grid', gap: 7, position: 'relative', ...style }}>\n {label ? (\n <label htmlFor={cbId} style={{ fontSize: 13, fontWeight: 600, color: 'var(--dt-muted-strong)' }}>\n {label}\n </label>\n ) : null}\n <div\n className=\"dt-field\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 9,\n height: 44,\n padding: '0 12px',\n boxShadow: open ? 'var(--dt-shadow-focus)' : undefined,\n background: open ? 'var(--dt-surface)' : 'var(--dt-surface-sunken)',\n }}\n onClick={() => setOpen(true)}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n style={{ color: 'var(--dt-muted)', flex: '0 0 auto' }}\n >\n <circle cx=\"11\" cy=\"11\" r=\"7\" stroke=\"currentColor\" strokeWidth=\"2\" />\n <path d=\"M21 21l-4-4\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" />\n </svg>\n <input\n id={cbId}\n value={open ? query : selected ? selected.label : ''}\n placeholder={selected && !open ? selected.label : placeholder}\n onChange={(e) => {\n setQuery(e.target.value);\n setOpen(true);\n setActive(0);\n }}\n onFocus={() => setOpen(true)}\n onKeyDown={onKey}\n style={{\n flex: 1,\n minWidth: 0,\n border: 'none',\n outline: 'none',\n background: 'transparent',\n fontSize: 14,\n fontFamily: 'inherit',\n color: 'var(--dt-ink-strong)',\n }}\n />\n {selected && !open ? (\n <span style={{ fontFamily: 'var(--dt-font-mono)', fontSize: 11, color: 'var(--dt-muted)' }}>\n {selected.meta}\n </span>\n ) : null}\n </div>\n\n {open ? (\n <div\n role=\"listbox\"\n style={{\n position: 'absolute',\n top: 'calc(100% + 6px)',\n left: 0,\n right: 0,\n zIndex: 20,\n background: 'var(--dt-surface)',\n border: '1px solid var(--dt-border-strong)',\n borderRadius: 'var(--dt-radius-lg)',\n boxShadow: 'var(--dt-shadow-md)',\n maxHeight: 240,\n overflowY: 'auto',\n padding: 4,\n }}\n >\n {filtered.length === 0 ? (\n <div style={{ padding: '12px 12px', fontSize: 13, color: 'var(--dt-muted)' }}>{emptyText}</div>\n ) : (\n filtered.map((o, i) => {\n const isActive = i === active;\n const isSel = o.value === value;\n return (\n <div\n key={o.value}\n role=\"option\"\n aria-selected={isSel}\n onMouseEnter={() => setActive(i)}\n onMouseDown={(e: MouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n commit(o);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '9px 10px',\n borderRadius: 'var(--dt-radius-md)',\n cursor: 'pointer',\n background: isActive ? 'var(--dt-surface-sunken)' : 'transparent',\n }}\n >\n <span\n style={{\n flex: 1,\n minWidth: 0,\n fontSize: 13.5,\n fontWeight: isSel ? 600 : 500,\n color: 'var(--dt-ink-strong)',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {o.label}\n </span>\n {o.meta ? (\n <span\n style={{\n fontFamily: 'var(--dt-font-mono)',\n fontSize: 11,\n color: 'var(--dt-muted)',\n flex: '0 0 auto',\n }}\n >\n {o.meta}\n </span>\n ) : null}\n {isSel ? (\n <svg\n width=\"15\"\n height=\"15\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n style={{ color: 'var(--dt-accent)', flex: '0 0 auto' }}\n >\n <path\n d=\"M20 6L9 17l-5-5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n ) : null}\n </div>\n );\n })\n )}\n </div>\n ) : null}\n {hint ? <span style={{ fontSize: 12, color: 'var(--dt-muted)' }}>{hint}</span> : null}\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ComboboxOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
meta?: string;
|
|
8
|
+
}
|
|
9
|
+
interface ComboboxProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id' | 'onChange' | 'style'> {
|
|
10
|
+
label?: string;
|
|
11
|
+
hint?: string;
|
|
12
|
+
options?: ComboboxOption[];
|
|
13
|
+
value?: string;
|
|
14
|
+
onChange?: (value: string) => void;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
emptyText?: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
style?: CSSProperties;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Searchable select for large option sets (the 230+ public-data API catalog).
|
|
22
|
+
* Hairline field; the listbox is a bordered plane. Filters on label + meta.
|
|
23
|
+
* @startingPoint section="Forms" subtitle="Searchable select over a large catalog" viewport="460x320"
|
|
24
|
+
*/
|
|
25
|
+
declare function Combobox({ label, hint, options, value, onChange, placeholder, emptyText, id, style, }: ComboboxProps): react.JSX.Element;
|
|
26
|
+
|
|
27
|
+
export { Combobox, type ComboboxOption, type ComboboxProps };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ComboboxOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
meta?: string;
|
|
8
|
+
}
|
|
9
|
+
interface ComboboxProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id' | 'onChange' | 'style'> {
|
|
10
|
+
label?: string;
|
|
11
|
+
hint?: string;
|
|
12
|
+
options?: ComboboxOption[];
|
|
13
|
+
value?: string;
|
|
14
|
+
onChange?: (value: string) => void;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
emptyText?: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
style?: CSSProperties;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Searchable select for large option sets (the 230+ public-data API catalog).
|
|
22
|
+
* Hairline field; the listbox is a bordered plane. Filters on label + meta.
|
|
23
|
+
* @startingPoint section="Forms" subtitle="Searchable select over a large catalog" viewport="460x320"
|
|
24
|
+
*/
|
|
25
|
+
declare function Combobox({ label, hint, options, value, onChange, placeholder, emptyText, id, style, }: ComboboxProps): react.JSX.Element;
|
|
26
|
+
|
|
27
|
+
export { Combobox, type ComboboxOption, type ComboboxProps };
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/components/forms/Combobox.tsx
|
|
5
|
+
function Combobox({
|
|
6
|
+
label,
|
|
7
|
+
hint,
|
|
8
|
+
options = [],
|
|
9
|
+
value,
|
|
10
|
+
onChange,
|
|
11
|
+
placeholder = "\uAC80\uC0C9\u2026",
|
|
12
|
+
emptyText = "\uACB0\uACFC \uC5C6\uC74C",
|
|
13
|
+
id,
|
|
14
|
+
style
|
|
15
|
+
}) {
|
|
16
|
+
const [open, setOpen] = useState(false);
|
|
17
|
+
const [query, setQuery] = useState("");
|
|
18
|
+
const [active, setActive] = useState(0);
|
|
19
|
+
const rootRef = useRef(null);
|
|
20
|
+
const cbId = id || (label ? `cb-${label.replace(/\s+/g, "-")}` : void 0);
|
|
21
|
+
const selected = options.find((o) => o.value === value) || null;
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const onDoc = (e) => {
|
|
24
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
|
|
25
|
+
};
|
|
26
|
+
document.addEventListener("mousedown", onDoc);
|
|
27
|
+
return () => document.removeEventListener("mousedown", onDoc);
|
|
28
|
+
}, []);
|
|
29
|
+
const q = query.trim().toLowerCase();
|
|
30
|
+
const filtered = q ? options.filter((o) => (o.label + " " + (o.meta || "")).toLowerCase().includes(q)) : options;
|
|
31
|
+
const commit = (o) => {
|
|
32
|
+
onChange?.(o.value);
|
|
33
|
+
setOpen(false);
|
|
34
|
+
setQuery("");
|
|
35
|
+
};
|
|
36
|
+
const onKey = (e) => {
|
|
37
|
+
if (e.key === "ArrowDown") {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
setOpen(true);
|
|
40
|
+
setActive((i) => Math.min(i + 1, filtered.length - 1));
|
|
41
|
+
} else if (e.key === "ArrowUp") {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
setActive((i) => Math.max(i - 1, 0));
|
|
44
|
+
} else if (e.key === "Enter" && open && filtered[active]) {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
commit(filtered[active]);
|
|
47
|
+
} else if (e.key === "Escape") {
|
|
48
|
+
setOpen(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, style: { display: "grid", gap: 7, position: "relative", ...style }, children: [
|
|
52
|
+
label ? /* @__PURE__ */ jsx("label", { htmlFor: cbId, style: { fontSize: 13, fontWeight: 600, color: "var(--dt-muted-strong)" }, children: label }) : null,
|
|
53
|
+
/* @__PURE__ */ jsxs(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
className: "dt-field",
|
|
57
|
+
style: {
|
|
58
|
+
display: "flex",
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
gap: 9,
|
|
61
|
+
height: 44,
|
|
62
|
+
padding: "0 12px",
|
|
63
|
+
boxShadow: open ? "var(--dt-shadow-focus)" : void 0,
|
|
64
|
+
background: open ? "var(--dt-surface)" : "var(--dt-surface-sunken)"
|
|
65
|
+
},
|
|
66
|
+
onClick: () => setOpen(true),
|
|
67
|
+
children: [
|
|
68
|
+
/* @__PURE__ */ jsxs(
|
|
69
|
+
"svg",
|
|
70
|
+
{
|
|
71
|
+
width: "16",
|
|
72
|
+
height: "16",
|
|
73
|
+
viewBox: "0 0 24 24",
|
|
74
|
+
fill: "none",
|
|
75
|
+
"aria-hidden": "true",
|
|
76
|
+
style: { color: "var(--dt-muted)", flex: "0 0 auto" },
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7", stroke: "currentColor", strokeWidth: "2" }),
|
|
79
|
+
/* @__PURE__ */ jsx("path", { d: "M21 21l-4-4", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
/* @__PURE__ */ jsx(
|
|
84
|
+
"input",
|
|
85
|
+
{
|
|
86
|
+
id: cbId,
|
|
87
|
+
value: open ? query : selected ? selected.label : "",
|
|
88
|
+
placeholder: selected && !open ? selected.label : placeholder,
|
|
89
|
+
onChange: (e) => {
|
|
90
|
+
setQuery(e.target.value);
|
|
91
|
+
setOpen(true);
|
|
92
|
+
setActive(0);
|
|
93
|
+
},
|
|
94
|
+
onFocus: () => setOpen(true),
|
|
95
|
+
onKeyDown: onKey,
|
|
96
|
+
style: {
|
|
97
|
+
flex: 1,
|
|
98
|
+
minWidth: 0,
|
|
99
|
+
border: "none",
|
|
100
|
+
outline: "none",
|
|
101
|
+
background: "transparent",
|
|
102
|
+
fontSize: 14,
|
|
103
|
+
fontFamily: "inherit",
|
|
104
|
+
color: "var(--dt-ink-strong)"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
),
|
|
108
|
+
selected && !open ? /* @__PURE__ */ jsx("span", { style: { fontFamily: "var(--dt-font-mono)", fontSize: 11, color: "var(--dt-muted)" }, children: selected.meta }) : null
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
open ? /* @__PURE__ */ jsx(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
role: "listbox",
|
|
116
|
+
style: {
|
|
117
|
+
position: "absolute",
|
|
118
|
+
top: "calc(100% + 6px)",
|
|
119
|
+
left: 0,
|
|
120
|
+
right: 0,
|
|
121
|
+
zIndex: 20,
|
|
122
|
+
background: "var(--dt-surface)",
|
|
123
|
+
border: "1px solid var(--dt-border-strong)",
|
|
124
|
+
borderRadius: "var(--dt-radius-lg)",
|
|
125
|
+
boxShadow: "var(--dt-shadow-md)",
|
|
126
|
+
maxHeight: 240,
|
|
127
|
+
overflowY: "auto",
|
|
128
|
+
padding: 4
|
|
129
|
+
},
|
|
130
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "12px 12px", fontSize: 13, color: "var(--dt-muted)" }, children: emptyText }) : filtered.map((o, i) => {
|
|
131
|
+
const isActive = i === active;
|
|
132
|
+
const isSel = o.value === value;
|
|
133
|
+
return /* @__PURE__ */ jsxs(
|
|
134
|
+
"div",
|
|
135
|
+
{
|
|
136
|
+
role: "option",
|
|
137
|
+
"aria-selected": isSel,
|
|
138
|
+
onMouseEnter: () => setActive(i),
|
|
139
|
+
onMouseDown: (e) => {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
commit(o);
|
|
142
|
+
},
|
|
143
|
+
style: {
|
|
144
|
+
display: "flex",
|
|
145
|
+
alignItems: "center",
|
|
146
|
+
gap: 10,
|
|
147
|
+
padding: "9px 10px",
|
|
148
|
+
borderRadius: "var(--dt-radius-md)",
|
|
149
|
+
cursor: "pointer",
|
|
150
|
+
background: isActive ? "var(--dt-surface-sunken)" : "transparent"
|
|
151
|
+
},
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ jsx(
|
|
154
|
+
"span",
|
|
155
|
+
{
|
|
156
|
+
style: {
|
|
157
|
+
flex: 1,
|
|
158
|
+
minWidth: 0,
|
|
159
|
+
fontSize: 13.5,
|
|
160
|
+
fontWeight: isSel ? 600 : 500,
|
|
161
|
+
color: "var(--dt-ink-strong)",
|
|
162
|
+
overflow: "hidden",
|
|
163
|
+
textOverflow: "ellipsis",
|
|
164
|
+
whiteSpace: "nowrap"
|
|
165
|
+
},
|
|
166
|
+
children: o.label
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
o.meta ? /* @__PURE__ */ jsx(
|
|
170
|
+
"span",
|
|
171
|
+
{
|
|
172
|
+
style: {
|
|
173
|
+
fontFamily: "var(--dt-font-mono)",
|
|
174
|
+
fontSize: 11,
|
|
175
|
+
color: "var(--dt-muted)",
|
|
176
|
+
flex: "0 0 auto"
|
|
177
|
+
},
|
|
178
|
+
children: o.meta
|
|
179
|
+
}
|
|
180
|
+
) : null,
|
|
181
|
+
isSel ? /* @__PURE__ */ jsx(
|
|
182
|
+
"svg",
|
|
183
|
+
{
|
|
184
|
+
width: "15",
|
|
185
|
+
height: "15",
|
|
186
|
+
viewBox: "0 0 24 24",
|
|
187
|
+
fill: "none",
|
|
188
|
+
"aria-hidden": "true",
|
|
189
|
+
style: { color: "var(--dt-accent)", flex: "0 0 auto" },
|
|
190
|
+
children: /* @__PURE__ */ jsx(
|
|
191
|
+
"path",
|
|
192
|
+
{
|
|
193
|
+
d: "M20 6L9 17l-5-5",
|
|
194
|
+
stroke: "currentColor",
|
|
195
|
+
strokeWidth: "2.2",
|
|
196
|
+
strokeLinecap: "round",
|
|
197
|
+
strokeLinejoin: "round"
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
) : null
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
o.value
|
|
205
|
+
);
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
) : null,
|
|
209
|
+
hint ? /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "var(--dt-muted)" }, children: hint }) : null
|
|
210
|
+
] });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export { Combobox };
|
|
214
|
+
//# sourceMappingURL=Combobox.mjs.map
|
|
215
|
+
//# sourceMappingURL=Combobox.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/forms/Combobox.tsx"],"names":[],"mappings":"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","file":"Combobox.mjs","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type { CSSProperties, HTMLAttributes, KeyboardEvent, MouseEvent } from 'react';\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n meta?: string;\n}\n\nexport interface ComboboxProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id' | 'onChange' | 'style'> {\n label?: string;\n hint?: string;\n options?: ComboboxOption[];\n value?: string;\n onChange?: (value: string) => void;\n placeholder?: string;\n emptyText?: string;\n id?: string;\n style?: CSSProperties;\n}\n\n/**\n * Searchable select for large option sets (the 230+ public-data API catalog).\n * Hairline field; the listbox is a bordered plane. Filters on label + meta.\n * @startingPoint section=\"Forms\" subtitle=\"Searchable select over a large catalog\" viewport=\"460x320\"\n */\nexport function Combobox({\n label,\n hint,\n options = [],\n value,\n onChange,\n placeholder = '검색…',\n emptyText = '결과 없음',\n id,\n style,\n}: ComboboxProps) {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const [active, setActive] = useState(0);\n const rootRef = useRef<HTMLDivElement>(null);\n const cbId = id || (label ? `cb-${label.replace(/\\s+/g, '-')}` : undefined);\n\n const selected = options.find((o) => o.value === value) || null;\n\n useEffect(() => {\n const onDoc = (e: globalThis.MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n };\n document.addEventListener('mousedown', onDoc);\n return () => document.removeEventListener('mousedown', onDoc);\n }, []);\n\n const q = query.trim().toLowerCase();\n const filtered = q\n ? options.filter((o) => (o.label + ' ' + (o.meta || '')).toLowerCase().includes(q))\n : options;\n\n const commit = (o: ComboboxOption) => {\n onChange?.(o.value);\n setOpen(false);\n setQuery('');\n };\n\n const onKey = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setOpen(true);\n setActive((i) => Math.min(i + 1, filtered.length - 1));\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setActive((i) => Math.max(i - 1, 0));\n } else if (e.key === 'Enter' && open && filtered[active]) {\n e.preventDefault();\n commit(filtered[active]);\n } else if (e.key === 'Escape') {\n setOpen(false);\n }\n };\n\n return (\n <div ref={rootRef} style={{ display: 'grid', gap: 7, position: 'relative', ...style }}>\n {label ? (\n <label htmlFor={cbId} style={{ fontSize: 13, fontWeight: 600, color: 'var(--dt-muted-strong)' }}>\n {label}\n </label>\n ) : null}\n <div\n className=\"dt-field\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 9,\n height: 44,\n padding: '0 12px',\n boxShadow: open ? 'var(--dt-shadow-focus)' : undefined,\n background: open ? 'var(--dt-surface)' : 'var(--dt-surface-sunken)',\n }}\n onClick={() => setOpen(true)}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n style={{ color: 'var(--dt-muted)', flex: '0 0 auto' }}\n >\n <circle cx=\"11\" cy=\"11\" r=\"7\" stroke=\"currentColor\" strokeWidth=\"2\" />\n <path d=\"M21 21l-4-4\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" />\n </svg>\n <input\n id={cbId}\n value={open ? query : selected ? selected.label : ''}\n placeholder={selected && !open ? selected.label : placeholder}\n onChange={(e) => {\n setQuery(e.target.value);\n setOpen(true);\n setActive(0);\n }}\n onFocus={() => setOpen(true)}\n onKeyDown={onKey}\n style={{\n flex: 1,\n minWidth: 0,\n border: 'none',\n outline: 'none',\n background: 'transparent',\n fontSize: 14,\n fontFamily: 'inherit',\n color: 'var(--dt-ink-strong)',\n }}\n />\n {selected && !open ? (\n <span style={{ fontFamily: 'var(--dt-font-mono)', fontSize: 11, color: 'var(--dt-muted)' }}>\n {selected.meta}\n </span>\n ) : null}\n </div>\n\n {open ? (\n <div\n role=\"listbox\"\n style={{\n position: 'absolute',\n top: 'calc(100% + 6px)',\n left: 0,\n right: 0,\n zIndex: 20,\n background: 'var(--dt-surface)',\n border: '1px solid var(--dt-border-strong)',\n borderRadius: 'var(--dt-radius-lg)',\n boxShadow: 'var(--dt-shadow-md)',\n maxHeight: 240,\n overflowY: 'auto',\n padding: 4,\n }}\n >\n {filtered.length === 0 ? (\n <div style={{ padding: '12px 12px', fontSize: 13, color: 'var(--dt-muted)' }}>{emptyText}</div>\n ) : (\n filtered.map((o, i) => {\n const isActive = i === active;\n const isSel = o.value === value;\n return (\n <div\n key={o.value}\n role=\"option\"\n aria-selected={isSel}\n onMouseEnter={() => setActive(i)}\n onMouseDown={(e: MouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n commit(o);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '9px 10px',\n borderRadius: 'var(--dt-radius-md)',\n cursor: 'pointer',\n background: isActive ? 'var(--dt-surface-sunken)' : 'transparent',\n }}\n >\n <span\n style={{\n flex: 1,\n minWidth: 0,\n fontSize: 13.5,\n fontWeight: isSel ? 600 : 500,\n color: 'var(--dt-ink-strong)',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {o.label}\n </span>\n {o.meta ? (\n <span\n style={{\n fontFamily: 'var(--dt-font-mono)',\n fontSize: 11,\n color: 'var(--dt-muted)',\n flex: '0 0 auto',\n }}\n >\n {o.meta}\n </span>\n ) : null}\n {isSel ? (\n <svg\n width=\"15\"\n height=\"15\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n style={{ color: 'var(--dt-accent)', flex: '0 0 auto' }}\n >\n <path\n d=\"M20 6L9 17l-5-5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n ) : null}\n </div>\n );\n })\n )}\n </div>\n ) : null}\n {hint ? <span style={{ fontSize: 12, color: 'var(--dt-muted)' }}>{hint}</span> : null}\n </div>\n );\n}\n"]}
|