@kjaniec-dev/ui 0.7.1 → 0.8.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/dist/index.js CHANGED
@@ -518,6 +518,7 @@ function useTabs() {
518
518
  }
519
519
  function Tabs({ value, defaultValue, onValueChange, className, children, ...props }) {
520
520
  const [internal, setInternal] = React18.useState(defaultValue ?? "");
521
+ const uid = React18.useId();
521
522
  const current = value ?? internal;
522
523
  const setValue = React18.useCallback(
523
524
  (v) => {
@@ -526,7 +527,7 @@ function Tabs({ value, defaultValue, onValueChange, className, children, ...prop
526
527
  },
527
528
  [value, onValueChange]
528
529
  );
529
- return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { value: current, setValue }, children: /* @__PURE__ */ jsx("div", { className, ...props, children }) });
530
+ return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { value: current, setValue, uid }, children: /* @__PURE__ */ jsx("div", { className, ...props, children }) });
530
531
  }
531
532
  var TabsList = React18.forwardRef(
532
533
  ({ className, onKeyDown, ...props }, ref) => {
@@ -577,7 +578,7 @@ var TabsList = React18.forwardRef(
577
578
  TabsList.displayName = "TabsList";
578
579
  var TabsTrigger = React18.forwardRef(
579
580
  ({ className, value, children, ...props }, ref) => {
580
- const { value: current, setValue } = useTabs();
581
+ const { value: current, setValue, uid } = useTabs();
581
582
  const active = current === value;
582
583
  return /* @__PURE__ */ jsx(
583
584
  "button",
@@ -585,8 +586,8 @@ var TabsTrigger = React18.forwardRef(
585
586
  ref,
586
587
  type: "button",
587
588
  role: "tab",
588
- id: `tabs-trigger-${value}`,
589
- "aria-controls": `tabs-panel-${value}`,
589
+ id: `tabs-trigger-${uid}-${value}`,
590
+ "aria-controls": `tabs-panel-${uid}-${value}`,
590
591
  "aria-selected": active,
591
592
  tabIndex: active ? 0 : -1,
592
593
  onClick: () => setValue(value),
@@ -606,7 +607,7 @@ var TabsTrigger = React18.forwardRef(
606
607
  TabsTrigger.displayName = "TabsTrigger";
607
608
  var TabsContent = React18.forwardRef(
608
609
  ({ className, value, ...props }, ref) => {
609
- const { value: current } = useTabs();
610
+ const { value: current, uid } = useTabs();
610
611
  const active = current === value;
611
612
  if (!active) return null;
612
613
  return /* @__PURE__ */ jsx(
@@ -614,8 +615,8 @@ var TabsContent = React18.forwardRef(
614
615
  {
615
616
  ref,
616
617
  role: "tabpanel",
617
- id: `tabs-panel-${value}`,
618
- "aria-labelledby": `tabs-trigger-${value}`,
618
+ id: `tabs-panel-${uid}-${value}`,
619
+ "aria-labelledby": `tabs-trigger-${uid}-${value}`,
619
620
  tabIndex: 0,
620
621
  className: cn("pt-5 text-[0.9rem] text-muted-foreground outline-none", className),
621
622
  ...props
@@ -1344,23 +1345,39 @@ function DataTable({
1344
1345
  }[col.align || "left"];
1345
1346
  const isSortable = col.sortable && onSort && col.sortKey;
1346
1347
  const isSortedActive = sortBy && col.sortKey === sortBy;
1348
+ const ariaSort = isSortable ? isSortedActive ? sortDirection === "asc" ? "ascending" : "descending" : "none" : void 0;
1347
1349
  return /* @__PURE__ */ jsx(
1348
1350
  TableHead,
1349
1351
  {
1350
1352
  className: cn(
1351
1353
  alignClass,
1352
- isSortable && "cursor-pointer select-none hover:bg-muted/30 transition-colors",
1354
+ isSortable && "p-0 hover:bg-muted/30 transition-colors",
1353
1355
  col.className
1354
1356
  ),
1355
- onClick: () => {
1356
- if (!isSortable || !col.sortKey) return;
1357
- const nextDirection = isSortedActive && sortDirection === "asc" ? "desc" : "asc";
1358
- onSort(col.sortKey, nextDirection);
1359
- },
1360
- children: /* @__PURE__ */ jsxs("div", { className: cn("inline-flex items-center gap-1.5", alignClass === "text-right" && "justify-end w-full"), children: [
1361
- /* @__PURE__ */ jsx("span", { children: col.header }),
1362
- isSortable && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground shrink-0 select-none", children: isSortedActive ? sortDirection === "asc" ? "\u25B2" : "\u25BC" : "\u21C5" })
1363
- ] })
1357
+ "aria-sort": ariaSort,
1358
+ children: isSortable ? /* @__PURE__ */ jsxs(
1359
+ "button",
1360
+ {
1361
+ type: "button",
1362
+ onClick: () => {
1363
+ if (!col.sortKey) return;
1364
+ const nextDirection = isSortedActive && sortDirection === "asc" ? "desc" : "asc";
1365
+ onSort(col.sortKey, nextDirection);
1366
+ },
1367
+ className: cn(
1368
+ "w-full px-4 py-3 flex items-center gap-1.5 font-semibold text-[0.72rem] uppercase tracking-[0.05em] text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 transition-colors cursor-pointer",
1369
+ {
1370
+ "justify-start text-left": col.align === "left" || !col.align,
1371
+ "justify-center text-center": col.align === "center",
1372
+ "justify-end text-right": col.align === "right"
1373
+ }
1374
+ ),
1375
+ children: [
1376
+ /* @__PURE__ */ jsx("span", { children: col.header }),
1377
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground shrink-0 select-none", children: isSortedActive ? sortDirection === "asc" ? "\u25B2" : "\u25BC" : "\u21C5" })
1378
+ ]
1379
+ }
1380
+ ) : /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center gap-1.5", alignClass === "text-right" && "justify-end w-full"), children: col.header })
1364
1381
  },
1365
1382
  idx
1366
1383
  );
@@ -1873,7 +1890,7 @@ function CommandPalette({ open, onClose, items, placeholder = "Type a command or
1873
1890
  }, [open, filteredItems, activeIndex, onClose]);
1874
1891
  React18.useEffect(() => {
1875
1892
  if (listRef.current) {
1876
- const activeEl = listRef.current.children[activeIndex];
1893
+ const activeEl = listRef.current.querySelector('[aria-selected="true"]');
1877
1894
  if (activeEl) {
1878
1895
  activeEl.scrollIntoView({ block: "nearest" });
1879
1896
  }
@@ -1896,6 +1913,9 @@ function CommandPalette({ open, onClose, items, placeholder = "Type a command or
1896
1913
  "div",
1897
1914
  {
1898
1915
  ref: containerRef,
1916
+ role: "dialog",
1917
+ "aria-modal": "true",
1918
+ "aria-label": "Command palette",
1899
1919
  className: "w-[95vw] max-w-lg bg-surface border border-border rounded-kj-xl shadow-kj-lg flex flex-col overflow-hidden max-h-[500px]",
1900
1920
  children: [
1901
1921
  /* @__PURE__ */ jsxs("div", { className: "p-4 border-b border-border flex items-center gap-2", children: [
@@ -1904,6 +1924,11 @@ function CommandPalette({ open, onClose, items, placeholder = "Type a command or
1904
1924
  "input",
1905
1925
  {
1906
1926
  type: "text",
1927
+ role: "combobox",
1928
+ "aria-expanded": open,
1929
+ "aria-autocomplete": "list",
1930
+ "aria-controls": "command-palette-listbox",
1931
+ "aria-activedescendant": filteredItems.length > 0 ? `cmd-item-${activeIndex}` : void 0,
1907
1932
  placeholder,
1908
1933
  value: search,
1909
1934
  onChange: (e) => setSearch(e.target.value),
@@ -1916,44 +1941,64 @@ function CommandPalette({ open, onClose, items, placeholder = "Type a command or
1916
1941
  'No results found for "',
1917
1942
  search,
1918
1943
  '"'
1919
- ] }) : /* @__PURE__ */ jsx("div", { ref: listRef, className: "space-y-4", children: Object.entries(groups).map(([cat, itemsInCat]) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1920
- /* @__PURE__ */ jsx("div", { className: "px-3 py-1 text-[0.65rem] font-bold uppercase tracking-wider text-muted-foreground", children: cat }),
1921
- itemsInCat.map((item) => {
1922
- const currentIdx = absoluteItemIndex++;
1923
- const isActive = currentIdx === activeIndex;
1924
- return /* @__PURE__ */ jsxs(
1925
- "div",
1926
- {
1927
- onClick: () => {
1928
- item.action();
1929
- onClose();
1930
- },
1931
- className: cn(
1932
- "flex items-center justify-between gap-3 px-3 py-2.5 rounded-kj-md cursor-pointer select-none transition-colors",
1933
- isActive ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
1934
- ),
1935
- children: [
1936
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
1937
- item.icon && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground [&_svg]:h-[1.1rem] [&_svg]:w-[1.1rem]", children: item.icon }),
1938
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex flex-col", children: [
1939
- /* @__PURE__ */ jsx("span", { className: cn("text-[0.85rem] font-semibold", isActive ? "text-primary" : "text-foreground"), children: item.title }),
1940
- item.subtitle && /* @__PURE__ */ jsx("span", { className: "text-[0.75rem] text-muted-foreground truncate", children: item.subtitle })
1941
- ] })
1942
- ] }),
1943
- item.shortcut && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: item.shortcut.map((sc, scIdx) => /* @__PURE__ */ jsx(
1944
- "kbd",
1945
- {
1946
- className: "px-1.5 py-0.5 rounded border border-border bg-subtle text-[10px] font-mono leading-none shadow-kj-xs text-muted-foreground",
1947
- children: sc
1944
+ ] }) : /* @__PURE__ */ jsx(
1945
+ "div",
1946
+ {
1947
+ ref: listRef,
1948
+ id: "command-palette-listbox",
1949
+ role: "listbox",
1950
+ "aria-label": "Commands",
1951
+ className: "space-y-4",
1952
+ children: Object.entries(groups).map(([cat, itemsInCat]) => /* @__PURE__ */ jsxs("div", { role: "group", "aria-labelledby": `cmd-cat-${cat}`, className: "space-y-1", children: [
1953
+ /* @__PURE__ */ jsx(
1954
+ "div",
1955
+ {
1956
+ id: `cmd-cat-${cat}`,
1957
+ className: "px-3 py-1 text-[0.65rem] font-bold uppercase tracking-wider text-muted-foreground",
1958
+ children: cat
1959
+ }
1960
+ ),
1961
+ itemsInCat.map((item) => {
1962
+ const currentIdx = absoluteItemIndex++;
1963
+ const isActive = currentIdx === activeIndex;
1964
+ return /* @__PURE__ */ jsxs(
1965
+ "div",
1966
+ {
1967
+ id: `cmd-item-${currentIdx}`,
1968
+ role: "option",
1969
+ "aria-selected": isActive,
1970
+ onClick: () => {
1971
+ item.action();
1972
+ onClose();
1948
1973
  },
1949
- scIdx
1950
- )) })
1951
- ]
1952
- },
1953
- item.id
1954
- );
1955
- })
1956
- ] }, cat)) }) }),
1974
+ className: cn(
1975
+ "flex items-center justify-between gap-3 px-3 py-2.5 rounded-kj-md cursor-pointer select-none transition-colors",
1976
+ isActive ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
1977
+ ),
1978
+ children: [
1979
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
1980
+ item.icon && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground [&_svg]:h-[1.1rem] [&_svg]:w-[1.1rem]", children: item.icon }),
1981
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex flex-col", children: [
1982
+ /* @__PURE__ */ jsx("span", { className: cn("text-[0.85rem] font-semibold", isActive ? "text-primary" : "text-foreground"), children: item.title }),
1983
+ item.subtitle && /* @__PURE__ */ jsx("span", { className: "text-[0.75rem] text-muted-foreground truncate", children: item.subtitle })
1984
+ ] })
1985
+ ] }),
1986
+ item.shortcut && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: item.shortcut.map((sc, scIdx) => /* @__PURE__ */ jsx(
1987
+ "kbd",
1988
+ {
1989
+ className: "px-1.5 py-0.5 rounded border border-border bg-subtle text-[10px] font-mono leading-none shadow-kj-xs text-muted-foreground",
1990
+ children: sc
1991
+ },
1992
+ scIdx
1993
+ )) })
1994
+ ]
1995
+ },
1996
+ item.id
1997
+ );
1998
+ })
1999
+ ] }, cat))
2000
+ }
2001
+ ) }),
1957
2002
  /* @__PURE__ */ jsxs("div", { className: "px-4 py-2.5 border-t border-border bg-subtle flex items-center justify-between text-[11px] text-muted-foreground font-medium", children: [
1958
2003
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1959
2004
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
@@ -2013,7 +2058,423 @@ function SidebarNav({ className, groups, currentHref, ...props }) {
2013
2058
  ] }, groupIdx)) });
