@diwauhris/ui 1.0.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 +75 -0
- package/lib/assets/ui.css +154 -0
- package/lib/index.cjs +183 -0
- package/lib/index.mjs +65 -0
- package/lib/index10.cjs +33 -0
- package/lib/index10.cjs.map +1 -0
- package/lib/index10.mjs +33 -0
- package/lib/index10.mjs.map +1 -0
- package/lib/index11.cjs +16 -0
- package/lib/index11.cjs.map +1 -0
- package/lib/index11.mjs +16 -0
- package/lib/index11.mjs.map +1 -0
- package/lib/index12.cjs +16 -0
- package/lib/index12.cjs.map +1 -0
- package/lib/index12.mjs +15 -0
- package/lib/index12.mjs.map +1 -0
- package/lib/index13.cjs +57 -0
- package/lib/index13.cjs.map +1 -0
- package/lib/index13.mjs +57 -0
- package/lib/index13.mjs.map +1 -0
- package/lib/index14.cjs +10 -0
- package/lib/index14.cjs.map +1 -0
- package/lib/index14.mjs +10 -0
- package/lib/index14.mjs.map +1 -0
- package/lib/index15.cjs +30 -0
- package/lib/index15.cjs.map +1 -0
- package/lib/index15.mjs +30 -0
- package/lib/index15.mjs.map +1 -0
- package/lib/index16.cjs +28 -0
- package/lib/index16.cjs.map +1 -0
- package/lib/index16.mjs +26 -0
- package/lib/index16.mjs.map +1 -0
- package/lib/index17.cjs +43 -0
- package/lib/index17.cjs.map +1 -0
- package/lib/index17.mjs +43 -0
- package/lib/index17.mjs.map +1 -0
- package/lib/index18.cjs +59 -0
- package/lib/index18.cjs.map +1 -0
- package/lib/index18.mjs +59 -0
- package/lib/index18.mjs.map +1 -0
- package/lib/index19.cjs +67 -0
- package/lib/index19.cjs.map +1 -0
- package/lib/index19.mjs +67 -0
- package/lib/index19.mjs.map +1 -0
- package/lib/index2.cjs +33 -0
- package/lib/index2.mjs +13 -0
- package/lib/index20.cjs +79 -0
- package/lib/index20.cjs.map +1 -0
- package/lib/index20.mjs +79 -0
- package/lib/index20.mjs.map +1 -0
- package/lib/index21.cjs +105 -0
- package/lib/index21.cjs.map +1 -0
- package/lib/index21.mjs +104 -0
- package/lib/index21.mjs.map +1 -0
- package/lib/index22.cjs +50 -0
- package/lib/index22.cjs.map +1 -0
- package/lib/index22.mjs +50 -0
- package/lib/index22.mjs.map +1 -0
- package/lib/index23.cjs +143 -0
- package/lib/index23.cjs.map +1 -0
- package/lib/index23.mjs +143 -0
- package/lib/index23.mjs.map +1 -0
- package/lib/index25.cjs +177 -0
- package/lib/index25.cjs.map +1 -0
- package/lib/index25.mjs +175 -0
- package/lib/index25.mjs.map +1 -0
- package/lib/index26.cjs +108 -0
- package/lib/index26.cjs.map +1 -0
- package/lib/index26.mjs +108 -0
- package/lib/index26.mjs.map +1 -0
- package/lib/index27.cjs +120 -0
- package/lib/index27.cjs.map +1 -0
- package/lib/index27.mjs +120 -0
- package/lib/index27.mjs.map +1 -0
- package/lib/index28.cjs +54 -0
- package/lib/index28.cjs.map +1 -0
- package/lib/index28.mjs +54 -0
- package/lib/index28.mjs.map +1 -0
- package/lib/index29.cjs +100 -0
- package/lib/index29.cjs.map +1 -0
- package/lib/index29.mjs +100 -0
- package/lib/index29.mjs.map +1 -0
- package/lib/index3.cjs +38 -0
- package/lib/index3.cjs.map +1 -0
- package/lib/index3.mjs +38 -0
- package/lib/index3.mjs.map +1 -0
- package/lib/index30.cjs +27 -0
- package/lib/index30.cjs.map +1 -0
- package/lib/index30.mjs +27 -0
- package/lib/index30.mjs.map +1 -0
- package/lib/index31.cjs +72 -0
- package/lib/index31.cjs.map +1 -0
- package/lib/index31.mjs +72 -0
- package/lib/index31.mjs.map +1 -0
- package/lib/index32.cjs +19 -0
- package/lib/index32.cjs.map +1 -0
- package/lib/index32.mjs +19 -0
- package/lib/index32.mjs.map +1 -0
- package/lib/index33.cjs +25 -0
- package/lib/index33.cjs.map +1 -0
- package/lib/index33.mjs +25 -0
- package/lib/index33.mjs.map +1 -0
- package/lib/index34.cjs +55 -0
- package/lib/index34.cjs.map +1 -0
- package/lib/index34.mjs +51 -0
- package/lib/index34.mjs.map +1 -0
- package/lib/index35.cjs +25 -0
- package/lib/index35.cjs.map +1 -0
- package/lib/index35.mjs +25 -0
- package/lib/index35.mjs.map +1 -0
- package/lib/index36.cjs +54 -0
- package/lib/index36.cjs.map +1 -0
- package/lib/index36.mjs +49 -0
- package/lib/index36.mjs.map +1 -0
- package/lib/index37.cjs +130 -0
- package/lib/index37.cjs.map +1 -0
- package/lib/index37.mjs +128 -0
- package/lib/index37.mjs.map +1 -0
- package/lib/index38.cjs +106 -0
- package/lib/index38.cjs.map +1 -0
- package/lib/index38.mjs +102 -0
- package/lib/index38.mjs.map +1 -0
- package/lib/index39.cjs +137 -0
- package/lib/index39.cjs.map +1 -0
- package/lib/index39.mjs +137 -0
- package/lib/index39.mjs.map +1 -0
- package/lib/index4.cjs +24 -0
- package/lib/index4.cjs.map +1 -0
- package/lib/index4.mjs +24 -0
- package/lib/index4.mjs.map +1 -0
- package/lib/index40.cjs +130 -0
- package/lib/index40.cjs.map +1 -0
- package/lib/index40.mjs +126 -0
- package/lib/index40.mjs.map +1 -0
- package/lib/index41.cjs +60 -0
- package/lib/index41.cjs.map +1 -0
- package/lib/index41.mjs +58 -0
- package/lib/index41.mjs.map +1 -0
- package/lib/index42.cjs +105 -0
- package/lib/index42.cjs.map +1 -0
- package/lib/index42.mjs +105 -0
- package/lib/index42.mjs.map +1 -0
- package/lib/index43.cjs +47 -0
- package/lib/index43.cjs.map +1 -0
- package/lib/index43.mjs +42 -0
- package/lib/index43.mjs.map +1 -0
- package/lib/index44.cjs +40 -0
- package/lib/index44.cjs.map +1 -0
- package/lib/index44.mjs +35 -0
- package/lib/index44.mjs.map +1 -0
- package/lib/index45.cjs +91 -0
- package/lib/index45.cjs.map +1 -0
- package/lib/index45.mjs +91 -0
- package/lib/index45.mjs.map +1 -0
- package/lib/index46.cjs +180 -0
- package/lib/index46.cjs.map +1 -0
- package/lib/index46.mjs +180 -0
- package/lib/index46.mjs.map +1 -0
- package/lib/index47.cjs +61 -0
- package/lib/index47.cjs.map +1 -0
- package/lib/index47.mjs +61 -0
- package/lib/index47.mjs.map +1 -0
- package/lib/index48.cjs +165 -0
- package/lib/index48.cjs.map +1 -0
- package/lib/index48.mjs +161 -0
- package/lib/index48.mjs.map +1 -0
- package/lib/index49.cjs +83 -0
- package/lib/index49.cjs.map +1 -0
- package/lib/index49.mjs +83 -0
- package/lib/index49.mjs.map +1 -0
- package/lib/index5.cjs +39 -0
- package/lib/index5.cjs.map +1 -0
- package/lib/index5.mjs +39 -0
- package/lib/index5.mjs.map +1 -0
- package/lib/index50.cjs +100 -0
- package/lib/index50.cjs.map +1 -0
- package/lib/index50.mjs +100 -0
- package/lib/index50.mjs.map +1 -0
- package/lib/index51.cjs +116 -0
- package/lib/index51.cjs.map +1 -0
- package/lib/index51.mjs +113 -0
- package/lib/index51.mjs.map +1 -0
- package/lib/index52.cjs +93 -0
- package/lib/index52.cjs.map +1 -0
- package/lib/index52.mjs +93 -0
- package/lib/index52.mjs.map +1 -0
- package/lib/index53.cjs +120 -0
- package/lib/index53.cjs.map +1 -0
- package/lib/index53.mjs +120 -0
- package/lib/index53.mjs.map +1 -0
- package/lib/index54.cjs +53 -0
- package/lib/index54.cjs.map +1 -0
- package/lib/index54.mjs +50 -0
- package/lib/index54.mjs.map +1 -0
- package/lib/index55.cjs +130 -0
- package/lib/index55.cjs.map +1 -0
- package/lib/index55.mjs +127 -0
- package/lib/index55.mjs.map +1 -0
- package/lib/index56.cjs +36 -0
- package/lib/index56.cjs.map +1 -0
- package/lib/index56.mjs +35 -0
- package/lib/index56.mjs.map +1 -0
- package/lib/index57.cjs +47 -0
- package/lib/index57.cjs.map +1 -0
- package/lib/index57.mjs +47 -0
- package/lib/index57.mjs.map +1 -0
- package/lib/index58.cjs +97 -0
- package/lib/index58.cjs.map +1 -0
- package/lib/index58.mjs +97 -0
- package/lib/index58.mjs.map +1 -0
- package/lib/index59.cjs +120 -0
- package/lib/index59.cjs.map +1 -0
- package/lib/index59.mjs +116 -0
- package/lib/index59.mjs.map +1 -0
- package/lib/index6.cjs +29 -0
- package/lib/index6.cjs.map +1 -0
- package/lib/index6.mjs +29 -0
- package/lib/index6.mjs.map +1 -0
- package/lib/index60.cjs +34 -0
- package/lib/index60.cjs.map +1 -0
- package/lib/index60.mjs +34 -0
- package/lib/index60.mjs.map +1 -0
- package/lib/index61.cjs +62 -0
- package/lib/index61.cjs.map +1 -0
- package/lib/index61.mjs +62 -0
- package/lib/index61.mjs.map +1 -0
- package/lib/index62.cjs +213 -0
- package/lib/index62.cjs.map +1 -0
- package/lib/index62.mjs +213 -0
- package/lib/index62.mjs.map +1 -0
- package/lib/index63.cjs +42 -0
- package/lib/index63.cjs.map +1 -0
- package/lib/index63.mjs +42 -0
- package/lib/index63.mjs.map +1 -0
- package/lib/index64.cjs +86 -0
- package/lib/index64.cjs.map +1 -0
- package/lib/index64.mjs +86 -0
- package/lib/index64.mjs.map +1 -0
- package/lib/index65.cjs +156 -0
- package/lib/index65.cjs.map +1 -0
- package/lib/index65.mjs +156 -0
- package/lib/index65.mjs.map +1 -0
- package/lib/index66.cjs +107 -0
- package/lib/index66.cjs.map +1 -0
- package/lib/index66.mjs +106 -0
- package/lib/index66.mjs.map +1 -0
- package/lib/index67.cjs +52 -0
- package/lib/index67.cjs.map +1 -0
- package/lib/index67.mjs +52 -0
- package/lib/index67.mjs.map +1 -0
- package/lib/index68.cjs +125 -0
- package/lib/index68.cjs.map +1 -0
- package/lib/index68.mjs +115 -0
- package/lib/index68.mjs.map +1 -0
- package/lib/index69.cjs +34 -0
- package/lib/index69.cjs.map +1 -0
- package/lib/index69.mjs +29 -0
- package/lib/index69.mjs.map +1 -0
- package/lib/index7.cjs +17 -0
- package/lib/index7.cjs.map +1 -0
- package/lib/index7.mjs +17 -0
- package/lib/index7.mjs.map +1 -0
- package/lib/index8.cjs +86 -0
- package/lib/index8.cjs.map +1 -0
- package/lib/index8.mjs +86 -0
- package/lib/index8.mjs.map +1 -0
- package/lib/index9.cjs +29 -0
- package/lib/index9.cjs.map +1 -0
- package/lib/index9.mjs +29 -0
- package/lib/index9.mjs.map +1 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +68 -0
package/lib/index50.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Check } from "lucide-react";
|
|
3
|
+
//#region src/ui-library/gallery/stepper/Stepper.tsx
|
|
4
|
+
function resolveState(id, currentStep, completedSteps, disabledSteps) {
|
|
5
|
+
if (disabledSteps.includes(id)) return "disabled";
|
|
6
|
+
if (completedSteps.includes(id)) return "completed";
|
|
7
|
+
if (id === currentStep) return "current";
|
|
8
|
+
return "upcoming";
|
|
9
|
+
}
|
|
10
|
+
function StepCircle({ state, number, icon }) {
|
|
11
|
+
return /* @__PURE__ */ jsx("div", {
|
|
12
|
+
className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-sm font-bold transition-all ${{
|
|
13
|
+
completed: "border-brand-blue bg-brand-blue text-white",
|
|
14
|
+
current: "border-brand-blue bg-white text-brand-blue shadow-md shadow-brand-blue/15",
|
|
15
|
+
upcoming: "border-slate-300 bg-white text-slate-400",
|
|
16
|
+
disabled: "border-slate-200 bg-slate-50 text-slate-300"
|
|
17
|
+
}[state]}`,
|
|
18
|
+
"aria-hidden": "true",
|
|
19
|
+
children: state === "completed" ? icon ?? /* @__PURE__ */ jsx(Check, {
|
|
20
|
+
size: 16,
|
|
21
|
+
strokeWidth: 3
|
|
22
|
+
}) : icon ?? /* @__PURE__ */ jsx("span", { children: number })
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [], orientation = "horizontal", className = "" }) {
|
|
26
|
+
const isHorizontal = orientation === "horizontal";
|
|
27
|
+
return /* @__PURE__ */ jsx("div", {
|
|
28
|
+
"aria-label": "Progress",
|
|
29
|
+
"aria-orientation": orientation,
|
|
30
|
+
className: [isHorizontal ? "flex items-start" : "flex flex-col", className].filter(Boolean).join(" "),
|
|
31
|
+
children: /* @__PURE__ */ jsx("ol", {
|
|
32
|
+
className: [isHorizontal ? "flex w-full items-start" : "flex flex-col"].filter(Boolean).join(" "),
|
|
33
|
+
children: steps.map((step, idx) => {
|
|
34
|
+
const state = resolveState(step.id, currentStep, completedSteps, disabledSteps);
|
|
35
|
+
const isCurrent = state === "current";
|
|
36
|
+
const isLast = idx === steps.length - 1;
|
|
37
|
+
const labelColor = {
|
|
38
|
+
completed: "text-brand-blue",
|
|
39
|
+
current: "text-brand-navy font-bold",
|
|
40
|
+
upcoming: "text-slate-500",
|
|
41
|
+
disabled: "text-slate-300"
|
|
42
|
+
};
|
|
43
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
44
|
+
"aria-current": isCurrent ? "step" : void 0,
|
|
45
|
+
className: isHorizontal ? "flex flex-1 items-start" : "flex items-start gap-4 pb-8 last:pb-0",
|
|
46
|
+
children: [
|
|
47
|
+
!isHorizontal && /* @__PURE__ */ jsxs("div", {
|
|
48
|
+
className: "relative flex flex-col items-center",
|
|
49
|
+
children: [/* @__PURE__ */ jsx(StepCircle, {
|
|
50
|
+
state,
|
|
51
|
+
number: idx + 1,
|
|
52
|
+
icon: step.icon
|
|
53
|
+
}), !isLast && /* @__PURE__ */ jsx("div", {
|
|
54
|
+
className: `mt-1 w-0.5 flex-1 min-h-[32px] ${state === "completed" ? "bg-brand-blue/50" : "bg-slate-200"}`,
|
|
55
|
+
"aria-hidden": "true"
|
|
56
|
+
})]
|
|
57
|
+
}),
|
|
58
|
+
isHorizontal && /* @__PURE__ */ jsxs("div", {
|
|
59
|
+
className: "flex flex-1 flex-col items-center",
|
|
60
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
61
|
+
className: "flex w-full items-center",
|
|
62
|
+
children: [/* @__PURE__ */ jsx(StepCircle, {
|
|
63
|
+
state,
|
|
64
|
+
number: idx + 1,
|
|
65
|
+
icon: step.icon
|
|
66
|
+
}), !isLast && /* @__PURE__ */ jsx("div", {
|
|
67
|
+
className: `h-0.5 flex-1 ${state === "completed" ? "bg-brand-blue/50" : "bg-slate-200"}`,
|
|
68
|
+
"aria-hidden": "true"
|
|
69
|
+
})]
|
|
70
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
71
|
+
className: "mt-2 text-center px-1",
|
|
72
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
73
|
+
className: `text-sm leading-tight ${labelColor[state]}`,
|
|
74
|
+
children: step.label
|
|
75
|
+
}), step.description && /* @__PURE__ */ jsx("p", {
|
|
76
|
+
className: "mt-0.5 text-xs text-slate-400 leading-tight",
|
|
77
|
+
children: step.description
|
|
78
|
+
})]
|
|
79
|
+
})]
|
|
80
|
+
}),
|
|
81
|
+
!isHorizontal && /* @__PURE__ */ jsxs("div", {
|
|
82
|
+
className: "min-w-0 flex-1 pt-1",
|
|
83
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
84
|
+
className: `text-sm leading-tight ${labelColor[state]}`,
|
|
85
|
+
children: step.label
|
|
86
|
+
}), step.description && /* @__PURE__ */ jsx("p", {
|
|
87
|
+
className: "mt-0.5 text-xs text-slate-400 leading-tight",
|
|
88
|
+
children: step.description
|
|
89
|
+
})]
|
|
90
|
+
})
|
|
91
|
+
]
|
|
92
|
+
}, step.id);
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { Stepper };
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=index50.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index50.mjs","names":[],"sources":["../src/ui-library/gallery/stepper/Stepper.tsx"],"sourcesContent":["/**\n * Stepper — Design System Component\n *\n * A step progress indicator for multi-step workflows.\n *\n * Step states: completed | current | upcoming | disabled\n *\n * Accessibility:\n * - The current step receives aria-current=\"step\".\n * - Completed steps have aria-label with a \"Completed\" suffix when no icon.\n * - The step list is an ordered list (<ol>) — each step is an <li>.\n * - Orientation is exposed via aria-orientation on the container.\n *\n * API:\n * <Stepper\n * steps={[\n * { id: '1', label: 'Personal Details', description: 'Name and contact' },\n * { id: '2', label: 'Employment', description: 'IDs and dates' },\n * ]}\n * currentStep=\"2\"\n * completedSteps={['1']}\n * />\n */\n\nimport type { ReactNode } from 'react';\nimport { Check } from 'lucide-react';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport type StepState = 'completed' | 'current' | 'upcoming' | 'disabled';\n\nexport interface StepDef {\n id: string;\n label: string;\n description?: string;\n icon?: ReactNode;\n}\n\nexport interface StepperProps {\n steps: StepDef[];\n /** The id of the currently active step */\n currentStep: string;\n /** IDs of steps that are completed */\n completedSteps?: string[];\n /** IDs of steps that are disabled */\n disabledSteps?: string[];\n orientation?: 'horizontal' | 'vertical';\n className?: string;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction resolveState(\n id: string,\n currentStep: string,\n completedSteps: string[],\n disabledSteps: string[],\n): StepState {\n if (disabledSteps.includes(id)) return 'disabled';\n if (completedSteps.includes(id)) return 'completed';\n if (id === currentStep) return 'current';\n return 'upcoming';\n}\n\n// ── Step indicator circle ─────────────────────────────────────────────────────\n\nfunction StepCircle({ state, number, icon }: { state: StepState; number: number; icon?: ReactNode }) {\n const base = 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-sm font-bold transition-all';\n\n const styles: Record<StepState, string> = {\n completed: 'border-brand-blue bg-brand-blue text-white',\n current: 'border-brand-blue bg-white text-brand-blue shadow-md shadow-brand-blue/15',\n upcoming: 'border-slate-300 bg-white text-slate-400',\n disabled: 'border-slate-200 bg-slate-50 text-slate-300',\n };\n\n return (\n <div className={`${base} ${styles[state]}`} aria-hidden=\"true\">\n {state === 'completed' ? (\n icon ?? <Check size={16} strokeWidth={3} />\n ) : (\n icon ?? <span>{number}</span>\n )}\n </div>\n );\n}\n\n// ── Stepper ───────────────────────────────────────────────────────────────────\n\nexport function Stepper({\n steps,\n currentStep,\n completedSteps = [],\n disabledSteps = [],\n orientation = 'horizontal',\n className = '',\n}: StepperProps) {\n const isHorizontal = orientation === 'horizontal';\n\n return (\n <div\n aria-label=\"Progress\"\n aria-orientation={orientation}\n className={[\n isHorizontal\n ? 'flex items-start'\n : 'flex flex-col',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <ol\n className={[\n isHorizontal ? 'flex w-full items-start' : 'flex flex-col',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {steps.map((step, idx) => {\n const state = resolveState(step.id, currentStep, completedSteps, disabledSteps);\n const isCurrent = state === 'current';\n const isLast = idx === steps.length - 1;\n\n const labelColor: Record<StepState, string> = {\n completed: 'text-brand-blue',\n current: 'text-brand-navy font-bold',\n upcoming: 'text-slate-500',\n disabled: 'text-slate-300',\n };\n\n return (\n <li\n key={step.id}\n aria-current={isCurrent ? 'step' : undefined}\n className={isHorizontal ? 'flex flex-1 items-start' : 'flex items-start gap-4 pb-8 last:pb-0'}\n >\n {/* Vertical connector + circle column */}\n {!isHorizontal && (\n <div className=\"relative flex flex-col items-center\">\n <StepCircle state={state} number={idx + 1} icon={step.icon} />\n {!isLast && (\n <div\n className={`mt-1 w-0.5 flex-1 min-h-[32px] ${\n state === 'completed' ? 'bg-brand-blue/50' : 'bg-slate-200'\n }`}\n aria-hidden=\"true\"\n />\n )}\n </div>\n )}\n\n {/* Horizontal: circle + connector */}\n {isHorizontal && (\n <div className=\"flex flex-1 flex-col items-center\">\n <div className=\"flex w-full items-center\">\n <StepCircle state={state} number={idx + 1} icon={step.icon} />\n {!isLast && (\n <div\n className={`h-0.5 flex-1 ${\n state === 'completed' ? 'bg-brand-blue/50' : 'bg-slate-200'\n }`}\n aria-hidden=\"true\"\n />\n )}\n </div>\n {/* Label below circle */}\n <div className=\"mt-2 text-center px-1\">\n <p className={`text-sm leading-tight ${labelColor[state]}`}>{step.label}</p>\n {step.description && (\n <p className=\"mt-0.5 text-xs text-slate-400 leading-tight\">{step.description}</p>\n )}\n </div>\n </div>\n )}\n\n {/* Vertical: label to the right */}\n {!isHorizontal && (\n <div className=\"min-w-0 flex-1 pt-1\">\n <p className={`text-sm leading-tight ${labelColor[state]}`}>{step.label}</p>\n {step.description && (\n <p className=\"mt-0.5 text-xs text-slate-400 leading-tight\">{step.description}</p>\n )}\n </div>\n )}\n </li>\n );\n })}\n </ol>\n </div>\n );\n}\n"],"mappings":";;;AAoDA,SAAS,aACP,IACA,aACA,gBACA,eACW;CACX,IAAI,cAAc,SAAS,EAAE,GAAG,OAAO;CACvC,IAAI,eAAe,SAAS,EAAE,GAAG,OAAO;CACxC,IAAI,OAAO,aAAa,OAAO;CAC/B,OAAO;AACT;AAIA,SAAS,WAAW,EAAE,OAAO,QAAQ,QAAgE;CAUnG,OACE,oBAAC,OAAD;EAAK,WAAW,4GAAW;GAP3B,WAAW;GACX,SAAW;GACX,UAAW;GACX,UAAW;EAIgB,EAAO;EAAU,eAAY;EACrD,UAAA,UAAU,cACT,QAAQ,oBAAC,OAAD;GAAO,MAAM;GAAI,aAAa;EAAI,CAAA,IAE1C,QAAQ,oBAAC,QAAD,EAAA,UAAO,OAAa,CAAA;CAE3B,CAAA;AAET;AAIA,SAAgB,QAAQ,EACtB,OACA,aACA,iBAAiB,CAAC,GAClB,gBAAiB,CAAC,GAClB,cAAc,cACd,YAAY,MACG;CACf,MAAM,eAAe,gBAAgB;CAErC,OACE,oBAAC,OAAD;EACE,cAAW;EACX,oBAAkB;EAClB,WAAW,CACT,eACI,qBACA,iBACJ,SACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;EAEX,UAAA,oBAAC,MAAD;GACE,WAAW,CACT,eAAe,4BAA4B,eAC7C,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV,UAAA,MAAM,KAAK,MAAM,QAAQ;IACxB,MAAM,QAAQ,aAAa,KAAK,IAAI,aAAa,gBAAgB,aAAa;IAC9E,MAAM,YAAY,UAAU;IAC5B,MAAM,SAAS,QAAQ,MAAM,SAAS;IAEtC,MAAM,aAAwC;KAC5C,WAAW;KACX,SAAW;KACX,UAAW;KACX,UAAW;IACb;IAEA,OACE,qBAAC,MAAD;KAEE,gBAAc,YAAY,SAAS,KAAA;KACnC,WAAW,eAAe,4BAA4B;KAHxD,UAAA;MAMG,CAAC,gBACA,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,YAAD;QAAmB;QAAO,QAAQ,MAAM;QAAG,MAAM,KAAK;OAAO,CAAA,GAC5D,CAAC,UACA,oBAAC,OAAD;QACE,WAAW,kCACT,UAAU,cAAc,qBAAqB;QAE/C,eAAY;OACb,CAAA,CAEA;;MAIN,gBACC,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,qBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,oBAAC,YAAD;SAAmB;SAAO,QAAQ,MAAM;SAAG,MAAM,KAAK;QAAO,CAAA,GAC5D,CAAC,UACA,oBAAC,OAAD;SACE,WAAW,gBACT,UAAU,cAAc,qBAAqB;SAE/C,eAAY;QACb,CAAA,CAEA;OAEL,CAAA,GAAA,qBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,oBAAC,KAAD;SAAG,WAAW,yBAAyB,WAAW;SAAW,UAAA,KAAK;QAAS,CAAA,GAC1E,KAAK,eACJ,oBAAC,KAAD;SAAG,WAAU;SAA+C,UAAA,KAAK;QAAe,CAAA,CAE/E;OACF,CAAA,CAAA;;MAIN,CAAC,gBACA,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,KAAD;QAAG,WAAW,yBAAyB,WAAW;QAAW,UAAA,KAAK;OAAS,CAAA,GAC1E,KAAK,eACJ,oBAAC,KAAD;QAAG,WAAU;QAA+C,UAAA,KAAK;OAAe,CAAA,CAE/E;;KAEL;IApDG,GAAA,KAAK,EAoDR;GAER,CAAC;EACC,CAAA;CACD,CAAA;AAET"}
|
package/lib/index51.cjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let lucide_react = require("lucide-react");
|
|
4
|
+
//#region src/ui-library/gallery/sidebar/Sidebar.tsx
|
|
5
|
+
/**
|
|
6
|
+
* Sidebar — Design System Component
|
|
7
|
+
*
|
|
8
|
+
* A vertical navigation panel. Supports grouped navigation sections,
|
|
9
|
+
* collapsible groups, active item state, icons, and badges.
|
|
10
|
+
*
|
|
11
|
+
* Accessibility:
|
|
12
|
+
* - Renders a <nav> landmark with aria-label.
|
|
13
|
+
* - Active item receives aria-current="page".
|
|
14
|
+
* - Collapsible group buttons have aria-expanded.
|
|
15
|
+
* - Collapsed content is hidden with aria-hidden + display:none equivalent.
|
|
16
|
+
*
|
|
17
|
+
* Composition:
|
|
18
|
+
* <SidebarNav aria-label="Main navigation">
|
|
19
|
+
* <SidebarSection label="Workspace">
|
|
20
|
+
* <SidebarItem href="/dashboard" icon={<LayoutDashboard />} active>Dashboard</SidebarItem>
|
|
21
|
+
* <SidebarItem href="/employees" icon={<Users />}>Employees</SidebarItem>
|
|
22
|
+
* <SidebarItem href="/reports" icon={<BarChart2 />} badge="3">Reports</SidebarItem>
|
|
23
|
+
* </SidebarSection>
|
|
24
|
+
* <SidebarGroup label="Settings" collapsible>
|
|
25
|
+
* <SidebarItem href="/settings/profile">Profile</SidebarItem>
|
|
26
|
+
* </SidebarGroup>
|
|
27
|
+
* </SidebarNav>
|
|
28
|
+
*/
|
|
29
|
+
function SidebarNav({ "aria-label": ariaLabel, children, className = "" }) {
|
|
30
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
|
|
31
|
+
"aria-label": ariaLabel,
|
|
32
|
+
className: `flex w-full flex-col gap-1 ${className}`,
|
|
33
|
+
children
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function SidebarSection({ label, children, className = "" }) {
|
|
37
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
38
|
+
className: `mb-3 ${className}`,
|
|
39
|
+
children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
40
|
+
className: "mb-1.5 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400",
|
|
41
|
+
children: label
|
|
42
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
43
|
+
className: "flex flex-col gap-0.5",
|
|
44
|
+
role: "list",
|
|
45
|
+
children
|
|
46
|
+
})]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function SidebarGroup({ label, defaultExpanded = true, children, className = "" }) {
|
|
50
|
+
const [expanded, setExpanded] = (0, react.useState)(defaultExpanded);
|
|
51
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
52
|
+
className: `mb-2 ${className}`,
|
|
53
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
54
|
+
type: "button",
|
|
55
|
+
"aria-expanded": expanded,
|
|
56
|
+
onClick: () => setExpanded(!expanded),
|
|
57
|
+
className: "flex w-full items-center justify-between rounded-lg px-3 py-2 text-[10px] font-bold uppercase tracking-widest text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
58
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
|
|
59
|
+
size: 12,
|
|
60
|
+
"aria-hidden": "true",
|
|
61
|
+
className: `transition-transform duration-200 ${expanded ? "" : "-rotate-90"}`
|
|
62
|
+
})]
|
|
63
|
+
}), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
64
|
+
className: "flex flex-col gap-0.5 mt-0.5",
|
|
65
|
+
role: "list",
|
|
66
|
+
children
|
|
67
|
+
})]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function SidebarItem({ href, onClick, active = false, icon, badge, disabled = false, children, className = "" }) {
|
|
71
|
+
const baseClass = [
|
|
72
|
+
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-semibold transition",
|
|
73
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
74
|
+
active ? "bg-blue-50 text-brand-blue" : disabled ? "cursor-not-allowed text-slate-300" : "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
|
75
|
+
className
|
|
76
|
+
].filter(Boolean).join(" ");
|
|
77
|
+
const content = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
78
|
+
icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
79
|
+
className: `shrink-0 ${active ? "text-brand-blue" : "text-slate-400"}`,
|
|
80
|
+
"aria-hidden": "true",
|
|
81
|
+
children: icon
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
84
|
+
className: "flex-1 truncate",
|
|
85
|
+
children
|
|
86
|
+
}),
|
|
87
|
+
badge !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
88
|
+
className: `ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${active ? "bg-blue-50 text-brand-blue" : "bg-slate-100 text-slate-500"}`,
|
|
89
|
+
"aria-label": `${badge} items`,
|
|
90
|
+
children: badge
|
|
91
|
+
})
|
|
92
|
+
] });
|
|
93
|
+
if (href && !disabled) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
94
|
+
href,
|
|
95
|
+
"aria-current": active ? "page" : void 0,
|
|
96
|
+
className: baseClass,
|
|
97
|
+
onClick,
|
|
98
|
+
children: content
|
|
99
|
+
}) });
|
|
100
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
101
|
+
type: "button",
|
|
102
|
+
"aria-current": active ? "page" : void 0,
|
|
103
|
+
"aria-disabled": disabled || void 0,
|
|
104
|
+
disabled,
|
|
105
|
+
onClick: !disabled ? onClick : void 0,
|
|
106
|
+
className: baseClass,
|
|
107
|
+
children: content
|
|
108
|
+
}) });
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
exports.SidebarGroup = SidebarGroup;
|
|
112
|
+
exports.SidebarItem = SidebarItem;
|
|
113
|
+
exports.SidebarNav = SidebarNav;
|
|
114
|
+
exports.SidebarSection = SidebarSection;
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=index51.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index51.cjs","names":[],"sources":["../src/ui-library/gallery/sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * Sidebar — Design System Component\n *\n * A vertical navigation panel. Supports grouped navigation sections,\n * collapsible groups, active item state, icons, and badges.\n *\n * Accessibility:\n * - Renders a <nav> landmark with aria-label.\n * - Active item receives aria-current=\"page\".\n * - Collapsible group buttons have aria-expanded.\n * - Collapsed content is hidden with aria-hidden + display:none equivalent.\n *\n * Composition:\n * <SidebarNav aria-label=\"Main navigation\">\n * <SidebarSection label=\"Workspace\">\n * <SidebarItem href=\"/dashboard\" icon={<LayoutDashboard />} active>Dashboard</SidebarItem>\n * <SidebarItem href=\"/employees\" icon={<Users />}>Employees</SidebarItem>\n * <SidebarItem href=\"/reports\" icon={<BarChart2 />} badge=\"3\">Reports</SidebarItem>\n * </SidebarSection>\n * <SidebarGroup label=\"Settings\" collapsible>\n * <SidebarItem href=\"/settings/profile\">Profile</SidebarItem>\n * </SidebarGroup>\n * </SidebarNav>\n */\n\nimport { useState, type ReactNode } from 'react';\nimport { ChevronDown } from 'lucide-react';\n\n// ── SidebarNav ────────────────────────────────────────────────────────────────\n\nexport interface SidebarNavProps {\n 'aria-label': string;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarNav({ 'aria-label': ariaLabel, children, className = '' }: SidebarNavProps) {\n return (\n <nav\n aria-label={ariaLabel}\n className={`flex w-full flex-col gap-1 ${className}`}\n >\n {children}\n </nav>\n );\n}\n\n// ── SidebarSection ────────────────────────────────────────────────────────────\n// A non-collapsible labeled group.\n\nexport interface SidebarSectionProps {\n label?: string;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarSection({ label, children, className = '' }: SidebarSectionProps) {\n return (\n <div className={`mb-3 ${className}`}>\n {label && (\n <p className=\"mb-1.5 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400\">\n {label}\n </p>\n )}\n <ul className=\"flex flex-col gap-0.5\" role=\"list\">\n {children}\n </ul>\n </div>\n );\n}\n\n// ── SidebarGroup ──────────────────────────────────────────────────────────────\n// A collapsible labeled group.\n\nexport interface SidebarGroupProps {\n label: string;\n defaultExpanded?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarGroup({ label, defaultExpanded = true, children, className = '' }: SidebarGroupProps) {\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n return (\n <div className={`mb-2 ${className}`}>\n <button\n type=\"button\"\n aria-expanded={expanded}\n onClick={() => setExpanded(!expanded)}\n className=\"flex w-full items-center justify-between rounded-lg px-3 py-2 text-[10px] font-bold uppercase tracking-widest text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <span>{label}</span>\n <ChevronDown\n size={12}\n aria-hidden=\"true\"\n className={`transition-transform duration-200 ${expanded ? '' : '-rotate-90'}`}\n />\n </button>\n {expanded && (\n <ul className=\"flex flex-col gap-0.5 mt-0.5\" role=\"list\">\n {children}\n </ul>\n )}\n </div>\n );\n}\n\n// ── SidebarItem ───────────────────────────────────────────────────────────────\n\nexport interface SidebarItemProps {\n href?: string;\n onClick?: () => void;\n active?: boolean;\n icon?: ReactNode;\n badge?: string | number;\n disabled?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarItem({\n href,\n onClick,\n active = false,\n icon,\n badge,\n disabled = false,\n children,\n className = '',\n}: SidebarItemProps) {\n const baseClass = [\n 'flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-semibold transition',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n active\n ? 'bg-blue-50 text-brand-blue'\n : disabled\n ? 'cursor-not-allowed text-slate-300'\n : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const content = (\n <>\n {icon && (\n <span className={`shrink-0 ${active ? 'text-brand-blue' : 'text-slate-400'}`} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <span className=\"flex-1 truncate\">{children}</span>\n {badge !== undefined && (\n <span\n className={`ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${\n active ? 'bg-blue-50 text-brand-blue' : 'bg-slate-100 text-slate-500'\n }`}\n aria-label={`${badge} items`}\n >\n {badge}\n </span>\n )}\n </>\n );\n\n if (href && !disabled) {\n return (\n <li>\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className={baseClass}\n onClick={onClick}\n >\n {content}\n </a>\n </li>\n );\n }\n\n return (\n <li>\n <button\n type=\"button\"\n aria-current={active ? 'page' : undefined}\n aria-disabled={disabled || undefined}\n disabled={disabled}\n onClick={!disabled ? onClick : undefined}\n className={baseClass}\n >\n {content}\n </button>\n </li>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,WAAW,EAAE,cAAc,WAAW,UAAU,YAAY,MAAuB;CACjG,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EACE,cAAY;EACZ,WAAW,8BAA8B;EAExC;CACE,CAAA;AAET;AAWA,SAAgB,eAAe,EAAE,OAAO,UAAU,YAAY,MAA2B;CACvF,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAW,QAAQ;EAAxB,UAAA,CACG,SACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;GAAG,WAAU;GACV,UAAA;EACA,CAAA,GAEL,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;GAAI,WAAU;GAAwB,MAAK;GACxC;EACC,CAAA,CACD;;AAET;AAYA,SAAgB,aAAa,EAAE,OAAO,kBAAkB,MAAM,UAAU,YAAY,MAAyB;CAC3G,MAAM,CAAC,UAAU,gBAAA,GAAe,MAAA,SAAA,CAAS,eAAe;CAExD,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAW,QAAQ;EAAxB,UAAA,CACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,UAAD;GACE,MAAK;GACL,iBAAe;GACf,eAAe,YAAY,CAAC,QAAQ;GACpC,WAAU;GAJZ,UAAA,CAME,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD,EAAA,UAAO,MAAY,CAAA,GACnB,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAA,aAAD;IACE,MAAM;IACN,eAAY;IACZ,WAAW,qCAAqC,WAAW,KAAK;GACjE,CAAA,CACK;EACP,CAAA,GAAA,YACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;GAAI,WAAU;GAA+B,MAAK;GAC/C;EACC,CAAA,CAEH;;AAET;AAeA,SAAgB,YAAY,EAC1B,MACA,SACA,SAAS,OACT,MACA,OACA,WAAW,OACX,UACA,YAAY,MACO;CACnB,MAAM,YAAY;EAChB;EACA;EACA,SACI,+BACA,WACE,sCACA;EACN;CACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;CAEX,MAAM,UACJ,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA;EACG,QACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,WAAW,YAAY,SAAS,oBAAoB;GAAoB,eAAY;GACvF,UAAA;EACG,CAAA;EAER,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,WAAU;GAAmB;EAAe,CAAA;EACjD,UAAU,KAAA,KACT,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GACE,WAAW,qEACT,SAAS,+BAA+B;GAE1C,cAAY,GAAG,MAAM;GAEpB,UAAA;EACG,CAAA;CAER,EAAA,CAAA;CAGJ,IAAI,QAAQ,CAAC,UACX,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;EACQ;EACN,gBAAc,SAAS,SAAS,KAAA;EAChC,WAAW;EACF;EAER,UAAA;CACA,CAAA,EACD,CAAA;CAIR,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;EACE,MAAK;EACL,gBAAc,SAAS,SAAS,KAAA;EAChC,iBAAe,YAAY,KAAA;EACjB;EACV,SAAS,CAAC,WAAW,UAAU,KAAA;EAC/B,WAAW;EAEV,UAAA;CACK,CAAA,EACN,CAAA;AAER"}
|
package/lib/index51.mjs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ChevronDown } from "lucide-react";
|
|
4
|
+
//#region src/ui-library/gallery/sidebar/Sidebar.tsx
|
|
5
|
+
/**
|
|
6
|
+
* Sidebar — Design System Component
|
|
7
|
+
*
|
|
8
|
+
* A vertical navigation panel. Supports grouped navigation sections,
|
|
9
|
+
* collapsible groups, active item state, icons, and badges.
|
|
10
|
+
*
|
|
11
|
+
* Accessibility:
|
|
12
|
+
* - Renders a <nav> landmark with aria-label.
|
|
13
|
+
* - Active item receives aria-current="page".
|
|
14
|
+
* - Collapsible group buttons have aria-expanded.
|
|
15
|
+
* - Collapsed content is hidden with aria-hidden + display:none equivalent.
|
|
16
|
+
*
|
|
17
|
+
* Composition:
|
|
18
|
+
* <SidebarNav aria-label="Main navigation">
|
|
19
|
+
* <SidebarSection label="Workspace">
|
|
20
|
+
* <SidebarItem href="/dashboard" icon={<LayoutDashboard />} active>Dashboard</SidebarItem>
|
|
21
|
+
* <SidebarItem href="/employees" icon={<Users />}>Employees</SidebarItem>
|
|
22
|
+
* <SidebarItem href="/reports" icon={<BarChart2 />} badge="3">Reports</SidebarItem>
|
|
23
|
+
* </SidebarSection>
|
|
24
|
+
* <SidebarGroup label="Settings" collapsible>
|
|
25
|
+
* <SidebarItem href="/settings/profile">Profile</SidebarItem>
|
|
26
|
+
* </SidebarGroup>
|
|
27
|
+
* </SidebarNav>
|
|
28
|
+
*/
|
|
29
|
+
function SidebarNav({ "aria-label": ariaLabel, children, className = "" }) {
|
|
30
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
31
|
+
"aria-label": ariaLabel,
|
|
32
|
+
className: `flex w-full flex-col gap-1 ${className}`,
|
|
33
|
+
children
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function SidebarSection({ label, children, className = "" }) {
|
|
37
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
38
|
+
className: `mb-3 ${className}`,
|
|
39
|
+
children: [label && /* @__PURE__ */ jsx("p", {
|
|
40
|
+
className: "mb-1.5 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400",
|
|
41
|
+
children: label
|
|
42
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
43
|
+
className: "flex flex-col gap-0.5",
|
|
44
|
+
role: "list",
|
|
45
|
+
children
|
|
46
|
+
})]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function SidebarGroup({ label, defaultExpanded = true, children, className = "" }) {
|
|
50
|
+
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
51
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
52
|
+
className: `mb-2 ${className}`,
|
|
53
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
54
|
+
type: "button",
|
|
55
|
+
"aria-expanded": expanded,
|
|
56
|
+
onClick: () => setExpanded(!expanded),
|
|
57
|
+
className: "flex w-full items-center justify-between rounded-lg px-3 py-2 text-[10px] font-bold uppercase tracking-widest text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
58
|
+
children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx(ChevronDown, {
|
|
59
|
+
size: 12,
|
|
60
|
+
"aria-hidden": "true",
|
|
61
|
+
className: `transition-transform duration-200 ${expanded ? "" : "-rotate-90"}`
|
|
62
|
+
})]
|
|
63
|
+
}), expanded && /* @__PURE__ */ jsx("ul", {
|
|
64
|
+
className: "flex flex-col gap-0.5 mt-0.5",
|
|
65
|
+
role: "list",
|
|
66
|
+
children
|
|
67
|
+
})]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function SidebarItem({ href, onClick, active = false, icon, badge, disabled = false, children, className = "" }) {
|
|
71
|
+
const baseClass = [
|
|
72
|
+
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-semibold transition",
|
|
73
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
74
|
+
active ? "bg-blue-50 text-brand-blue" : disabled ? "cursor-not-allowed text-slate-300" : "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
|
75
|
+
className
|
|
76
|
+
].filter(Boolean).join(" ");
|
|
77
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
78
|
+
icon && /* @__PURE__ */ jsx("span", {
|
|
79
|
+
className: `shrink-0 ${active ? "text-brand-blue" : "text-slate-400"}`,
|
|
80
|
+
"aria-hidden": "true",
|
|
81
|
+
children: icon
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ jsx("span", {
|
|
84
|
+
className: "flex-1 truncate",
|
|
85
|
+
children
|
|
86
|
+
}),
|
|
87
|
+
badge !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
88
|
+
className: `ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${active ? "bg-blue-50 text-brand-blue" : "bg-slate-100 text-slate-500"}`,
|
|
89
|
+
"aria-label": `${badge} items`,
|
|
90
|
+
children: badge
|
|
91
|
+
})
|
|
92
|
+
] });
|
|
93
|
+
if (href && !disabled) return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
|
|
94
|
+
href,
|
|
95
|
+
"aria-current": active ? "page" : void 0,
|
|
96
|
+
className: baseClass,
|
|
97
|
+
onClick,
|
|
98
|
+
children: content
|
|
99
|
+
}) });
|
|
100
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
101
|
+
type: "button",
|
|
102
|
+
"aria-current": active ? "page" : void 0,
|
|
103
|
+
"aria-disabled": disabled || void 0,
|
|
104
|
+
disabled,
|
|
105
|
+
onClick: !disabled ? onClick : void 0,
|
|
106
|
+
className: baseClass,
|
|
107
|
+
children: content
|
|
108
|
+
}) });
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { SidebarGroup, SidebarItem, SidebarNav, SidebarSection };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=index51.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index51.mjs","names":[],"sources":["../src/ui-library/gallery/sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * Sidebar — Design System Component\n *\n * A vertical navigation panel. Supports grouped navigation sections,\n * collapsible groups, active item state, icons, and badges.\n *\n * Accessibility:\n * - Renders a <nav> landmark with aria-label.\n * - Active item receives aria-current=\"page\".\n * - Collapsible group buttons have aria-expanded.\n * - Collapsed content is hidden with aria-hidden + display:none equivalent.\n *\n * Composition:\n * <SidebarNav aria-label=\"Main navigation\">\n * <SidebarSection label=\"Workspace\">\n * <SidebarItem href=\"/dashboard\" icon={<LayoutDashboard />} active>Dashboard</SidebarItem>\n * <SidebarItem href=\"/employees\" icon={<Users />}>Employees</SidebarItem>\n * <SidebarItem href=\"/reports\" icon={<BarChart2 />} badge=\"3\">Reports</SidebarItem>\n * </SidebarSection>\n * <SidebarGroup label=\"Settings\" collapsible>\n * <SidebarItem href=\"/settings/profile\">Profile</SidebarItem>\n * </SidebarGroup>\n * </SidebarNav>\n */\n\nimport { useState, type ReactNode } from 'react';\nimport { ChevronDown } from 'lucide-react';\n\n// ── SidebarNav ────────────────────────────────────────────────────────────────\n\nexport interface SidebarNavProps {\n 'aria-label': string;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarNav({ 'aria-label': ariaLabel, children, className = '' }: SidebarNavProps) {\n return (\n <nav\n aria-label={ariaLabel}\n className={`flex w-full flex-col gap-1 ${className}`}\n >\n {children}\n </nav>\n );\n}\n\n// ── SidebarSection ────────────────────────────────────────────────────────────\n// A non-collapsible labeled group.\n\nexport interface SidebarSectionProps {\n label?: string;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarSection({ label, children, className = '' }: SidebarSectionProps) {\n return (\n <div className={`mb-3 ${className}`}>\n {label && (\n <p className=\"mb-1.5 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400\">\n {label}\n </p>\n )}\n <ul className=\"flex flex-col gap-0.5\" role=\"list\">\n {children}\n </ul>\n </div>\n );\n}\n\n// ── SidebarGroup ──────────────────────────────────────────────────────────────\n// A collapsible labeled group.\n\nexport interface SidebarGroupProps {\n label: string;\n defaultExpanded?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarGroup({ label, defaultExpanded = true, children, className = '' }: SidebarGroupProps) {\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n return (\n <div className={`mb-2 ${className}`}>\n <button\n type=\"button\"\n aria-expanded={expanded}\n onClick={() => setExpanded(!expanded)}\n className=\"flex w-full items-center justify-between rounded-lg px-3 py-2 text-[10px] font-bold uppercase tracking-widest text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <span>{label}</span>\n <ChevronDown\n size={12}\n aria-hidden=\"true\"\n className={`transition-transform duration-200 ${expanded ? '' : '-rotate-90'}`}\n />\n </button>\n {expanded && (\n <ul className=\"flex flex-col gap-0.5 mt-0.5\" role=\"list\">\n {children}\n </ul>\n )}\n </div>\n );\n}\n\n// ── SidebarItem ───────────────────────────────────────────────────────────────\n\nexport interface SidebarItemProps {\n href?: string;\n onClick?: () => void;\n active?: boolean;\n icon?: ReactNode;\n badge?: string | number;\n disabled?: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function SidebarItem({\n href,\n onClick,\n active = false,\n icon,\n badge,\n disabled = false,\n children,\n className = '',\n}: SidebarItemProps) {\n const baseClass = [\n 'flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-semibold transition',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n active\n ? 'bg-blue-50 text-brand-blue'\n : disabled\n ? 'cursor-not-allowed text-slate-300'\n : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const content = (\n <>\n {icon && (\n <span className={`shrink-0 ${active ? 'text-brand-blue' : 'text-slate-400'}`} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <span className=\"flex-1 truncate\">{children}</span>\n {badge !== undefined && (\n <span\n className={`ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${\n active ? 'bg-blue-50 text-brand-blue' : 'bg-slate-100 text-slate-500'\n }`}\n aria-label={`${badge} items`}\n >\n {badge}\n </span>\n )}\n </>\n );\n\n if (href && !disabled) {\n return (\n <li>\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className={baseClass}\n onClick={onClick}\n >\n {content}\n </a>\n </li>\n );\n }\n\n return (\n <li>\n <button\n type=\"button\"\n aria-current={active ? 'page' : undefined}\n aria-disabled={disabled || undefined}\n disabled={disabled}\n onClick={!disabled ? onClick : undefined}\n className={baseClass}\n >\n {content}\n </button>\n </li>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,WAAW,EAAE,cAAc,WAAW,UAAU,YAAY,MAAuB;CACjG,OACE,oBAAC,OAAD;EACE,cAAY;EACZ,WAAW,8BAA8B;EAExC;CACE,CAAA;AAET;AAWA,SAAgB,eAAe,EAAE,OAAO,UAAU,YAAY,MAA2B;CACvF,OACE,qBAAC,OAAD;EAAK,WAAW,QAAQ;EAAxB,UAAA,CACG,SACC,oBAAC,KAAD;GAAG,WAAU;GACV,UAAA;EACA,CAAA,GAEL,oBAAC,MAAD;GAAI,WAAU;GAAwB,MAAK;GACxC;EACC,CAAA,CACD;;AAET;AAYA,SAAgB,aAAa,EAAE,OAAO,kBAAkB,MAAM,UAAU,YAAY,MAAyB;CAC3G,MAAM,CAAC,UAAU,eAAe,SAAS,eAAe;CAExD,OACE,qBAAC,OAAD;EAAK,WAAW,QAAQ;EAAxB,UAAA,CACE,qBAAC,UAAD;GACE,MAAK;GACL,iBAAe;GACf,eAAe,YAAY,CAAC,QAAQ;GACpC,WAAU;GAJZ,UAAA,CAME,oBAAC,QAAD,EAAA,UAAO,MAAY,CAAA,GACnB,oBAAC,aAAD;IACE,MAAM;IACN,eAAY;IACZ,WAAW,qCAAqC,WAAW,KAAK;GACjE,CAAA,CACK;EACP,CAAA,GAAA,YACC,oBAAC,MAAD;GAAI,WAAU;GAA+B,MAAK;GAC/C;EACC,CAAA,CAEH;;AAET;AAeA,SAAgB,YAAY,EAC1B,MACA,SACA,SAAS,OACT,MACA,OACA,WAAW,OACX,UACA,YAAY,MACO;CACnB,MAAM,YAAY;EAChB;EACA;EACA,SACI,+BACA,WACE,sCACA;EACN;CACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;CAEX,MAAM,UACJ,qBAAA,UAAA,EAAA,UAAA;EACG,QACC,oBAAC,QAAD;GAAM,WAAW,YAAY,SAAS,oBAAoB;GAAoB,eAAY;GACvF,UAAA;EACG,CAAA;EAER,oBAAC,QAAD;GAAM,WAAU;GAAmB;EAAe,CAAA;EACjD,UAAU,KAAA,KACT,oBAAC,QAAD;GACE,WAAW,qEACT,SAAS,+BAA+B;GAE1C,cAAY,GAAG,MAAM;GAEpB,UAAA;EACG,CAAA;CAER,EAAA,CAAA;CAGJ,IAAI,QAAQ,CAAC,UACX,OACE,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;EACQ;EACN,gBAAc,SAAS,SAAS,KAAA;EAChC,WAAW;EACF;EAER,UAAA;CACA,CAAA,EACD,CAAA;CAIR,OACE,oBAAC,MAAD,EAAA,UACE,oBAAC,UAAD;EACE,MAAK;EACL,gBAAc,SAAS,SAAS,KAAA;EAChC,iBAAe,YAAY,KAAA;EACjB;EACV,SAAS,CAAC,WAAW,UAAU,KAAA;EAC/B,WAAW;EAEV,UAAA;CACK,CAAA,EACN,CAAA;AAER"}
|
package/lib/index52.cjs
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
//#region src/ui-library/gallery/dropdown/Dropdown.tsx
|
|
4
|
+
/**
|
|
5
|
+
* Dropdown — Design System Component
|
|
6
|
+
*
|
|
7
|
+
* A controlled floating panel anchored to a trigger button.
|
|
8
|
+
* The consumer owns the open state, trigger content, and panel content.
|
|
9
|
+
*
|
|
10
|
+
* Responsibilities:
|
|
11
|
+
* - Trigger button with aria-expanded, aria-haspopup, aria-controls
|
|
12
|
+
* - Floating panel anchored below the trigger
|
|
13
|
+
* - Close on outside click (mousedown)
|
|
14
|
+
* - Close on Escape key
|
|
15
|
+
* - Stable panel ID per instance
|
|
16
|
+
*
|
|
17
|
+
* This component is intentionally presentation-only. It does not:
|
|
18
|
+
* - Manage selected options
|
|
19
|
+
* - Impose role="listbox" or role="option" on children
|
|
20
|
+
* - Handle async data
|
|
21
|
+
*
|
|
22
|
+
* Consumers are responsible for the trigger layout, option semantics,
|
|
23
|
+
* and any keyboard navigation within the panel.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* <Dropdown
|
|
27
|
+
* trigger={<><Filter size={16} /><span>Filter</span><ChevronDown size={14} /></>}
|
|
28
|
+
* isOpen={open}
|
|
29
|
+
* onToggle={() => setOpen(!open)}
|
|
30
|
+
* onClose={() => setOpen(false)}
|
|
31
|
+
* >
|
|
32
|
+
* <button onClick={() => select('all')}>All</button>
|
|
33
|
+
* <button onClick={() => select('active')}>Active</button>
|
|
34
|
+
* </Dropdown>
|
|
35
|
+
*/
|
|
36
|
+
function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassName = "", panelClassName = "" }) {
|
|
37
|
+
const ref = (0, react.useRef)(null);
|
|
38
|
+
const triggerRef = (0, react.useRef)(null);
|
|
39
|
+
const panelId = `${(0, react.useId)().replace(/:/g, "")}-panel`;
|
|
40
|
+
(0, react.useEffect)(() => {
|
|
41
|
+
if (!isOpen) return;
|
|
42
|
+
const handlePointerDown = (e) => {
|
|
43
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
44
|
+
onClose();
|
|
45
|
+
triggerRef.current?.focus();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
49
|
+
return () => document.removeEventListener("mousedown", handlePointerDown);
|
|
50
|
+
}, [isOpen, onClose]);
|
|
51
|
+
(0, react.useEffect)(() => {
|
|
52
|
+
if (!isOpen) return;
|
|
53
|
+
const handleKeyDown = (e) => {
|
|
54
|
+
if (e.key === "Escape") {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
onClose();
|
|
57
|
+
triggerRef.current?.focus();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
61
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
62
|
+
}, [isOpen, onClose]);
|
|
63
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
64
|
+
className: "relative inline-block",
|
|
65
|
+
ref,
|
|
66
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
67
|
+
ref: triggerRef,
|
|
68
|
+
type: "button",
|
|
69
|
+
onClick: onToggle,
|
|
70
|
+
"aria-expanded": isOpen,
|
|
71
|
+
"aria-haspopup": "true",
|
|
72
|
+
"aria-controls": isOpen ? panelId : void 0,
|
|
73
|
+
className: [
|
|
74
|
+
"inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all",
|
|
75
|
+
"hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
76
|
+
triggerClassName
|
|
77
|
+
].filter(Boolean).join(" "),
|
|
78
|
+
children: trigger
|
|
79
|
+
}), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
80
|
+
id: panelId,
|
|
81
|
+
className: [
|
|
82
|
+
"absolute top-full left-0 z-20 mt-1 min-w-[180px] max-h-[280px] overflow-y-auto",
|
|
83
|
+
"rounded-xl border border-slate-200 bg-white shadow-lg",
|
|
84
|
+
panelClassName
|
|
85
|
+
].filter(Boolean).join(" "),
|
|
86
|
+
children
|
|
87
|
+
})]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
exports.Dropdown = Dropdown;
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=index52.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index52.cjs","names":[],"sources":["../src/ui-library/gallery/dropdown/Dropdown.tsx"],"sourcesContent":["/**\n * Dropdown — Design System Component\n *\n * A controlled floating panel anchored to a trigger button.\n * The consumer owns the open state, trigger content, and panel content.\n *\n * Responsibilities:\n * - Trigger button with aria-expanded, aria-haspopup, aria-controls\n * - Floating panel anchored below the trigger\n * - Close on outside click (mousedown)\n * - Close on Escape key\n * - Stable panel ID per instance\n *\n * This component is intentionally presentation-only. It does not:\n * - Manage selected options\n * - Impose role=\"listbox\" or role=\"option\" on children\n * - Handle async data\n *\n * Consumers are responsible for the trigger layout, option semantics,\n * and any keyboard navigation within the panel.\n *\n * Usage:\n * <Dropdown\n * trigger={<><Filter size={16} /><span>Filter</span><ChevronDown size={14} /></>}\n * isOpen={open}\n * onToggle={() => setOpen(!open)}\n * onClose={() => setOpen(false)}\n * >\n * <button onClick={() => select('all')}>All</button>\n * <button onClick={() => select('active')}>Active</button>\n * </Dropdown>\n */\n\nimport { useEffect, useId, useRef, type ReactNode } from 'react';\n\nexport interface DropdownProps {\n /** Complete trigger content — icons, label, chevron — owned by consumer. */\n trigger: ReactNode;\n /** Whether the panel is open. Owned by the consumer. */\n isOpen: boolean;\n /** Called when the trigger button is clicked. Consumer toggles isOpen. */\n onToggle: () => void;\n /** Called on outside click or Escape press. Consumer sets isOpen to false. */\n onClose: () => void;\n /** Panel content — option buttons or any children. */\n children: ReactNode;\n /** Optional className on the trigger button */\n triggerClassName?: string;\n /** Optional className on the floating panel */\n panelClassName?: string;\n}\n\nexport function Dropdown({\n trigger,\n isOpen,\n onToggle,\n onClose,\n children,\n triggerClassName = '',\n panelClassName = '',\n}: DropdownProps) {\n const ref = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const uid = useId();\n const panelId = `${uid.replace(/:/g, '')}-panel`;\n\n // Close on outside mousedown — return focus to trigger\n useEffect(() => {\n if (!isOpen) return;\n const handlePointerDown = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n onClose();\n triggerRef.current?.focus();\n }\n };\n document.addEventListener('mousedown', handlePointerDown);\n return () => document.removeEventListener('mousedown', handlePointerDown);\n }, [isOpen, onClose]);\n\n // Close on Escape — return focus to trigger\n useEffect(() => {\n if (!isOpen) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n onClose();\n triggerRef.current?.focus();\n }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, onClose]);\n\n return (\n <div className=\"relative inline-block\" ref={ref}>\n <button\n ref={triggerRef}\n type=\"button\"\n onClick={onToggle}\n aria-expanded={isOpen}\n aria-haspopup=\"true\"\n aria-controls={isOpen ? panelId : undefined}\n className={[\n 'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all',\n 'hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n triggerClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {trigger}\n </button>\n\n {isOpen && (\n <div\n id={panelId}\n className={[\n 'absolute top-full left-0 z-20 mt-1 min-w-[180px] max-h-[280px] overflow-y-auto',\n 'rounded-xl border border-slate-200 bg-white shadow-lg',\n panelClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {children}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,SAAS,EACvB,SACA,QACA,UACA,SACA,UACA,mBAAmB,IACnB,iBAAiB,MACD;CAChB,MAAM,OAAA,GAAM,MAAA,OAAA,CAAuB,IAAI;CACvC,MAAM,cAAA,GAAa,MAAA,OAAA,CAA0B,IAAI;CAEjD,MAAM,UAAU,IAAA,GADJ,MAAA,MAAA,CACO,CAAA,CAAI,QAAQ,MAAM,EAAE,EAAE;CAGzC,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,qBAAqB,MAAkB;GAC3C,IAAI,IAAI,WAAW,CAAC,IAAI,QAAQ,SAAS,EAAE,MAAc,GAAG;IAC1D,QAAQ;IACR,WAAW,SAAS,MAAM;GAC5B;EACF;EACA,SAAS,iBAAiB,aAAa,iBAAiB;EACxD,aAAa,SAAS,oBAAoB,aAAa,iBAAiB;CAC1E,GAAG,CAAC,QAAQ,OAAO,CAAC;CAGpB,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,UAAU;IACtB,EAAE,eAAe;IACjB,QAAQ;IACR,WAAW,SAAS,MAAM;GAC5B;EACF;EACA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa,SAAS,oBAAoB,WAAW,aAAa;CACpE,GAAG,CAAC,QAAQ,OAAO,CAAC;CAEpB,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAU;EAA6B;EAA5C,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,KAAK;GACL,MAAK;GACL,SAAS;GACT,iBAAe;GACf,iBAAc;GACd,iBAAe,SAAS,UAAU,KAAA;GAClC,WAAW;IACT;IACA;IACA;GACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV,UAAA;EACK,CAAA,GAEP,UACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;GACE,IAAI;GACJ,WAAW;IACT;IACA;IACA;GACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV;EACE,CAAA,CAEJ;;AAET"}
|
package/lib/index52.mjs
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef } from "react";
|
|
3
|
+
//#region src/ui-library/gallery/dropdown/Dropdown.tsx
|
|
4
|
+
/**
|
|
5
|
+
* Dropdown — Design System Component
|
|
6
|
+
*
|
|
7
|
+
* A controlled floating panel anchored to a trigger button.
|
|
8
|
+
* The consumer owns the open state, trigger content, and panel content.
|
|
9
|
+
*
|
|
10
|
+
* Responsibilities:
|
|
11
|
+
* - Trigger button with aria-expanded, aria-haspopup, aria-controls
|
|
12
|
+
* - Floating panel anchored below the trigger
|
|
13
|
+
* - Close on outside click (mousedown)
|
|
14
|
+
* - Close on Escape key
|
|
15
|
+
* - Stable panel ID per instance
|
|
16
|
+
*
|
|
17
|
+
* This component is intentionally presentation-only. It does not:
|
|
18
|
+
* - Manage selected options
|
|
19
|
+
* - Impose role="listbox" or role="option" on children
|
|
20
|
+
* - Handle async data
|
|
21
|
+
*
|
|
22
|
+
* Consumers are responsible for the trigger layout, option semantics,
|
|
23
|
+
* and any keyboard navigation within the panel.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* <Dropdown
|
|
27
|
+
* trigger={<><Filter size={16} /><span>Filter</span><ChevronDown size={14} /></>}
|
|
28
|
+
* isOpen={open}
|
|
29
|
+
* onToggle={() => setOpen(!open)}
|
|
30
|
+
* onClose={() => setOpen(false)}
|
|
31
|
+
* >
|
|
32
|
+
* <button onClick={() => select('all')}>All</button>
|
|
33
|
+
* <button onClick={() => select('active')}>Active</button>
|
|
34
|
+
* </Dropdown>
|
|
35
|
+
*/
|
|
36
|
+
function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassName = "", panelClassName = "" }) {
|
|
37
|
+
const ref = useRef(null);
|
|
38
|
+
const triggerRef = useRef(null);
|
|
39
|
+
const panelId = `${useId().replace(/:/g, "")}-panel`;
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!isOpen) return;
|
|
42
|
+
const handlePointerDown = (e) => {
|
|
43
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
44
|
+
onClose();
|
|
45
|
+
triggerRef.current?.focus();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
49
|
+
return () => document.removeEventListener("mousedown", handlePointerDown);
|
|
50
|
+
}, [isOpen, onClose]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!isOpen) return;
|
|
53
|
+
const handleKeyDown = (e) => {
|
|
54
|
+
if (e.key === "Escape") {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
onClose();
|
|
57
|
+
triggerRef.current?.focus();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
61
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
62
|
+
}, [isOpen, onClose]);
|
|
63
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
64
|
+
className: "relative inline-block",
|
|
65
|
+
ref,
|
|
66
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
67
|
+
ref: triggerRef,
|
|
68
|
+
type: "button",
|
|
69
|
+
onClick: onToggle,
|
|
70
|
+
"aria-expanded": isOpen,
|
|
71
|
+
"aria-haspopup": "true",
|
|
72
|
+
"aria-controls": isOpen ? panelId : void 0,
|
|
73
|
+
className: [
|
|
74
|
+
"inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all",
|
|
75
|
+
"hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
76
|
+
triggerClassName
|
|
77
|
+
].filter(Boolean).join(" "),
|
|
78
|
+
children: trigger
|
|
79
|
+
}), isOpen && /* @__PURE__ */ jsx("div", {
|
|
80
|
+
id: panelId,
|
|
81
|
+
className: [
|
|
82
|
+
"absolute top-full left-0 z-20 mt-1 min-w-[180px] max-h-[280px] overflow-y-auto",
|
|
83
|
+
"rounded-xl border border-slate-200 bg-white shadow-lg",
|
|
84
|
+
panelClassName
|
|
85
|
+
].filter(Boolean).join(" "),
|
|
86
|
+
children
|
|
87
|
+
})]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { Dropdown };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=index52.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index52.mjs","names":[],"sources":["../src/ui-library/gallery/dropdown/Dropdown.tsx"],"sourcesContent":["/**\n * Dropdown — Design System Component\n *\n * A controlled floating panel anchored to a trigger button.\n * The consumer owns the open state, trigger content, and panel content.\n *\n * Responsibilities:\n * - Trigger button with aria-expanded, aria-haspopup, aria-controls\n * - Floating panel anchored below the trigger\n * - Close on outside click (mousedown)\n * - Close on Escape key\n * - Stable panel ID per instance\n *\n * This component is intentionally presentation-only. It does not:\n * - Manage selected options\n * - Impose role=\"listbox\" or role=\"option\" on children\n * - Handle async data\n *\n * Consumers are responsible for the trigger layout, option semantics,\n * and any keyboard navigation within the panel.\n *\n * Usage:\n * <Dropdown\n * trigger={<><Filter size={16} /><span>Filter</span><ChevronDown size={14} /></>}\n * isOpen={open}\n * onToggle={() => setOpen(!open)}\n * onClose={() => setOpen(false)}\n * >\n * <button onClick={() => select('all')}>All</button>\n * <button onClick={() => select('active')}>Active</button>\n * </Dropdown>\n */\n\nimport { useEffect, useId, useRef, type ReactNode } from 'react';\n\nexport interface DropdownProps {\n /** Complete trigger content — icons, label, chevron — owned by consumer. */\n trigger: ReactNode;\n /** Whether the panel is open. Owned by the consumer. */\n isOpen: boolean;\n /** Called when the trigger button is clicked. Consumer toggles isOpen. */\n onToggle: () => void;\n /** Called on outside click or Escape press. Consumer sets isOpen to false. */\n onClose: () => void;\n /** Panel content — option buttons or any children. */\n children: ReactNode;\n /** Optional className on the trigger button */\n triggerClassName?: string;\n /** Optional className on the floating panel */\n panelClassName?: string;\n}\n\nexport function Dropdown({\n trigger,\n isOpen,\n onToggle,\n onClose,\n children,\n triggerClassName = '',\n panelClassName = '',\n}: DropdownProps) {\n const ref = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const uid = useId();\n const panelId = `${uid.replace(/:/g, '')}-panel`;\n\n // Close on outside mousedown — return focus to trigger\n useEffect(() => {\n if (!isOpen) return;\n const handlePointerDown = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n onClose();\n triggerRef.current?.focus();\n }\n };\n document.addEventListener('mousedown', handlePointerDown);\n return () => document.removeEventListener('mousedown', handlePointerDown);\n }, [isOpen, onClose]);\n\n // Close on Escape — return focus to trigger\n useEffect(() => {\n if (!isOpen) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n onClose();\n triggerRef.current?.focus();\n }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, onClose]);\n\n return (\n <div className=\"relative inline-block\" ref={ref}>\n <button\n ref={triggerRef}\n type=\"button\"\n onClick={onToggle}\n aria-expanded={isOpen}\n aria-haspopup=\"true\"\n aria-controls={isOpen ? panelId : undefined}\n className={[\n 'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all',\n 'hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30',\n triggerClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {trigger}\n </button>\n\n {isOpen && (\n <div\n id={panelId}\n className={[\n 'absolute top-full left-0 z-20 mt-1 min-w-[180px] max-h-[280px] overflow-y-auto',\n 'rounded-xl border border-slate-200 bg-white shadow-lg',\n panelClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {children}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,SAAS,EACvB,SACA,QACA,UACA,SACA,UACA,mBAAmB,IACnB,iBAAiB,MACD;CAChB,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,aAAa,OAA0B,IAAI;CAEjD,MAAM,UAAU,GADJ,MACO,CAAA,CAAI,QAAQ,MAAM,EAAE,EAAE;CAGzC,gBAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,qBAAqB,MAAkB;GAC3C,IAAI,IAAI,WAAW,CAAC,IAAI,QAAQ,SAAS,EAAE,MAAc,GAAG;IAC1D,QAAQ;IACR,WAAW,SAAS,MAAM;GAC5B;EACF;EACA,SAAS,iBAAiB,aAAa,iBAAiB;EACxD,aAAa,SAAS,oBAAoB,aAAa,iBAAiB;CAC1E,GAAG,CAAC,QAAQ,OAAO,CAAC;CAGpB,gBAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,UAAU;IACtB,EAAE,eAAe;IACjB,QAAQ;IACR,WAAW,SAAS,MAAM;GAC5B;EACF;EACA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa,SAAS,oBAAoB,WAAW,aAAa;CACpE,GAAG,CAAC,QAAQ,OAAO,CAAC;CAEpB,OACE,qBAAC,OAAD;EAAK,WAAU;EAA6B;EAA5C,UAAA,CACE,oBAAC,UAAD;GACE,KAAK;GACL,MAAK;GACL,SAAS;GACT,iBAAe;GACf,iBAAc;GACd,iBAAe,SAAS,UAAU,KAAA;GAClC,WAAW;IACT;IACA;IACA;GACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV,UAAA;EACK,CAAA,GAEP,UACC,oBAAC,OAAD;GACE,IAAI;GACJ,WAAW;IACT;IACA;IACA;GACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEV;EACE,CAAA,CAEJ;;AAET"}
|