@xenide-io/the-old-ui-theme 0.5.5 → 0.6.1

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.
Files changed (63) hide show
  1. package/README.md +21 -9
  2. package/dist/{Chip-C5hRFAhe.d.mts → Chip-DzC5xtK9.d.mts} +8 -3
  3. package/dist/{Chip-C5hRFAhe.d.ts → Chip-DzC5xtK9.d.ts} +8 -3
  4. package/dist/chunk-3G43JEI3.mjs +2564 -0
  5. package/dist/index.d.mts +2 -2
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +7 -2
  8. package/dist/index.mjs +3 -1
  9. package/dist/tailwind-preset.js +1 -0
  10. package/dist/tailwind-preset.mjs +1 -0
  11. package/dist/ui.d.mts +4 -531
  12. package/dist/ui.d.ts +4 -531
  13. package/dist/ui.js +407 -2504
  14. package/dist/ui.mjs +5 -91
  15. package/package.json +1 -1
  16. package/src/app/globals.css +18 -0
  17. package/src/components/ui/Typography.tsx +12 -4
  18. package/src/components/ui/index.ts +7 -184
  19. package/src/components/ui/typography.test.tsx +27 -0
  20. package/src/styles/typography.css +75 -3
  21. package/tailwind-preset.ts +1 -0
  22. package/dist/chunk-SBBKXV35.mjs +0 -4620
  23. package/src/components/ui/AlertDialog.tsx +0 -62
  24. package/src/components/ui/AppLayout.tsx +0 -27
  25. package/src/components/ui/Autocomplete.tsx +0 -106
  26. package/src/components/ui/Banner.tsx +0 -63
  27. package/src/components/ui/ButtonGroup.tsx +0 -16
  28. package/src/components/ui/ChatBubble.tsx +0 -71
  29. package/src/components/ui/Collapsible.tsx +0 -46
  30. package/src/components/ui/Combobox.tsx +0 -119
  31. package/src/components/ui/ContextMenu.tsx +0 -81
  32. package/src/components/ui/DataTable.tsx +0 -133
  33. package/src/components/ui/DatePicker.tsx +0 -96
  34. package/src/components/ui/Dialog.tsx +0 -75
  35. package/src/components/ui/Divider.tsx +0 -39
  36. package/src/components/ui/Drawer.tsx +0 -71
  37. package/src/components/ui/HoverCard.tsx +0 -36
  38. package/src/components/ui/Indicator.tsx +0 -61
  39. package/src/components/ui/InputGroup.tsx +0 -16
  40. package/src/components/ui/Lettermark.tsx +0 -47
  41. package/src/components/ui/LoadingBar.tsx +0 -25
  42. package/src/components/ui/Menubar.tsx +0 -66
  43. package/src/components/ui/Metric.tsx +0 -34
  44. package/src/components/ui/Navigation.tsx +0 -94
  45. package/src/components/ui/NumberField.tsx +0 -76
  46. package/src/components/ui/Popover.tsx +0 -73
  47. package/src/components/ui/ProgressCircle.tsx +0 -63
  48. package/src/components/ui/Rating.tsx +0 -65
  49. package/src/components/ui/Resizable.tsx +0 -71
  50. package/src/components/ui/Row.tsx +0 -51
  51. package/src/components/ui/ScrollArea.tsx +0 -54
  52. package/src/components/ui/Sidebar.tsx +0 -79
  53. package/src/components/ui/Snack.tsx +0 -48
  54. package/src/components/ui/Splotch.tsx +0 -33
  55. package/src/components/ui/Stepper.tsx +0 -74
  56. package/src/components/ui/Tabs.tsx +0 -53
  57. package/src/components/ui/Tag.tsx +0 -65
  58. package/src/components/ui/Terminal.tsx +0 -54
  59. package/src/components/ui/ThemeManager.tsx +0 -18
  60. package/src/components/ui/Timeline.tsx +0 -60
  61. package/src/components/ui/Toast.tsx +0 -70
  62. package/src/components/ui/Toggle.tsx +0 -62
  63. package/src/components/ui/Widget.tsx +0 -31