2014
2059
  }
2015
2060
  SidebarNav.displayName = "SidebarNav";
2061
+ var fabVariants = cva(
2062
+ [
2063
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap select-none",
2064
+ "font-sans font-semibold leading-none",
2065
+ "border border-transparent rounded-full cursor-pointer shadow-lg",
2066
+ "transition-[background-color,color,border-color,box-shadow,transform] duration-150",
2067
+ "active:translate-y-px active:shadow-md",
2068
+ "focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45",
2069
+ "disabled:opacity-50 disabled:pointer-events-none"
2070
+ ],
2071
+ {
2072
+ variants: {
2073
+ variant: {
2074
+ primary: "bg-primary text-primary-foreground hover:bg-primary-hover",
2075
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover",
2076
+ outline: "bg-transparent text-foreground border-border hover:bg-muted hover:border-muted-foreground",
2077
+ danger: "bg-danger text-white hover:brightness-95"
2078
+ },
2079
+ size: {
2080
+ sm: "h-10 w-10 p-0 text-sm",
2081
+ md: "h-14 w-14 p-0 text-base",
2082
+ lg: "h-16 w-16 p-0 text-lg"
2083
+ },
2084
+ position: {
2085
+ "bottom-right": "fixed bottom-6 right-6 z-50",
2086
+ "bottom-left": "fixed bottom-6 left-6 z-50",
2087
+ "bottom-center": "fixed bottom-6 left-1/2 -translate-x-1/2 z-50",
2088
+ none: "relative"
2089
+ }
2090
+ },
2091
+ defaultVariants: { variant: "primary", size: "md", position: "bottom-right" }
2092
+ }
2093
+ );
2094
+ var Fab = React18.forwardRef(
2095
+ ({ className, variant, size, position, mobileOnly, icon, label, loading, disabled, ...props }, ref) => {
2096
+ return /* @__PURE__ */ jsx(
2097
+ "button",
2098
+ {
2099
+ ref,
2100
+ className: cn(
2101
+ fabVariants({ variant, size, position }),
2102
+ mobileOnly && "md:hidden",
2103
+ className
2104
+ ),
2105
+ ...props,
2106
+ disabled: disabled || loading,
2107
+ "aria-label": label,
2108
+ "aria-busy": loading || void 0,
2109
+ children: loading ? /* @__PURE__ */ jsx(
2110
+ "span",
2111
+ {
2112
+ "aria-hidden": true,
2113
+ className: "h-[1.2em] w-[1.2em] rounded-full border-2 border-current border-t-transparent animate-spin"
2114
+ }
2115
+ ) : /* @__PURE__ */ jsx("span", { className: "[&_svg]:h-[1.35em] [&_svg]:w-[1.35em] flex items-center justify-center", children: icon })
2116
+ }
2117
+ );
2118
+ }
2119
+ );
2120
+ Fab.displayName = "Fab";
2121
+ var BottomSheetContext = React18.createContext(null);
2122
+ function BottomSheet({
2123
+ open,
2124
+ onClose,
2125
+ children,
2126
+ maxWidth = "max-w-lg",
2127
+ className,
2128
+ showClose = true
2129
+ }) {
2130
+ const titleId = React18.useId();
2131
+ const descId = React18.useId();
2132
+ const containerRef = React18.useRef(null);
2133
+ const lastActiveElement = React18.useRef(null);
2134
+ const [mounted, setMounted] = React18.useState(open);
2135
+ React18.useEffect(() => {
2136
+ if (open) {
2137
+ setMounted(true);
2138
+ } else {
2139
+ const timer = setTimeout(() => setMounted(false), 200);
2140
+ return () => clearTimeout(timer);
2141
+ }
2142
+ }, [open]);
2143
+ React18.useEffect(() => {
2144
+ if (!open) return;
2145
+ lastActiveElement.current = document.activeElement;
2146
+ const container = containerRef.current;
2147
+ if (container) {
2148
+ const focusables = container.querySelectorAll(
2149
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
2150
+ );
2151
+ if (focusables.length > 0) {
2152
+ focusables[0].focus();
2153
+ setTimeout(() => {
2154
+ if (container.contains(document.activeElement)) return;
2155
+ focusables[0].focus();
2156
+ }, 50);
2157
+ } else {
2158
+ container.focus();
2159
+ }
2160
+ }
2161
+ const handleKeyDown = (e) => {
2162
+ if (e.key === "Escape") {
2163
+ onClose();
2164
+ return;
2165
+ }
2166
+ if (e.key === "Tab") {
2167
+ const container2 = containerRef.current;
2168
+ if (!container2) return;
2169
+ const focusables = Array.from(
2170
+ container2.querySelectorAll(
2171
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
2172
+ )
2173
+ );
2174
+ if (focusables.length === 0) {
2175
+ e.preventDefault();
2176
+ return;
2177
+ }
2178
+ const first = focusables[0];
2179
+ const last = focusables[focusables.length - 1];
2180
+ if (!container2.contains(document.activeElement)) {
2181
+ e.preventDefault();
2182
+ if (e.shiftKey) {
2183
+ last.focus();
2184
+ } else {
2185
+ first.focus();
2186
+ }
2187
+ return;
2188
+ }
2189
+ if (e.shiftKey) {
2190
+ if (document.activeElement === first || document.activeElement === container2) {
2191
+ last.focus();
2192
+ e.preventDefault();
2193
+ }
2194
+ } else {
2195
+ if (document.activeElement === last) {
2196
+ first.focus();
2197
+ e.preventDefault();
2198
+ }
2199
+ }
2200
+ }
2201
+ };
2202
+ window.addEventListener("keydown", handleKeyDown);
2203
+ document.body.style.overflow = "hidden";
2204
+ return () => {
2205
+ window.removeEventListener("keydown", handleKeyDown);
2206
+ document.body.style.overflow = "";
2207
+ if (lastActiveElement.current) {
2208
+ lastActiveElement.current.focus();
2209
+ lastActiveElement.current = null;
2210
+ }
2211
+ };
2212
+ }, [open, onClose]);
2213
+ if (!open && !mounted) return null;
2214
+ return /* @__PURE__ */ jsx(BottomSheetContext.Provider, { value: { titleId, descId }, children: /* @__PURE__ */ jsx(
2215
+ "div",
2216
+ {
2217
+ role: "presentation",
2218
+ onClick: (e) => e.target === e.currentTarget && onClose(),
2219
+ className: cn(
2220
+ "fixed inset-0 z-[100] grid items-end sm:place-items-center p-0 sm:p-6 backdrop-blur-[3px] transition-all duration-200",
2221
+ "bg-[color-mix(in_oklch,#09090b_55%,transparent)]",
2222
+ open ? "opacity-100 pointer-events-auto visible" : "opacity-0 pointer-events-none invisible"
2223
+ ),
2224
+ children: /* @__PURE__ */ jsxs(
2225
+ "div",
2226
+ {
2227
+ ref: containerRef,
2228
+ role: "dialog",
2229
+ "aria-modal": "true",
2230
+ "aria-labelledby": titleId,
2231
+ "aria-describedby": descId,
2232
+ tabIndex: -1,
2233
+ className: cn(
2234
+ "relative w-full bg-surface border-t sm:border border-border shadow-kj-lg transition-all duration-200 outline-none",
2235
+ "rounded-t-kj-2xl sm:rounded-kj-2xl flex flex-col max-h-[85vh] sm:max-h-[90vh]",
2236
+ // Mobile sliding & Desktop scale/opacity transitions
2237
+ open ? "translate-y-0 sm:scale-100 sm:opacity-100" : "translate-y-full sm:scale-95 sm:opacity-0",
2238
+ // Mobile styling vs Desktop styling
2239
+ "bottom-0 sm:bottom-auto",
2240
+ maxWidth,
2241
+ className
2242
+ ),
2243
+ children: [
2244
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center py-2 sm:hidden cursor-grab", children: /* @__PURE__ */ jsx("div", { className: "w-12 h-1.5 rounded-full bg-muted-foreground/20" }) }),
2245
+ showClose && /* @__PURE__ */ jsx(
2246
+ "button",
2247
+ {
2248
+ type: "button",
2249
+ onClick: onClose,
2250
+ "aria-label": "Close",
2251
+ className: "absolute top-4 right-4 p-1.5 rounded-kj-sm text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer z-10",
2252
+ children: /* @__PURE__ */ jsx("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
2253
+ }
2254
+ ),
2255
+ children
2256
+ ]
2257
+ }
2258
+ )
2259
+ }
2260
+ ) });
2261
+ }
2262
+ function BottomSheetHeader({ className, ...props }) {
2263
+ return /* @__PURE__ */ jsx("div", { className: cn("p-6 flex flex-col gap-1.5 border-b border-border", className), ...props });
2264
+ }
2265
+ function BottomSheetTitle({ className, id, ...props }) {
2266
+ const ctx = React18.useContext(BottomSheetContext);
2267
+ return /* @__PURE__ */ jsx("h2", { id: id ?? ctx?.titleId, className: cn("m-0 text-[1.15rem] font-bold tracking-[-0.01em]", className), ...props });
2268
+ }
2269
+ function BottomSheetDescription({ className, id, ...props }) {
2270
+ const ctx = React18.useContext(BottomSheetContext);
2271
+ return /* @__PURE__ */ jsx("p", { id: id ?? ctx?.descId, className: cn("m-0 text-[0.9rem] text-muted-foreground", className), ...props });
2272
+ }
2273
+ function BottomSheetContent({ className, ...props }) {
2274
+ return /* @__PURE__ */ jsx("div", { className: cn("p-6 overflow-y-auto flex-1", className), ...props });
2275
+ }
2276
+ function BottomSheetFooter({ className, ...props }) {
2277
+ return /* @__PURE__ */ jsx("div", { className: cn("p-6 border-t border-border flex gap-2.5 justify-end mt-auto", className), ...props });
2278
+ }
2279
+ var kbdVariants = cva(
2280
+ "inline-flex items-center justify-center font-mono font-medium text-muted-foreground bg-muted border border-border border-b-2 rounded-kj-sm select-none",
2281
+ {
2282
+ variants: {
2283
+ size: {
2284
+ sm: "h-5 min-w-5 px-1 text-[0.68rem]",
2285
+ md: "h-6 min-w-6 px-1.5 text-[0.78rem]"
2286
+ }
2287
+ },
2288
+ defaultVariants: { size: "sm" }
2289
+ }
2290
+ );
2291
+ var Kbd = React18.forwardRef(
2292
+ ({ className, size, keys, children, ...props }, ref) => {
2293
+ if (keys && keys.length > 0) {
2294
+ return /* @__PURE__ */ jsx(
2295
+ "span",
2296
+ {
2297
+ ref,
2298
+ className: cn("inline-flex items-center gap-1", className),
2299
+ ...props,
2300
+ children: keys.map((k, i) => /* @__PURE__ */ jsx("kbd", { className: kbdVariants({ size }), children: k }, i))
2301
+ }
2302
+ );
2303
+ }
2304
+ return /* @__PURE__ */ jsx("kbd", { ref, className: cn(kbdVariants({ size }), className), ...props, children });
2305
+ }
2306
+ );
2307
+ Kbd.displayName = "Kbd";
2308
+ var CodeBlock = React18.forwardRef(
2309
+ ({ className, code, language, filename, copyable = true, maxHeight, ...props }, ref) => {
2310
+ const [copied, setCopied] = React18.useState(false);
2311
+ const canCopy = copyable && typeof navigator !== "undefined" && !!navigator.clipboard;
2312
+ const onCopy = () => {
2313
+ navigator.clipboard.writeText(code).then(
2314
+ () => {
2315
+ setCopied(true);
2316
+ window.setTimeout(() => setCopied(false), 1500);
2317
+ },
2318
+ () => {
2319
+ }
2320
+ );
2321
+ };
2322
+ const label = filename ?? language;
2323
+ return /* @__PURE__ */ jsxs(
2324
+ "div",
2325
+ {
2326
+ ref,
2327
+ className: cn("rounded-kj-md border border-border bg-muted/50 overflow-hidden", className),
2328
+ ...props,
2329
+ children: [
2330
+ (label || canCopy) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3.5 py-2 border-b border-border", children: [
2331
+ /* @__PURE__ */ jsx("span", { className: "text-[0.72rem] font-mono text-muted-foreground", children: label }),
2332
+ canCopy && /* @__PURE__ */ jsx(
2333
+ "button",
2334
+ {
2335
+ type: "button",
2336
+ onClick: onCopy,
2337
+ className: "text-[0.72rem] font-medium text-muted-foreground hover:text-foreground transition-colors cursor-pointer bg-transparent border-0 p-0",
2338
+ children: copied ? "Copied" : "Copy"
2339
+ }
2340
+ )
2341
+ ] }),
2342
+ /* @__PURE__ */ jsx(
2343
+ "pre",
2344
+ {
2345
+ className: "m-0 px-3.5 py-3 overflow-x-auto text-[0.8rem] leading-relaxed font-mono text-foreground",
2346
+ style: maxHeight ? { maxHeight, overflowY: "auto" } : void 0,
2347
+ children: /* @__PURE__ */ jsx("code", { children: code })
2348
+ }
2349
+ )
2350
+ ]
2351
+ }
2352
+ );
2353
+ }
2354
+ );
2355
+ CodeBlock.displayName = "CodeBlock";
2356
+ var PopoverContext = React18.createContext(null);
2357
+ function usePopoverContext(part) {
2358
+ const ctx = React18.useContext(PopoverContext);
2359
+ if (!ctx) throw new Error(`${part} must be used inside <Popover>`);
2360
+ return ctx;
2361
+ }
2362
+ function Popover({ children, className, open: openProp, onOpenChange }) {
2363
+ const [openState, setOpenState] = React18.useState(false);
2364
+ const controlled = openProp !== void 0;
2365
+ const open = controlled ? openProp : openState;
2366
+ const triggerRef = React18.useRef(null);
2367
+ const ref = React18.useRef(null);
2368
+ const setOpen = React18.useCallback(
2369
+ (v) => {
2370
+ if (!controlled) setOpenState(v);
2371
+ onOpenChange?.(v);
2372
+ },
2373
+ [controlled, onOpenChange]
2374
+ );
2375
+ React18.useEffect(() => {
2376
+ if (!open) return;
2377
+ const onDoc = (e) => {
2378
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
2379
+ };
2380
+ const onEsc = (e) => {
2381
+ if (e.key === "Escape") {
2382
+ setOpen(false);
2383
+ triggerRef.current?.focus();
2384
+ }
2385
+ };
2386
+ document.addEventListener("mousedown", onDoc);
2387
+ document.addEventListener("keydown", onEsc);
2388
+ return () => {
2389
+ document.removeEventListener("mousedown", onDoc);
2390
+ document.removeEventListener("keydown", onEsc);
2391
+ };
2392
+ }, [open, setOpen]);
2393
+ return /* @__PURE__ */ jsx(PopoverContext.Provider, { value: { open, setOpen, triggerRef }, children: /* @__PURE__ */ jsx("div", { ref, className: cn("relative inline-block", className), children }) });
2394
+ }
2395
+ var PopoverTrigger = React18.forwardRef(({ onClick, asChild, ...props }, ref) => {
2396
+ const ctx = usePopoverContext("PopoverTrigger");
2397
+ const setRefs = (node) => {
2398
+ ctx.triggerRef.current = node;
2399
+ if (typeof ref === "function") ref(node);
2400
+ else if (ref) ref.current = node;
2401
+ };
2402
+ if (asChild) {
2403
+ const child = React18.Children.only(props.children);
2404
+ return React18.cloneElement(child, {
2405
+ ref: setRefs,
2406
+ ...props,
2407
+ ...child.props,
2408
+ onClick: (e) => {
2409
+ ctx.setOpen(!ctx.open);
2410
+ onClick?.(e);
2411
+ child.props.onClick?.(e);
2412
+ },
2413
+ "aria-haspopup": "dialog",
2414
+ "aria-expanded": ctx.open
2415
+ });
2416
+ }
2417
+ return /* @__PURE__ */ jsx(
2418
+ "button",
2419
+ {
2420
+ ref: setRefs,
2421
+ ...props,
2422
+ type: "button",
2423
+ "aria-haspopup": "dialog",
2424
+ "aria-expanded": ctx.open,
2425
+ onClick: (e) => {
2426
+ ctx.setOpen(!ctx.open);
2427
+ onClick?.(e);
2428
+ }
2429
+ }
2430
+ );
2431
+ });
2432
+ PopoverTrigger.displayName = "PopoverTrigger";
2433
+ var sideAlignClasses = {
2434
+ "bottom-start": "top-[calc(100%+6px)] left-0",
2435
+ "bottom-center": "top-[calc(100%+6px)] left-1/2 -translate-x-1/2",
2436
+ "bottom-end": "top-[calc(100%+6px)] right-0",
2437
+ "top-start": "bottom-[calc(100%+6px)] left-0",
2438
+ "top-center": "bottom-[calc(100%+6px)] left-1/2 -translate-x-1/2",
2439
+ "top-end": "bottom-[calc(100%+6px)] right-0",
2440
+ "left-start": "right-[calc(100%+6px)] top-0",
2441
+ "left-center": "right-[calc(100%+6px)] top-1/2 -translate-y-1/2",
2442
+ "left-end": "right-[calc(100%+6px)] bottom-0",
2443
+ "right-start": "left-[calc(100%+6px)] top-0",
2444
+ "right-center": "left-[calc(100%+6px)] top-1/2 -translate-y-1/2",
2445
+ "right-end": "left-[calc(100%+6px)] bottom-0"
2446
+ };
2447
+ function PopoverContent({
2448
+ className,
2449
+ side = "bottom",
2450
+ align = "center",
2451
+ children,
2452
+ ...props
2453
+ }) {
2454
+ const ctx = usePopoverContext("PopoverContent");
2455
+ const ref = React18.useRef(null);
2456
+ React18.useEffect(() => {
2457
+ if (ctx.open) ref.current?.focus();
2458
+ }, [ctx.open]);
2459
+ if (!ctx.open) return null;
2460
+ return /* @__PURE__ */ jsx(
2461
+ "div",
2462
+ {
2463
+ ref,
2464
+ role: "dialog",
2465
+ tabIndex: -1,
2466
+ className: cn(
2467
+ "absolute z-40 min-w-[200px] p-4 rounded-kj-md bg-surface border border-border shadow-kj-lg outline-none",
2468
+ "animate-[kjpop_.12s_ease]",
2469
+ sideAlignClasses[`${side}-${align}`],
2470
+ className
2471
+ ),
2472
+ ...props,
2473
+ children
2474
+ }
2475
+ );
2476
+ }
2016
2477
 
