@xenide-io/the-old-ui-theme 0.5.4 → 0.6.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/{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/suite-skin.css +21 -1
- package/src/styles/typography.css +72 -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,34 +0,0 @@
|
|
|
1
|
-
import { type ReactNode, type HTMLAttributes } from "react";
|
|
2
|
-
import { cn } from "@/lib/cn";
|
|
3
|
-
|
|
4
|
-
export interface MetricProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
-
value: string | number;
|
|
6
|
-
label: string;
|
|
7
|
-
badge?: ReactNode;
|
|
8
|
-
trend?: ReactNode;
|
|
9
|
-
icon?: ReactNode;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function Metric({
|
|
13
|
-
value,
|
|
14
|
-
label,
|
|
15
|
-
badge,
|
|
16
|
-
trend,
|
|
17
|
-
icon,
|
|
18
|
-
className,
|
|
19
|
-
...props
|
|
20
|
-
}: MetricProps) {
|
|
21
|
-
return (
|
|
22
|
-
<div className={cn("ph-metric", className)} {...props}>
|
|
23
|
-
<div className="ph-metric__header">
|
|
24
|
-
{icon && <span className="ph-metric__icon">{icon}</span>}
|
|
25
|
-
<span className="ph-metric__label">{label}</span>
|
|
26
|
-
{badge && <span className="ph-metric__badge">{badge}</span>}
|
|
27
|
-
</div>
|
|
28
|
-
<div className="ph-metric__value">{value}</div>
|
|
29
|
-
{trend && <div className="ph-metric__trend">{trend}</div>}
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
Metric.displayName = "Metric";
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { IconChevronLeft, IconChevronRight } from "@/components/icons";
|
|
2
|
-
import { cn } from "@/lib/cn";
|
|
3
|
-
|
|
4
|
-
export interface BreadcrumbItem {
|
|
5
|
-
label: string;
|
|
6
|
-
href?: string;
|
|
7
|
-
current?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface BreadcrumbsProps {
|
|
11
|
-
items: BreadcrumbItem[];
|
|
12
|
-
label?: string;
|
|
13
|
-
className?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function Breadcrumbs({ items, label = "Breadcrumb", className }: BreadcrumbsProps) {
|
|
17
|
-
return (
|
|
18
|
-
<nav aria-label={label} className={cn("ph-breadcrumbs", className)}>
|
|
19
|
-
{items.map((item, index) => (
|
|
20
|
-
<span key={`${item.label}-${index}`} className="contents">
|
|
21
|
-
{index > 0 ? <span className="ph-breadcrumb-sep" aria-hidden="true">/</span> : null}
|
|
22
|
-
{item.current || !item.href ? (
|
|
23
|
-
<span className="ph-breadcrumb-current" aria-current={item.current ? "page" : undefined}>
|
|
24
|
-
{item.label}
|
|
25
|
-
</span>
|
|
26
|
-
) : (
|
|
27
|
-
<a href={item.href} className="ph-breadcrumb-link">
|
|
28
|
-
{item.label}
|
|
29
|
-
</a>
|
|
30
|
-
)}
|
|
31
|
-
</span>
|
|
32
|
-
))}
|
|
33
|
-
</nav>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface PaginationProps {
|
|
38
|
-
page: number;
|
|
39
|
-
totalPages: number;
|
|
40
|
-
onPageChange?: (page: number) => void;
|
|
41
|
-
label?: string;
|
|
42
|
-
compact?: boolean;
|
|
43
|
-
className?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function Pagination({
|
|
47
|
-
page,
|
|
48
|
-
totalPages,
|
|
49
|
-
onPageChange,
|
|
50
|
-
label = "Pagination",
|
|
51
|
-
compact = false,
|
|
52
|
-
className,
|
|
53
|
-
}: PaginationProps) {
|
|
54
|
-
const pages = compact ? [Math.max(1, page - 1), page, Math.min(totalPages, page + 1)] : [1, 2, 3, totalPages];
|
|
55
|
-
const uniquePages = Array.from(new Set(pages)).filter((n) => n >= 1 && n <= totalPages);
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<nav aria-label={label} className={cn("ph-pagination", className)}>
|
|
59
|
-
<button
|
|
60
|
-
type="button"
|
|
61
|
-
className="ph-pagination-btn"
|
|
62
|
-
disabled={page <= 1}
|
|
63
|
-
aria-label="Previous page"
|
|
64
|
-
onClick={() => onPageChange?.(page - 1)}
|
|
65
|
-
>
|
|
66
|
-
<IconChevronLeft className="mx-auto h-4 w-4" aria-hidden />
|
|
67
|
-
</button>
|
|
68
|
-
{uniquePages.map((pageNumber, index) => (
|
|
69
|
-
<span key={pageNumber} className="contents">
|
|
70
|
-
{!compact && index === uniquePages.length - 1 && pageNumber > 4 ? (
|
|
71
|
-
<span className="ph-pagination-ellipsis" aria-hidden="true">...</span>
|
|
72
|
-
) : null}
|
|
73
|
-
<button
|
|
74
|
-
type="button"
|
|
75
|
-
className={cn("ph-pagination-btn", pageNumber === page && "ph-pagination-btn-active")}
|
|
76
|
-
aria-current={pageNumber === page ? "page" : undefined}
|
|
77
|
-
onClick={() => onPageChange?.(pageNumber)}
|
|
78
|
-
>
|
|
79
|
-
{pageNumber}
|
|
80
|
-
</button>
|
|
81
|
-
</span>
|
|
82
|
-
))}
|
|
83
|
-
<button
|
|
84
|
-
type="button"
|
|
85
|
-
className="ph-pagination-btn"
|
|
86
|
-
disabled={page >= totalPages}
|
|
87
|
-
aria-label="Next page"
|
|
88
|
-
onClick={() => onPageChange?.(page + 1)}
|
|
89
|
-
>
|
|
90
|
-
<IconChevronRight className="mx-auto h-4 w-4" aria-hidden />
|
|
91
|
-
</button>
|
|
92
|
-
</nav>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, type InputHTMLAttributes } from "react";
|
|
4
|
-
import { cn } from "@/lib/cn";
|
|
5
|
-
|
|
6
|
-
export interface NumberFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange"> {
|
|
7
|
-
value?: number;
|
|
8
|
-
onChange?: (value: number) => void;
|
|
9
|
-
min?: number;
|
|
10
|
-
max?: number;
|
|
11
|
-
step?: number;
|
|
12
|
-
label?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function NumberField({
|
|
16
|
-
value: controlledValue,
|
|
17
|
-
onChange,
|
|
18
|
-
min,
|
|
19
|
-
max,
|
|
20
|
-
step = 1,
|
|
21
|
-
label,
|
|
22
|
-
className,
|
|
23
|
-
disabled,
|
|
24
|
-
defaultValue,
|
|
25
|
-
...inputProps
|
|
26
|
-
}: NumberFieldProps) {
|
|
27
|
-
const [internal, setInternal] = useState(controlledValue ?? 0);
|
|
28
|
-
const value = controlledValue ?? internal;
|
|
29
|
-
|
|
30
|
-
const set = (v: number) => {
|
|
31
|
-
const clamped = Math.max(min ?? -Infinity, Math.min(max ?? Infinity, v));
|
|
32
|
-
setInternal(clamped);
|
|
33
|
-
onChange?.(clamped);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div className={cn("ph-number-field", className)}>
|
|
38
|
-
{label && <label className="ph-label">{label}</label>}
|
|
39
|
-
<div className="ph-number-field__control">
|
|
40
|
-
<button
|
|
41
|
-
type="button"
|
|
42
|
-
disabled={disabled || (min !== undefined && value <= min)}
|
|
43
|
-
onClick={() => set(value - step)}
|
|
44
|
-
className="ph-number-field__btn"
|
|
45
|
-
aria-label="Decrease"
|
|
46
|
-
>
|
|
47
|
-
<svg width="12" height="2" viewBox="0 0 12 2" fill="currentColor"><rect y="0" width="12" height="2" rx="1" /></svg>
|
|
48
|
-
</button>
|
|
49
|
-
<input
|
|
50
|
-
type="number"
|
|
51
|
-
value={value}
|
|
52
|
-
onChange={(e) => set(Number(e.target.value))}
|
|
53
|
-
disabled={disabled}
|
|
54
|
-
min={min}
|
|
55
|
-
max={max}
|
|
56
|
-
step={step}
|
|
57
|
-
className="ph-number-field__input"
|
|
58
|
-
{...inputProps}
|
|
59
|
-
/>
|
|
60
|
-
<button
|
|
61
|
-
type="button"
|
|
62
|
-
disabled={disabled || (max !== undefined && value >= max)}
|
|
63
|
-
onClick={() => set(value + step)}
|
|
64
|
-
className="ph-number-field__btn"
|
|
65
|
-
aria-label="Increase"
|
|
66
|
-
>
|
|
67
|
-
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
68
|
-
<path d="M6 2v8M2 6h8" />
|
|
69
|
-
</svg>
|
|
70
|
-
</button>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
NumberField.displayName = "NumberField";
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { useState, useRef, useEffect, type ReactNode, type HTMLAttributes } from "react";
|
|
2
|
-
import { cn } from "@/lib/cn";
|
|
3
|
-
|
|
4
|
-
export interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
-
trigger: ReactNode;
|
|
6
|
-
children: ReactNode;
|
|
7
|
-
open?: boolean;
|
|
8
|
-
onOpenChange?: (open: boolean) => void;
|
|
9
|
-
placement?: "top" | "bottom" | "left" | "right";
|
|
10
|
-
matchTriggerWidth?: boolean;
|
|
11
|
-
padded?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function Popover({
|
|
15
|
-
trigger,
|
|
16
|
-
children,
|
|
17
|
-
open: controlledOpen,
|
|
18
|
-
onOpenChange,
|
|
19
|
-
placement = "bottom",
|
|
20
|
-
matchTriggerWidth = false,
|
|
21
|
-
padded = true,
|
|
22
|
-
className,
|
|
23
|
-
...props
|
|
24
|
-
}: PopoverProps) {
|
|
25
|
-
const [internalOpen, setInternalOpen] = useState(false);
|
|
26
|
-
const isOpen = controlledOpen ?? internalOpen;
|
|
27
|
-
const setOpen = onOpenChange ?? setInternalOpen;
|
|
28
|
-
|
|
29
|
-
const triggerRef = useRef<HTMLDivElement>(null);
|
|
30
|
-
const panelRef = useRef<HTMLDivElement>(null);
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (!isOpen) return;
|
|
34
|
-
const handleClick = (e: MouseEvent) => {
|
|
35
|
-
if (
|
|
36
|
-
triggerRef.current && !triggerRef.current.contains(e.target as Node) &&
|
|
37
|
-
panelRef.current && !panelRef.current.contains(e.target as Node)
|
|
38
|
-
) {
|
|
39
|
-
setOpen(false);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
document.addEventListener("mousedown", handleClick);
|
|
43
|
-
return () => document.removeEventListener("mousedown", handleClick);
|
|
44
|
-
}, [isOpen, setOpen]);
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<div className={cn("ph-popover relative inline-flex", className)} {...props}>
|
|
48
|
-
<div
|
|
49
|
-
ref={triggerRef}
|
|
50
|
-
onClick={() => setOpen(!isOpen)}
|
|
51
|
-
className="ph-popover__trigger"
|
|
52
|
-
>
|
|
53
|
-
{trigger}
|
|
54
|
-
</div>
|
|
55
|
-
{isOpen && (
|
|
56
|
-
<div
|
|
57
|
-
ref={panelRef}
|
|
58
|
-
className={cn(
|
|
59
|
-
"ph-popover__panel",
|
|
60
|
-
`ph-popover--${placement}`,
|
|
61
|
-
padded && "ph-popover__panel--padded",
|
|
62
|
-
matchTriggerWidth && "ph-popover__panel--match"
|
|
63
|
-
)}
|
|
64
|
-
style={matchTriggerWidth ? { width: triggerRef.current?.offsetWidth } : undefined}
|
|
65
|
-
>
|
|
66
|
-
{children}
|
|
67
|
-
</div>
|
|
68
|
-
)}
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
Popover.displayName = "Popover";
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { ReactNode, HTMLAttributes } from "react";
|
|
2
|
-
import { cn } from "@/lib/cn";
|
|
3
|
-
|
|
4
|
-
export interface ProgressCircleProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
-
progress: number;
|
|
6
|
-
size?: number;
|
|
7
|
-
strokeWidth?: number;
|
|
8
|
-
children?: ReactNode;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function ProgressCircle({
|
|
12
|
-
progress,
|
|
13
|
-
size = 40,
|
|
14
|
-
strokeWidth: sw,
|
|
15
|
-
children,
|
|
16
|
-
className,
|
|
17
|
-
...props
|
|
18
|
-
}: ProgressCircleProps) {
|
|
19
|
-
const stroke = sw ?? size * 0.08;
|
|
20
|
-
const radius = (size - stroke) / 2;
|
|
21
|
-
const circumference = 2 * Math.PI * radius;
|
|
22
|
-
const offset = circumference - (Math.min(1, Math.max(0, progress)) * circumference);
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<span
|
|
26
|
-
className={cn("ph-progress-circle", className)}
|
|
27
|
-
style={{ width: size, height: size }}
|
|
28
|
-
role="progressbar"
|
|
29
|
-
aria-valuenow={Math.round(progress * 100)}
|
|
30
|
-
aria-valuemin={0}
|
|
31
|
-
aria-valuemax={100}
|
|
32
|
-
{...props}
|
|
33
|
-
>
|
|
34
|
-
<svg width={size} height={size}>
|
|
35
|
-
<circle
|
|
36
|
-
cx={size / 2}
|
|
37
|
-
cy={size / 2}
|
|
38
|
-
r={radius}
|
|
39
|
-
fill="none"
|
|
40
|
-
stroke="currentColor"
|
|
41
|
-
strokeWidth={stroke}
|
|
42
|
-
opacity={0.15}
|
|
43
|
-
/>
|
|
44
|
-
<circle
|
|
45
|
-
cx={size / 2}
|
|
46
|
-
cy={size / 2}
|
|
47
|
-
r={radius}
|
|
48
|
-
fill="none"
|
|
49
|
-
stroke="currentColor"
|
|
50
|
-
strokeWidth={stroke}
|
|
51
|
-
strokeDasharray={circumference}
|
|
52
|
-
strokeDashoffset={offset}
|
|
53
|
-
strokeLinecap="round"
|
|
54
|
-
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
|
55
|
-
className="ph-progress-circle__bar"
|
|
56
|
-
/>
|
|
57
|
-
</svg>
|
|
58
|
-
{children && <span className="ph-progress-circle__content">{children}</span>}
|
|
59
|
-
</span>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
ProgressCircle.displayName = "ProgressCircle";
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
-
import { IconStar } from "@/components/icons";
|
|
3
|
-
import { cn } from "@/lib/cn";
|
|
4
|
-
|
|
5
|
-
export interface RatingProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange"> {
|
|
6
|
-
label?: string;
|
|
7
|
-
value?: number;
|
|
8
|
-
max?: number;
|
|
9
|
-
readOnly?: boolean;
|
|
10
|
-
onChange?: (value: number) => void;
|
|
11
|
-
wrapperClassName?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function Rating({
|
|
15
|
-
label,
|
|
16
|
-
value = 0,
|
|
17
|
-
max = 5,
|
|
18
|
-
readOnly = true,
|
|
19
|
-
onChange,
|
|
20
|
-
className,
|
|
21
|
-
wrapperClassName,
|
|
22
|
-
...props
|
|
23
|
-
}: RatingProps) {
|
|
24
|
-
const stars = Array.from({ length: max }, (_, index) => index + 1);
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div className={cn("ph-field", wrapperClassName)}>
|
|
28
|
-
{label && <span className="ph-label">{label}</span>}
|
|
29
|
-
<div
|
|
30
|
-
className={cn("ph-rating flex", className)}
|
|
31
|
-
role={readOnly ? "img" : "radiogroup"}
|
|
32
|
-
aria-label={label ?? `Rating: ${value} out of ${max}`}
|
|
33
|
-
{...props}
|
|
34
|
-
>
|
|
35
|
-
{stars.map((star) => {
|
|
36
|
-
const active = star <= value;
|
|
37
|
-
|
|
38
|
-
if (readOnly) {
|
|
39
|
-
return (
|
|
40
|
-
<IconStar
|
|
41
|
-
key={star}
|
|
42
|
-
className={cn("h-6 w-6", active ? "text-amber-500" : "text-ph-border")}
|
|
43
|
-
aria-hidden
|
|
44
|
-
/>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<button
|
|
50
|
-
key={star}
|
|
51
|
-
type="button"
|
|
52
|
-
className="rounded p-0.5 transition hover:scale-105 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ph-brand"
|
|
53
|
-
role="radio"
|
|
54
|
-
aria-checked={active}
|
|
55
|
-
aria-label={`${star} out of ${max}`}
|
|
56
|
-
onClick={() => onChange?.(star)}
|
|
57
|
-
>
|
|
58
|
-
<IconStar className={cn("h-6 w-6", active ? "text-amber-500" : "text-ph-border")} />
|
|
59
|
-
</button>
|
|
60
|
-
);
|
|
61
|
-
})}
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useRef, useCallback, type ReactNode, type HTMLAttributes } from "react";
|
|
4
|
-
import { cn } from "@/lib/cn";
|
|
5
|
-
|
|
6
|
-
export interface ResizableProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
-
left: ReactNode;
|
|
8
|
-
right: ReactNode;
|
|
9
|
-
defaultLeftWidth?: number;
|
|
10
|
-
minLeftWidth?: number;
|
|
11
|
-
minRightWidth?: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function Resizable({
|
|
15
|
-
left,
|
|
16
|
-
right,
|
|
17
|
-
defaultLeftWidth = 50,
|
|
18
|
-
minLeftWidth = 20,
|
|
19
|
-
minRightWidth = 20,
|
|
20
|
-
className,
|
|
21
|
-
...props
|
|
22
|
-
}: ResizableProps) {
|
|
23
|
-
const [leftPercent, setLeftPercent] = useState(defaultLeftWidth);
|
|
24
|
-
const dragging = useRef(false);
|
|
25
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
26
|
-
|
|
27
|
-
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
|
28
|
-
e.preventDefault();
|
|
29
|
-
dragging.current = true;
|
|
30
|
-
const startX = e.clientX;
|
|
31
|
-
const startPercent = leftPercent;
|
|
32
|
-
|
|
33
|
-
const handleMove = (ev: MouseEvent) => {
|
|
34
|
-
if (!dragging.current || !containerRef.current) return;
|
|
35
|
-
const rect = containerRef.current.getBoundingClientRect();
|
|
36
|
-
const delta = ((ev.clientX - startX) / rect.width) * 100;
|
|
37
|
-
const newVal = Math.max(minLeftWidth, Math.min(100 - minRightWidth, startPercent + delta));
|
|
38
|
-
setLeftPercent(newVal);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const handleUp = () => {
|
|
42
|
-
dragging.current = false;
|
|
43
|
-
document.removeEventListener("mousemove", handleMove);
|
|
44
|
-
document.removeEventListener("mouseup", handleUp);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
document.addEventListener("mousemove", handleMove);
|
|
48
|
-
document.addEventListener("mouseup", handleUp);
|
|
49
|
-
}, [leftPercent, minLeftWidth, minRightWidth]);
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<div
|
|
53
|
-
ref={containerRef}
|
|
54
|
-
className={cn("ph-resizable flex overflow-hidden", className)}
|
|
55
|
-
{...props}
|
|
56
|
-
>
|
|
57
|
-
<div className="ph-resizable__left overflow-auto" style={{ width: `${leftPercent}%` }}>
|
|
58
|
-
{left}
|
|
59
|
-
</div>
|
|
60
|
-
<div
|
|
61
|
-
className="ph-resizable__handle cursor-col-resize shrink-0"
|
|
62
|
-
onMouseDown={handleMouseDown}
|
|
63
|
-
/>
|
|
64
|
-
<div className="ph-resizable__right flex-1 overflow-auto">
|
|
65
|
-
{right}
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
Resizable.displayName = "Resizable";
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type ReactElement, type ReactNode, type HTMLAttributes } from "react";
|
|
2
|
-
import { cn } from "@/lib/cn";
|
|
3
|
-
|
|
4
|
-
export interface RowProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
-
icon?: ReactElement | null;
|
|
6
|
-
sideIcon?: ReactElement | null;
|
|
7
|
-
status?: "default" | "success" | "warning" | "danger" | "muted";
|
|
8
|
-
fullWidth?: boolean;
|
|
9
|
-
center?: boolean;
|
|
10
|
-
size?: "sm" | "md" | "lg";
|
|
11
|
-
children?: ReactNode;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const Row = forwardRef<HTMLDivElement, RowProps>(function Row({
|
|
15
|
-
icon,
|
|
16
|
-
sideIcon,
|
|
17
|
-
status = "default",
|
|
18
|
-
fullWidth = false,
|
|
19
|
-
center = false,
|
|
20
|
-
size = "md",
|
|
21
|
-
className,
|
|
22
|
-
children,
|
|
23
|
-
...props
|
|
24
|
-
}, ref) {
|
|
25
|
-
const symbolic = children === null || children === undefined || children === false;
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div
|
|
29
|
-
ref={ref}
|
|
30
|
-
className={cn(
|
|
31
|
-
"ph-row",
|
|
32
|
-
status !== "default" && `ph-row--${status}`,
|
|
33
|
-
fullWidth && "ph-row--full-width",
|
|
34
|
-
center && "ph-row--center",
|
|
35
|
-
size === "sm" && "ph-row--sm",
|
|
36
|
-
size === "lg" && "ph-row--lg",
|
|
37
|
-
symbolic && "ph-row--symbolic",
|
|
38
|
-
className
|
|
39
|
-
)}
|
|
40
|
-
{...props}
|
|
41
|
-
>
|
|
42
|
-
<div className="ph-row__main">
|
|
43
|
-
{icon && <span className="ph-row__icon">{icon}</span>}
|
|
44
|
-
{!symbolic && <div className="ph-row__content">{children}</div>}
|
|
45
|
-
{sideIcon && <span className="ph-row__side-icon">{sideIcon}</span>}
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
Row.displayName = "Row";
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useRef, useState, useEffect, type ReactNode, type HTMLAttributes } from "react";
|
|
4
|
-
import { cn } from "@/lib/cn";
|
|
5
|
-
|
|
6
|
-
export interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
-
children: ReactNode;
|
|
8
|
-
orientation?: "vertical" | "horizontal" | "both";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function ScrollArea({
|
|
12
|
-
children,
|
|
13
|
-
orientation = "vertical",
|
|
14
|
-
className,
|
|
15
|
-
...props
|
|
16
|
-
}: ScrollAreaProps) {
|
|
17
|
-
const viewportRef = useRef<HTMLDivElement>(null);
|
|
18
|
-
const [showTop, setShowTop] = useState(false);
|
|
19
|
-
const [showBottom, setShowBottom] = useState(false);
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
const el = viewportRef.current;
|
|
23
|
-
if (!el) return;
|
|
24
|
-
const update = () => {
|
|
25
|
-
setShowTop(el.scrollTop > 4);
|
|
26
|
-
setShowBottom(el.scrollTop < el.scrollHeight - el.clientHeight - 4);
|
|
27
|
-
};
|
|
28
|
-
update();
|
|
29
|
-
el.addEventListener("scroll", update, { passive: true });
|
|
30
|
-
const ro = new ResizeObserver(update);
|
|
31
|
-
ro.observe(el);
|
|
32
|
-
return () => { el.removeEventListener("scroll", update); ro.disconnect(); };
|
|
33
|
-
}, []);
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div
|
|
37
|
-
className={cn(
|
|
38
|
-
"ph-scroll-area relative overflow-hidden",
|
|
39
|
-
orientation === "horizontal" && "overflow-x-auto",
|
|
40
|
-
orientation === "both" && "overflow-auto",
|
|
41
|
-
className
|
|
42
|
-
)}
|
|
43
|
-
{...props}
|
|
44
|
-
>
|
|
45
|
-
{showTop && <div className="ph-scroll-area__shadow-top" />}
|
|
46
|
-
<div ref={viewportRef} className="ph-scroll-area__viewport overflow-y-auto overscroll-contain">
|
|
47
|
-
{children}
|
|
48
|
-
</div>
|
|
49
|
-
{showBottom && <div className="ph-scroll-area__shadow-bottom" />}
|
|
50
|
-
</div>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
ScrollArea.displayName = "ScrollArea";
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { type ReactNode } from "react";
|
|
4
|
-
import { cn } from "@/lib/cn";
|
|
5
|
-
|
|
6
|
-
export interface SidebarGroup {
|
|
7
|
-
label?: string;
|
|
8
|
-
items: SidebarItemDef[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface SidebarItemDef {
|
|
12
|
-
label: string;
|
|
13
|
-
icon?: ReactNode;
|
|
14
|
-
href?: string;
|
|
15
|
-
badge?: ReactNode;
|
|
16
|
-
active?: boolean;
|
|
17
|
-
onClick?: () => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface SidebarProps {
|
|
21
|
-
groups: SidebarGroup[];
|
|
22
|
-
className?: string;
|
|
23
|
-
header?: ReactNode;
|
|
24
|
-
footer?: ReactNode;
|
|
25
|
-
children?: ReactNode;
|
|
26
|
-
collapsed?: boolean;
|
|
27
|
-
onCollapse?: (collapsed: boolean) => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function Sidebar({ groups, className, header, footer, children, collapsed = false, onCollapse }: SidebarProps) {
|
|
31
|
-
return (
|
|
32
|
-
<nav className={cn("ph-sidebar flex flex-col h-full", collapsed && "ph-sidebar--collapsed", className)}>
|
|
33
|
-
{header && <div className="ph-sidebar__header px-3 py-3 border-b border-ph-border-subtle">{header}</div>}
|
|
34
|
-
<div className="ph-sidebar__body flex-1 overflow-y-auto px-2 py-3 space-y-5">
|
|
35
|
-
{groups.map((group) => (
|
|
36
|
-
<div key={group.label ?? 'default'} className="ph-sidebar__group">
|
|
37
|
-
{group.label && !collapsed && (
|
|
38
|
-
<h3 className="ph-sidebar__group-label px-2 mb-1 text-[10px] font-semibold uppercase tracking-wider text-ph-mutedtext">
|
|
39
|
-
{group.label}
|
|
40
|
-
</h3>
|
|
41
|
-
)}
|
|
42
|
-
<div className="ph-sidebar__items space-y-0.5">
|
|
43
|
-
{group.items.map((item) => (
|
|
44
|
-
<SidebarItem key={item.label} {...item} collapsed={collapsed} />
|
|
45
|
-
))}
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
))}
|
|
49
|
-
</div>
|
|
50
|
-
{children}
|
|
51
|
-
{footer && <div className="ph-sidebar__footer px-3 py-3 border-t border-ph-border-subtle">{footer}</div>}
|
|
52
|
-
</nav>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function SidebarItem({ label, icon, badge, active, href, onClick, collapsed }: SidebarItemDef & { collapsed: boolean }) {
|
|
57
|
-
const Comp = onClick ? "button" : "a";
|
|
58
|
-
return (
|
|
59
|
-
<Comp
|
|
60
|
-
type={onClick ? "button" as const : undefined}
|
|
61
|
-
href={onClick ? undefined : href}
|
|
62
|
-
onClick={onClick}
|
|
63
|
-
className={cn(
|
|
64
|
-
"ph-sidebar__item flex items-center gap-2.5 w-full rounded-md px-2.5 py-2 text-sm text-left transition",
|
|
65
|
-
active
|
|
66
|
-
? "bg-ph-selected-bg text-ph-selected-text font-medium"
|
|
67
|
-
: "text-ph-subtle hover:bg-ph-muted hover:text-ph-ink",
|
|
68
|
-
collapsed && "justify-center px-2"
|
|
69
|
-
)}
|
|
70
|
-
title={collapsed ? label : undefined}
|
|
71
|
-
>
|
|
72
|
-
{icon && <span className={cn("ph-sidebar__item-icon shrink-0 flex items-center justify-center", collapsed ? "w-5 h-5" : "w-4 h-4")}>{icon}</span>}
|
|
73
|
-
{!collapsed && <span className="ph-sidebar__item-label truncate flex-1">{label}</span>}
|
|
74
|
-
{!collapsed && badge && <span className="ph-sidebar__item-badge shrink-0">{badge}</span>}
|
|
75
|
-
</Comp>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
Sidebar.displayName = "Sidebar";
|