@@ -1,133 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useMemo, type ReactNode } from "react";
4
- import { cn } from "@/lib/cn";
5
-
6
- export interface DataTableColumn<T> {
7
- key: string;
8
- label: string;
9
- render?: (item: T) => ReactNode;
10
- sortable?: boolean;
11
- className?: string;
12
- }
13
-
14
- export interface DataTableProps<T> {
15
- columns: DataTableColumn<T>[];
16
- data: T[];
17
- pageSize?: number;
18
- searchable?: boolean;
19
- searchPlaceholder?: string;
20
- className?: string;
21
- }
22
-
23
- export function DataTable<T extends Record<string, any>>({
24
- columns,
25
- data,
26
- pageSize = 10,
27
- searchable = false,
28
- searchPlaceholder = "Search...",
29
- className,
30
- }: DataTableProps<T>) {
31
- const [search, setSearch] = useState("");
32
- const [sortKey, setSortKey] = useState<string | null>(null);
33
- const [sortDir, setSortDir] = useState<"asc" | "desc">("asc");
34
- const [page, setPage] = useState(0);
35
-
36
- const filtered = useMemo(() => {
37
- let items = data;
38
- if (search) {
39
- const q = search.toLowerCase();
40
- items = items.filter((item) =>
41
- Object.values(item).some((v) => String(v).toLowerCase().includes(q))
42
- );
43
- }
44
- if (sortKey) {
45
- items = [...items].sort((a, b) => {
46
- const av = a[sortKey];
47
- const bv = b[sortKey];
48
- const cmp = av < bv ? -1 : av > bv ? 1 : 0;
49
- return sortDir === "asc" ? cmp : -cmp;
50
- });
51
- }
52
- return items;
53
- }, [data, search, sortKey, sortDir]);
54
-
55
- const totalPages = Math.ceil(filtered.length / pageSize);
56
- const paged = filtered.slice(page * pageSize, (page + 1) * pageSize);
57
-
58
- const handleSort = (key: string) => {
59
- if (sortKey === key) {
60
- setSortDir((d) => (d === "asc" ? "desc" : "asc"));
61
- } else {
62
- setSortKey(key);
63
- setSortDir("asc");
64
- }
65
- };
66
-
67
- return (
68
- <div className={cn("ph-data-table", className)}>
69
- {searchable && (
70
- <div className="ph-data-table__toolbar">
71
- <input
72
- type="text"
73
- value={search}
74
- onChange={(e) => { setSearch(e.target.value); setPage(0); }}
75
- placeholder={searchPlaceholder}
76
- className="ph-input ph-input-sm"
77
- />
78
- </div>
79
- )}
80
- <div className="ph-table-wrap">
81
- <table className="ph-table">
82
- <thead>
83
- <tr>
84
- {columns.map((col) => (
85
- <th
86
- key={col.key}
87
- className={cn(col.sortable && "cursor-pointer select-none", col.className)}
88
- onClick={() => col.sortable && handleSort(col.key)}
89
- >
90
- {col.label}
91
- {sortKey === col.key && (
92
- <span className="ml-1 inline-block">{sortDir === "asc" ? "\u2191" : "\u2193"}</span>
93
- )}
94
- </th>
95
- ))}
96
- </tr>
97
- </thead>
98
- <tbody>
99
- {paged.map((item, i) => (
100
- <tr key={i}>
101
- {columns.map((col) => (
102
- <td key={col.key} className={col.className}>
103
- {col.render ? col.render(item) : item[col.key]}
104
- </td>
105
- ))}
106
- </tr>
107
- ))}
108
- {paged.length === 0 && (
109
- <tr>
110
- <td colSpan={columns.length} className="text-center text-ph-mutedtext py-8">No results</td>
111
- </tr>
112
- )}
113
- </tbody>
114
- </table>
115
- </div>
116
- {totalPages > 1 && (
117
- <div className="ph-data-table__pagination">
118
- <button disabled={page === 0} onClick={() => setPage((p) => p - 1)} className="ph-data-table__page-btn">
119
- Previous
120
- </button>
121
- <span className="text-xs text-ph-mutedtext">
122
- Page {page + 1} of {totalPages}
123
- </span>
124
- <button disabled={page >= totalPages - 1} onClick={() => setPage((p) => p + 1)} className="ph-data-table__page-btn">
125
- Next
126
- </button>
127
- </div>
128
- )}
129
- </div>
130
- );
131
- }
132
-
133
- DataTable.displayName = "DataTable";
@@ -1,96 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useMemo, type InputHTMLAttributes } from "react";
4
- import { cn } from "@/lib/cn";
5
-
6
- const MONTHS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
7
- const DAYS = ["Su","Mo","Tu","We","Th","Fr","Sa"];
8
-
9
- export interface DatePickerProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "type"> {
10
- value?: Date;
11
- onChange?: (date: Date) => void;
12
- }
13
-
14
- export function DatePicker({ value, onChange, className, ...props }: DatePickerProps) {
15
- const [open, setOpen] = useState(false);
16
- const [viewDate, setViewDate] = useState(value ?? new Date());
17
- const selected = value;
18
-
19
- const days = useMemo(() => {
20
- const year = viewDate.getFullYear();
21
- const month = viewDate.getMonth();
22
- const first = new Date(year, month, 1).getDay();
23
- const last = new Date(year, month + 1, 0).getDate();
24
- const prevLast = new Date(year, month, 0).getDate();
25
- const cells: { day: number; month: "prev" | "current" | "next" }[] = [];
26
- for (let i = first - 1; i >= 0; i--) cells.push({ day: prevLast - i, month: "prev" });
27
- for (let d = 1; d <= last; d++) cells.push({ day: d, month: "current" });
28
- const remaining = 42 - cells.length;
29
- for (let d = 1; d <= remaining; d++) cells.push({ day: d, month: "next" });
30
- return cells;
31
- }, [viewDate]);
32
-
33
- const isSelected = (d: number, m: "prev" | "current" | "next") => {
34
- if (!selected || m !== "current") return false;
35
- const date = new Date(viewDate.getFullYear(), viewDate.getMonth(), d);
36
- return date.toDateString() === selected.toDateString();
37
- };
38
-
39
- const selectDay = (d: number, m: string) => {
40
- const month = m === "prev" ? viewDate.getMonth() - 1 : m === "next" ? viewDate.getMonth() + 1 : viewDate.getMonth();
41
- const year = month < 0 ? viewDate.getFullYear() - 1 : month > 11 ? viewDate.getFullYear() + 1 : viewDate.getFullYear();
42
- const adjustedMonth = ((month % 12) + 12) % 12;
43
- const date = new Date(year, adjustedMonth, d);
44
- onChange?.(date);
45
- setOpen(false);
46
- };
47
-
48
- const nav = (delta: number) => setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() + delta, 1));
49
-
50
- return (
51
- <div className={cn("ph-datepicker relative", className)}>
52
- <input
53
- type="text"
54
- readOnly
55
- value={selected ? `${MONTHS[selected.getMonth()]} ${selected.getDate()}, ${selected.getFullYear()}` : ""}
56
- onFocus={() => setOpen(true)}
57
- placeholder="Pick a date"
58
- className="ph-input w-full cursor-pointer"
59
- {...props}
60
- />
61
- {open && (
62
- <div className="ph-datepicker__dropdown">
63
- <div className="ph-datepicker__nav">
64
- <button type="button" onClick={() => nav(-1)} className="ph-datepicker__nav-btn" aria-label="Previous month">
65
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M7 3L4 6l3 3"/></svg>
66
- </button>
67
- <span className="ph-datepicker__nav-label">{MONTHS[viewDate.getMonth()]} {viewDate.getFullYear()}</span>
68
- <button type="button" onClick={() => nav(1)} className="ph-datepicker__nav-btn" aria-label="Next month">
69
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M5 3l3 3-3 3"/></svg>
70
- </button>
71
- </div>
72
- <div className="ph-datepicker__grid">
73
- {DAYS.map((d) => <div key={d} className="ph-datepicker__day-header">{d}</div>)}
74
- {days.map((d, i) => (
75
- <button
76
- key={i}
77
- type="button"
78
- disabled={d.month !== "current"}
79
- onClick={() => selectDay(d.day, d.month)}
80
- className={cn(
81
- "ph-datepicker__day",
82
- d.month !== "current" && "ph-datepicker__day--other",
83
- isSelected(d.day, d.month) && "ph-datepicker__day--selected"
84
- )}
85
- >
86
- {d.day}
87
- </button>
88
- ))}
89
- </div>
90
- </div>
91
- )}
92
- </div>
93
- );
94
- }
95
-
96
- DatePicker.displayName = "DatePicker";
@@ -1,75 +0,0 @@
1
- import { useState, type ReactNode } from "react";
2
- import { Modal, type ModalProps } from "@/components/ui/Modal";
3
- import { Button, type ButtonProps } from "@/components/ui/Button";
4
-
5
- export interface DialogProps extends Omit<ModalProps, "onClose"> {
6
- isOpen?: boolean;
7
- onClose?: () => void;
8
- onConfirm?: () => void | Promise<void>;
9
- confirmText?: string;
10
- cancelText?: string;
11
- confirmProps?: Partial<ButtonProps>;
12
- cancelProps?: Partial<ButtonProps>;
13
- children?: ReactNode;
14
- }
15
-
16
- export function Dialog({
17
- isOpen: openProp,
18
- onClose,
19
- onConfirm,
20
- confirmText = "Confirm",
21
- cancelText = "Cancel",
22
- confirmProps,
23
- cancelProps,
24
- children,
25
- title,
26
- ...modalProps
27
- }: DialogProps) {
28
- const [loading, setLoading] = useState(false);
29
- const isOpen = openProp ?? true;
30
-
31
- const handleConfirm = async () => {
32
- if (!onConfirm) return;
33
- setLoading(true);
34
- try {
35
- await onConfirm();
36
- } finally {
37
- setLoading(false);
38
- }
39
- onClose?.();
40
- };
41
-
42
- return (
43
- <Modal
44
- open={isOpen}
45
- onClose={onClose}
46
- title={title}
47
- footer={
48
- <div className="flex justify-end gap-2">
49
- <Button variant="secondary" {...cancelProps} onClick={onClose}>
50
- {cancelText}
51
- </Button>
52
- {onConfirm && (
53
- <Button
54
- variant="primary"
55
- loading={loading}
56
- {...confirmProps}
57
- onClick={handleConfirm}
58
- >
59
- {confirmText}
60
- </Button>
61
- )}
62
- </div>
63
- }
64
- {...modalProps}
65
- >
66
- {children}
67
- </Modal>
68
- );
69
- }
70
-
71
- Dialog.displayName = "Dialog";
72
-
73
- export function confirmDialog(props: DialogProps) {
74
- return <Dialog {...props} />;
75
- }
@@ -1,39 +0,0 @@
1
- import type { HTMLAttributes } from "react";
2
- import { cn } from "@/lib/cn";
3
-
4
- export interface DividerProps extends HTMLAttributes<HTMLDivElement> {
5
- vertical?: boolean;
6
- dashed?: boolean;
7
- thick?: boolean;
8
- label?: string;
9
- }
10
-
11
- export function Divider({
12
- vertical = false,
13
- dashed = false,
14
- thick = false,
15
- label,
16
- className,
17
- ...props
18
- }: DividerProps) {
19
- return (
20
- <div
21
- role="separator"
22
- aria-orientation={vertical ? "vertical" : "horizontal"}
23
- className={cn(
24
- "ph-divider",
25
- vertical && "ph-divider--vertical",
26
- thick && "ph-divider--thick",
27
- dashed && "ph-divider--dashed",
28
- !!label && "ph-divider--with-label",
29
- !className && (vertical ? "mx-2 self-stretch" : "my-2"),
30
- className
31
- )}
32
- {...props}
33
- >
34
- {label && <span className="ph-divider__label">{label}</span>}
35
- </div>
36
- );
37
- }
38
-
39
- Divider.displayName = "Divider";
@@ -1,71 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import { IconClose } from "@/components/icons";
3
- import { cn } from "@/lib/cn";
4
- import { Button } from "@/components/ui/Button";
5
-
6
- export type DrawerSide = "left" | "right";
7
- export type DrawerSize = "sm" | "md" | "lg";
8
-
9
- export interface DrawerProps {
10
- open?: boolean;
11
- onClose?: () => void;
12
- title?: ReactNode;
13
- children?: ReactNode;
14
- footer?: ReactNode;
15
- side?: DrawerSide;
16
- size?: DrawerSize;
17
- className?: string;
18
- showCloseButton?: boolean;
19
- }
20
-
21
- const sideMap: Record<DrawerSide, string> = {
22
- left: "left-0 border-r",
23
- right: "right-0 border-l",
24
- };
25
-
26
- const sizeMap: Record<DrawerSize, string> = {
27
- sm: "w-[min(100%,18rem)]",
28
- md: "w-[min(100%,22rem)]",
29
- lg: "w-[min(100%,30rem)]",
30
- };
31
-
32
- export function Drawer({
33
- open = false,
34
- onClose,
35
- title,
36
- children,
37
- footer,
38
- side = "right",
39
- size = "md",
40
- className,
41
- showCloseButton = true,
42
- }: DrawerProps) {
43
- if (!open) return null;
44
-
45
- return (
46
- <>
47
- <div className="fixed inset-0 z-[120] bg-black/45 backdrop-blur-[2px]" role="presentation" onClick={onClose} />
48
- <aside
49
- className={cn(
50
- "fixed inset-y-0 z-[130] flex flex-col overflow-y-auto border-ph-border bg-ph-surface shadow-ph-md",
51
- sideMap[side],
52
- sizeMap[size],
53
- className
54
- )}
55
- >
56
- {(title || showCloseButton) && (
57
- <header className="flex items-center justify-between border-b border-ph-border px-4 py-3">
58
- <span className="text-sm font-bold text-ph-ink">{title}</span>
59
- {showCloseButton && onClose && (
60
- <Button variant="ghost" className="ph-btn-circle" onClick={onClose} aria-label="Close drawer">
61
- <IconClose className="h-5 w-5" />
62
- </Button>
63
- )}
64
- </header>
65
- )}
66
- <div className="flex-1">{children}</div>
67
- {footer && <footer className="border-t border-ph-border px-4 py-3">{footer}</footer>}
68
- </aside>
69
- </>
70
- );
71
- }
@@ -1,36 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { cn } from "@/lib/cn";
5
-
6
- interface HoverCardProps {
7
- trigger: React.ReactNode;
8
- children: React.ReactNode;
9
- className?: string;
10
- }
11
-
12
- export function HoverCard({ trigger, children, className }: HoverCardProps) {
13
- const [isVisible, setIsVisible] = useState(false);
14
-
15
- return (
16
- <div
17
- className="relative inline-block"
18
- onMouseEnter={() => setIsVisible(true)}
19
- onMouseLeave={() => setIsVisible(false)}
20
- >
21
- {trigger}
22
- <div
23
- className={cn(
24
- "absolute left-1/2 top-full z-50 mt-2 w-64 -translate-x-1/2 rounded-lg border border-ph-border bg-ph-surface p-4 shadow-ph-md transition-all duration-150",
25
- isVisible
26
- ? "visible translate-y-0 opacity-100"
27
- : "invisible -translate-y-1 opacity-0",
28
- className
29
- )}
30
- >
31
- <div className="absolute -top-1.5 left-1/2 h-3 w-3 -translate-x-1/2 rotate-45 border-l border-t border-ph-border bg-ph-surface" />
32
- <div className="relative">{children}</div>
33
- </div>
34
- </div>
35
- );
36
- }
@@ -1,61 +0,0 @@
1
- import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
- import { cn } from "@/lib/cn";
3
-
4
- export type IndicatorPosition = "top-right" | "top-left" | "bottom-right" | "bottom-left";
5
-
6
- export interface IndicatorProps extends ComponentPropsWithoutRef<"div"> {
7
- /** The element to attach the indicator to */
8
- children: ReactNode;
9
- /** Badge content (number, text, or "!") */
10
- badge?: ReactNode;
11
- /** Indicator colour */
12
- color?: string;
13
- /** Position of the indicator dot */
14
- position?: IndicatorPosition;
15
- /** Show a simple dot instead of a badge */
16
- dot?: boolean;
17
- /** Offset from corner (default: 2px) */
18
- offset?: number;
19
- }
20
-
21
- const positionMap: Record<IndicatorPosition, string> = {
22
- "top-right": "-right-[2px] -top-[2px]",
23
- "top-left": "-left-[2px] -top-[2px]",
24
- "bottom-right": "-right-[2px] -bottom-[2px]",
25
- "bottom-left": "-left-[2px] -bottom-[2px]",
26
- };
27
-
28
- export function Indicator({
29
- children,
30
- badge,
31
- color = "bg-ph-brand",
32
- position = "top-right",
33
- dot = false,
34
- className,
35
- ...props
36
- }: IndicatorProps) {
37
- return (
38
- <div className={cn("relative inline-flex items-center justify-center", className)} {...props}>
39
- {dot ? (
40
- <span
41
- className={cn(
42
- "absolute z-10 h-2.5 w-2.5 rounded-full border-2 border-white",
43
- color,
44
- positionMap[position],
45
- )}
46
- />
47
- ) : badge != null ? (
48
- <span
49
- className={cn(
50
- "absolute z-10 flex min-h-[22px] min-w-[22px] items-center justify-center rounded-full px-1 text-[11px] font-bold text-white",
51
- color,
52
- positionMap[position],
53
- )}
54
- >
55
- {badge}
56
- </span>
57
- ) : null}
58
- {children}
59
- </div>
60
- );
61
- }
@@ -1,16 +0,0 @@
1
- import { type ReactNode, type HTMLAttributes } from "react";
2
- import { cn } from "@/lib/cn";
3
-
4
- export interface InputGroupProps extends HTMLAttributes<HTMLDivElement> {
5
- children: ReactNode;
6
- }
7
-
8
- export function InputGroup({ children, className, ...props }: InputGroupProps) {
9
- return (
10
- <div className={cn("ph-input-group", className)} {...props}>
11
- {children}
12
- </div>
13
- );
14
- }
15
-
16
- InputGroup.displayName = "InputGroup";
@@ -1,47 +0,0 @@
1
- import { forwardRef, type HTMLAttributes } from "react";
2
- import { cn } from "@/lib/cn";
3
-
4
- export interface LettermarkProps extends HTMLAttributes<HTMLDivElement> {
5
- name?: string | number | null;
6
- index?: number;
7
- rounded?: boolean;
8
- outlined?: boolean;
9
- size?: "xs" | "sm" | "md" | "lg";
10
- }
11
-
12
- export const Lettermark = forwardRef<HTMLDivElement, LettermarkProps>(function Lettermark({
13
- name,
14
- index,
15
- outlined = false,
16
- rounded = false,
17
- size = "md",
18
- className,
19
- ...props
20
- }, ref) {
21
- const letter = name
22
- ? typeof name === "number"
23
- ? String(Math.floor(name))
24
- : String.fromCodePoint(name.codePointAt(0)!).toLocaleUpperCase()
25
- : "?";
26
-
27
- return (
28
- <div
29
- ref={ref}
30
- className={cn(
31
- "ph-lettermark",
32
- `ph-lettermark--${size}`,
33
- typeof index === "number" && `ph-lettermark--v-${(index % 8) + 1}`,
34
- outlined && "ph-lettermark--outlined",
35
- rounded && "ph-lettermark--rounded",
36
- letter === "?" && "ph-lettermark--unknown",
37
- className
38
- )}
39
- aria-hidden="true"
40
- {...props}
41
- >
42
- {letter}
43
- </div>
44
- );
45
- });
46
-
47
- Lettermark.displayName = "Lettermark";
@@ -1,25 +0,0 @@
1
- import type { HTMLAttributes } from "react";
2
- import { cn } from "@/lib/cn";
3
-
4
- export interface LoadingBarProps extends HTMLAttributes<HTMLDivElement> {
5
- active?: boolean;
6
- }
7
-
8
- export function LoadingBar({ active = true, className, ...props }: LoadingBarProps) {
9
- return (
10
- <div
11
- className={cn(
12
- "ph-loading-bar",
13
- active && "ph-loading-bar--active",
14
- className
15
- )}
16
- role="progressbar"
17
- aria-label="Loading"
18
- {...props}
19
- >
20
- <div className="ph-loading-bar__fill" />
21
- </div>
22
- );
23
- }
24
-
25
- LoadingBar.displayName = "LoadingBar";
@@ -1,66 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useRef, useEffect, type ReactNode, type HTMLAttributes } from "react";
4
- import { cn } from "@/lib/cn";
5
-
6
- export interface MenubarItem {
7
- label: string;
8
- items?: { label?: string; onClick?: () => void; disabled?: boolean; separator?: boolean }[];
9
- }
10
-
11
- export interface MenubarProps extends HTMLAttributes<HTMLDivElement> {
12
- items: MenubarItem[];
13
- }
14
-
15
- export function Menubar({ items, className, ...props }: MenubarProps) {
16
- const [openIndex, setOpenIndex] = useState(-1);
17
- const barRef = useRef<HTMLDivElement>(null);
18
-
19
- useEffect(() => {
20
- if (openIndex < 0) return;
21
- const handle = (e: MouseEvent) => {
22
- if (barRef.current && !barRef.current.contains(e.target as Node)) {
23
- setOpenIndex(-1);
24
- }
25
- };
26
- document.addEventListener("mousedown", handle);
27
- return () => document.removeEventListener("mousedown", handle);
28
- }, [openIndex]);
29
-
30
- return (
31
- <div ref={barRef} className={cn("ph-menubar", className)} {...props}>
32
- {items.map((item, i) => (
33
- <div key={i} className="ph-menubar__item">
34
- <button
35
- type="button"
36
- onClick={() => setOpenIndex(openIndex === i ? -1 : i)}
37
- className={cn("ph-menubar__trigger", openIndex === i && "ph-menubar__trigger--open")}
38
- >
39
- {item.label}
40
- </button>
41
- {openIndex === i && item.items && (
42
- <div className="ph-menubar__dropdown">
43
- {item.items.map((sub, j) =>
44
- sub.separator ? (
45
- <div key={j} className="ph-menubar__separator" />
46
- ) : (
47
- <button
48
- key={j}
49
- type="button"
50
- disabled={sub.disabled}
51
- onClick={() => { sub.onClick?.(); setOpenIndex(-1); }}
52
- className="ph-menubar__sub-item"
53
- >
54
- {sub.label}
55
- </button>
56
- )
57
- )}
58
- </div>
59
- )}
60
- </div>
61
- ))}
62
- </div>
63
- );
64
- }
65
-
66
- Menubar.displayName = "Menubar";