2017
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, Avatar, AvatarGroup, Badge, BottomNavigation, Breadcrumb, BreadcrumbItem, BreadcrumbSeparator, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CheckboxField, CommandPalette, ConfirmDialog, DashboardShell, DataTable, DetailPageLayout, Drawer, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState, ErrorState, Field, FormField, Hint, Input, Label, MetricCard, Modal, ModalActions, ModalDescription, ModalTitle, PageHeader, Pagination, Progress, Radio, Segmented, Select, SelectField, SettingsLayout, SidebarNav, Skeleton, Slider, Spinner, Stat, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableToolbar, TableWrap, Tabs, TabsContent, TabsList, TabsTrigger, TextField, Textarea, ToastProvider, Tooltip, badgeVariants, buttonVariants, cn, sliderThumbCSS, useToast };
2478
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, Avatar, AvatarGroup, Badge, BottomNavigation, BottomSheet, BottomSheetContent, BottomSheetDescription, BottomSheetFooter, BottomSheetHeader, BottomSheetTitle, Breadcrumb, BreadcrumbItem, BreadcrumbSeparator, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CheckboxField, CodeBlock, CommandPalette, ConfirmDialog, DashboardShell, DataTable, DetailPageLayout, Drawer, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState, ErrorState, Fab, Field, FormField, Hint, Input, Kbd, Label, MetricCard, Modal, ModalActions, ModalDescription, ModalTitle, PageHeader, Pagination, Popover, PopoverContent, PopoverTrigger, Progress, Radio, Segmented, Select, SelectField, SettingsLayout, SidebarNav, Skeleton, Slider, Spinner, Stat, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableToolbar, TableWrap, Tabs, TabsContent, TabsList, TabsTrigger, TextField, Textarea, ToastProvider, Tooltip, badgeVariants, buttonVariants, cn, sliderThumbCSS, useToast };
2018
2479
  //# sourceMappingURL=index.js.map
2019
2480
  //# sourceMappingURL=index.js.map