@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.
- package/README.md +21 -9
- package/dist/{Chip-C5hRFAhe.d.mts → Chip-DzC5xtK9.d.mts} +8 -3
- package/dist/{Chip-C5hRFAhe.d.ts → Chip-DzC5xtK9.d.ts} +8 -3
- package/dist/chunk-3G43JEI3.mjs +2564 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -2
- package/dist/index.mjs +3 -1
- package/dist/tailwind-preset.js +1 -0
- package/dist/tailwind-preset.mjs +1 -0
- package/dist/ui.d.mts +4 -531
- package/dist/ui.d.ts +4 -531
- package/dist/ui.js +407 -2504
- package/dist/ui.mjs +5 -91
- package/package.json +1 -1
- package/src/app/globals.css +18 -0
- package/src/components/ui/Typography.tsx +12 -4
- package/src/components/ui/index.ts +7 -184
- package/src/components/ui/typography.test.tsx +27 -0
- package/src/styles/typography.css +75 -3
- package/tailwind-preset.ts +1 -0
- package/dist/chunk-SBBKXV35.mjs +0 -4620
- package/src/components/ui/AlertDialog.tsx +0 -62
- package/src/components/ui/AppLayout.tsx +0 -27
- package/src/components/ui/Autocomplete.tsx +0 -106
- package/src/components/ui/Banner.tsx +0 -63
- package/src/components/ui/ButtonGroup.tsx +0 -16
- package/src/components/ui/ChatBubble.tsx +0 -71
- package/src/components/ui/Collapsible.tsx +0 -46
- package/src/components/ui/Combobox.tsx +0 -119
- package/src/components/ui/ContextMenu.tsx +0 -81
- package/src/components/ui/DataTable.tsx +0 -133
- package/src/components/ui/DatePicker.tsx +0 -96
- package/src/components/ui/Dialog.tsx +0 -75
- package/src/components/ui/Divider.tsx +0 -39
- package/src/components/ui/Drawer.tsx +0 -71
- package/src/components/ui/HoverCard.tsx +0 -36
- package/src/components/ui/Indicator.tsx +0 -61
- package/src/components/ui/InputGroup.tsx +0 -16
- package/src/components/ui/Lettermark.tsx +0 -47
- package/src/components/ui/LoadingBar.tsx +0 -25
- package/src/components/ui/Menubar.tsx +0 -66
- package/src/components/ui/Metric.tsx +0 -34
- package/src/components/ui/Navigation.tsx +0 -94
- package/src/components/ui/NumberField.tsx +0 -76
- package/src/components/ui/Popover.tsx +0 -73
- package/src/components/ui/ProgressCircle.tsx +0 -63
- package/src/components/ui/Rating.tsx +0 -65
- package/src/components/ui/Resizable.tsx +0 -71
- package/src/components/ui/Row.tsx +0 -51
- package/src/components/ui/ScrollArea.tsx +0 -54
- package/src/components/ui/Sidebar.tsx +0 -79
- package/src/components/ui/Snack.tsx +0 -48
- package/src/components/ui/Splotch.tsx +0 -33
- package/src/components/ui/Stepper.tsx +0 -74
- package/src/components/ui/Tabs.tsx +0 -53
- package/src/components/ui/Tag.tsx +0 -65
- package/src/components/ui/Terminal.tsx +0 -54
- package/src/components/ui/ThemeManager.tsx +0 -18
- package/src/components/ui/Timeline.tsx +0 -60
- package/src/components/ui/Toast.tsx +0 -70
- package/src/components/ui/Toggle.tsx +0 -62
- 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";
|