@xenide-io/the-old-ui-theme 0.2.9 → 0.3.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 +1 -1
- package/dist/chunk-5HSOBJ4F.mjs +5968 -0
- package/dist/index-BgG8Homu.d.mts +1461 -0
- package/dist/index-BgG8Homu.d.ts +1461 -0
- package/dist/index.d.mts +206 -100
- package/dist/index.d.ts +206 -100
- package/dist/index.js +4275 -1455
- package/dist/index.mjs +689 -735
- package/dist/tailwind-preset.js +7 -0
- package/dist/tailwind-preset.mjs +7 -0
- package/dist/ui.d.mts +3 -2
- package/dist/ui.d.ts +3 -2
- package/dist/ui.js +3449 -701
- package/dist/ui.mjs +106 -3
- package/package.json +20 -16
- package/src/app/globals.css +1953 -266
- package/src/components/charts/index.ts +9 -8
- package/src/components/charts/quill/BarChart.tsx +85 -0
- package/src/components/charts/quill/FunnelChart.tsx +49 -0
- package/src/components/charts/quill/InsightShell.tsx +27 -0
- package/src/components/charts/quill/MetricCard.tsx +44 -0
- package/src/components/charts/quill/PieChart.tsx +81 -0
- package/src/components/charts/quill/Sparkline.tsx +57 -0
- package/src/components/charts/quill/TimeSeriesLineChart.tsx +62 -0
- package/src/components/charts/quill/index.ts +9 -0
- package/src/components/icons/IconBase.tsx +20 -12
- package/src/components/icons/createIconBase.tsx +12 -7
- package/src/components/icons/icons.tsx +210 -18
- package/src/components/icons/index.ts +3 -0
- package/src/components/icons/lemon-brand-icons.tsx +16 -0
- package/src/components/icons/lemon-category-icons.tsx +72 -0
- package/src/components/icons/lemon-icons.tsx +57 -0
- package/src/components/sections/AlertShowcase.tsx +2 -1
- package/src/components/sections/BadgeShowcase.tsx +1 -1
- package/src/components/sections/ButtonShowcase.tsx +6 -10
- package/src/components/sections/CardShowcase.tsx +22 -6
- package/src/components/sections/DropdownShowcase.tsx +4 -6
- package/src/components/sections/FilterChipsShowcase.tsx +74 -26
- package/src/components/sections/FormShowcase.tsx +2 -1
- package/src/components/sections/IconShowcase.tsx +195 -212
- package/src/components/sections/KbdShowcase.tsx +47 -21
- package/src/components/sections/ModalShowcase.tsx +7 -6
- package/src/components/sections/NewComponentsShowcase.tsx +1052 -0
- package/src/components/sections/ProductLayoutsShowcase.tsx +78 -0
- package/src/components/sections/ProgressShowcase.tsx +50 -53
- package/src/components/sections/QuillChartShowcase.tsx +92 -0
- package/src/components/sections/QuillDashboardShowcase.tsx +149 -0
- package/src/components/sections/SidebarShowcase.tsx +151 -0
- package/src/components/sections/SwapShowcase.tsx +13 -5
- package/src/components/sections/TableShowcase.tsx +2 -0
- package/src/components/sections/TooltipShowcase.tsx +19 -25
- package/src/components/ui/Alert.tsx +28 -19
- package/src/components/ui/AlertDialog.tsx +62 -0
- package/src/components/ui/AppLayout.tsx +27 -0
- package/src/components/ui/AuthLayout.tsx +58 -0
- package/src/components/ui/Autocomplete.tsx +106 -0
- package/src/components/ui/Badge.tsx +10 -5
- package/src/components/ui/Banner.tsx +63 -0
- package/src/components/ui/Button.tsx +22 -12
- package/src/components/ui/ButtonChrome.tsx +1 -1
- package/src/components/ui/ButtonGroup.tsx +16 -0
- package/src/components/ui/Calendar.tsx +6 -7
- package/src/components/ui/Card.tsx +21 -11
- package/src/components/ui/Chip.tsx +46 -0
- package/src/components/ui/Collapsible.tsx +46 -0
- package/src/components/ui/CollapsibleSection.tsx +11 -11
- package/src/components/ui/Combobox.tsx +119 -0
- package/src/components/ui/ContextMenu.tsx +81 -0
- package/src/components/ui/DataTable.tsx +133 -0
- package/src/components/ui/DatePicker.tsx +96 -0
- package/src/components/ui/Dialog.tsx +75 -0
- package/src/components/ui/Divider.tsx +39 -0
- package/src/components/ui/Dot.tsx +32 -0
- package/src/components/ui/DropdownMenu.tsx +189 -65
- package/src/components/ui/FilterBar.tsx +165 -0
- package/src/components/ui/FilterChips.tsx +65 -35
- package/src/components/ui/FilterControls.tsx +30 -0
- package/src/components/ui/FormField.tsx +69 -0
- package/src/components/ui/Input.tsx +166 -140
- package/src/components/ui/InputGroup.tsx +16 -0
- package/src/components/ui/Kbd.tsx +88 -13
- package/src/components/ui/Lettermark.tsx +47 -0
- package/src/components/ui/Link.tsx +32 -0
- package/src/components/ui/Loader.tsx +96 -0
- package/src/components/ui/LoadingBar.tsx +25 -0
- package/src/components/ui/Menubar.tsx +66 -0
- package/src/components/ui/Metric.tsx +34 -0
- package/src/components/ui/Modal.tsx +66 -36
- package/src/components/ui/NumberField.tsx +76 -0
- package/src/components/ui/Popover.tsx +73 -0
- package/src/components/ui/ProgressCircle.tsx +63 -0
- package/src/components/ui/Resizable.tsx +71 -0
- package/src/components/ui/Row.tsx +51 -0
- package/src/components/ui/ScrollArea.tsx +54 -0
- package/src/components/ui/SettingsLayout.tsx +94 -0
- package/src/components/ui/ShowcaseWrapper.tsx +4 -16
- package/src/components/ui/Sidebar.tsx +77 -0
- package/src/components/ui/Snack.tsx +48 -0
- package/src/components/ui/Spinner.tsx +30 -0
- package/src/components/ui/Splotch.tsx +33 -0
- package/src/components/ui/Stepper.tsx +5 -6
- package/src/components/ui/Table.tsx +2 -0
- package/src/components/ui/Tag.tsx +65 -0
- package/src/components/ui/Toggle.tsx +62 -0
- package/src/components/ui/Tooltip.test.tsx +58 -0
- package/src/components/ui/Tooltip.tsx +211 -0
- package/src/components/ui/Widget.tsx +31 -0
- package/src/components/ui/first-slice.test.tsx +98 -0
- package/src/components/ui/index.ts +335 -22
- package/src/components/ui/interaction-primitives.test.tsx +77 -0
- package/src/components/ui/kbd-icons.test.tsx +90 -0
- package/src/components/ui/product-patterns.test.tsx +77 -0
- package/src/styles/themes.css +411 -1
- package/tailwind-preset.ts +7 -0
- package/dist/chunk-SHF57J7U.mjs +0 -2910
- package/dist/index-CmS6hcUk.d.mts +0 -753
- package/dist/index-CmS6hcUk.d.ts +0 -753
- package/src/components/charts/ph-insight-charts.tsx +0 -162
- package/src/components/dashboard/DashboardFiltersBar.tsx +0 -19
- package/src/components/dashboard/DashboardGrid.tsx +0 -23
- package/src/components/dashboard/DashboardInsightPlaceholder.tsx +0 -37
- package/src/components/dashboard/DashboardKpiCard.tsx +0 -25
- package/src/components/dashboard/DashboardToolbar.tsx +0 -23
- package/src/components/dashboard/DashboardWell.tsx +0 -10
- package/src/components/dashboard/InsightMiniCard.tsx +0 -26
- package/src/components/dashboard/InsightShell.tsx +0 -37
- package/src/components/dashboard/InsightShellHeader.tsx +0 -22
- package/src/components/dashboard/MiniTrendBars.tsx +0 -51
- package/src/components/dashboard/index.ts +0 -31
- package/src/components/dashboard/types.ts +0 -9
- package/src/components/sections/ChartShowcase.tsx +0 -190
- package/src/components/sections/DashboardShowcase.tsx +0 -191
- package/src/components/sections/StatShowcase.tsx +0 -129
|
@@ -0,0 +1,73 @@
|
|
|
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";
|
|
@@ -0,0 +1,63 @@
|
|
|
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";
|
|
@@ -0,0 +1,71 @@
|
|
|
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";
|
|
@@ -0,0 +1,51 @@
|
|
|
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";
|
|
@@ -0,0 +1,54 @@
|
|
|
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";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { IconTuning } from "@/components/icons";
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
|
+
|
|
5
|
+
export interface SettingsNavItem {
|
|
6
|
+
id: string;
|
|
7
|
+
label: ReactNode;
|
|
8
|
+
href: string;
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
active?: boolean;
|
|
11
|
+
badge?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SettingsNavGroup {
|
|
15
|
+
label: ReactNode;
|
|
16
|
+
items: readonly SettingsNavItem[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SettingsNavProps extends ComponentPropsWithoutRef<"div"> {
|
|
20
|
+
groups: readonly SettingsNavGroup[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SettingsLayoutProps extends Omit<ComponentPropsWithoutRef<"section">, "title"> {
|
|
24
|
+
title: ReactNode;
|
|
25
|
+
description?: ReactNode;
|
|
26
|
+
actions?: ReactNode;
|
|
27
|
+
navigation: ReactNode;
|
|
28
|
+
navigationLabel?: string;
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function SettingsNav({ groups, className, ...props }: SettingsNavProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div className={cn("ph-settings-nav", className)} {...props}>
|
|
35
|
+
{groups.map((group, index) => (
|
|
36
|
+
<div className="ph-settings-nav__group" key={index}>
|
|
37
|
+
<h3>{group.label}</h3>
|
|
38
|
+
<div className="ph-settings-nav__items">
|
|
39
|
+
{group.items.map((item) => (
|
|
40
|
+
<a
|
|
41
|
+
key={item.id}
|
|
42
|
+
href={item.href}
|
|
43
|
+
className={cn("ph-settings-nav__item", item.active && "ph-settings-nav__item--active")}
|
|
44
|
+
aria-current={item.active ? "page" : undefined}
|
|
45
|
+
>
|
|
46
|
+
{item.icon ? <span className="ph-settings-nav__icon">{item.icon}</span> : null}
|
|
47
|
+
<span>{item.label}</span>
|
|
48
|
+
{item.badge ? <span className="ph-settings-nav__badge">{item.badge}</span> : null}
|
|
49
|
+
</a>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function SettingsLayout({
|
|
59
|
+
title,
|
|
60
|
+
description,
|
|
61
|
+
actions,
|
|
62
|
+
navigation,
|
|
63
|
+
navigationLabel = "Settings",
|
|
64
|
+
children,
|
|
65
|
+
className,
|
|
66
|
+
...props
|
|
67
|
+
}: SettingsLayoutProps) {
|
|
68
|
+
return (
|
|
69
|
+
<section className={cn("ph-settings-layout", className)} {...props}>
|
|
70
|
+
<header className="ph-settings-layout__header">
|
|
71
|
+
<div>
|
|
72
|
+
<h2>{title}</h2>
|
|
73
|
+
{description ? <p>{description}</p> : null}
|
|
74
|
+
</div>
|
|
75
|
+
{actions ? <div className="ph-settings-layout__actions">{actions}</div> : null}
|
|
76
|
+
</header>
|
|
77
|
+
|
|
78
|
+
<details className="ph-settings-layout__mobile-nav">
|
|
79
|
+
<summary>
|
|
80
|
+
<IconTuning className="h-4 w-4" aria-hidden />
|
|
81
|
+
Settings menu
|
|
82
|
+
</summary>
|
|
83
|
+
<nav aria-label={navigationLabel}>{navigation}</nav>
|
|
84
|
+
</details>
|
|
85
|
+
|
|
86
|
+
<div className="ph-settings-layout__grid">
|
|
87
|
+
<aside className="ph-settings-layout__sidebar">
|
|
88
|
+
<nav aria-label={navigationLabel}>{navigation}</nav>
|
|
89
|
+
</aside>
|
|
90
|
+
<div className="ph-settings-layout__content">{children}</div>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState, type ReactNode } from "react";
|
|
4
4
|
import { cn } from "@/lib/cn";
|
|
5
5
|
import { CodeBlock } from "@/components/ui";
|
|
6
|
+
import { IconCodePreview } from "@/components/icons";
|
|
6
7
|
|
|
7
8
|
interface ShowcaseWrapperProps {
|
|
8
9
|
id?: string;
|
|
@@ -39,26 +40,13 @@ export function ShowcaseWrapper({
|
|
|
39
40
|
className={cn(
|
|
40
41
|
"flex h-10 w-10 items-center justify-center rounded-full border transition-all",
|
|
41
42
|
showCode
|
|
42
|
-
? "border-ph-brand bg-ph-brand text-
|
|
43
|
+
? "border-ph-brand bg-ph-brand text-[var(--ph-on-accent)] shadow-lg shadow-ph-brand/25"
|
|
43
44
|
: "border-ph-border bg-ph-surface text-ph-subtle hover:border-ph-brand hover:text-ph-brand"
|
|
44
45
|
)}
|
|
45
46
|
title={showCode ? "Hide code" : "Show code"}
|
|
47
|
+
aria-label={showCode ? "Hide code" : "Show code"}
|
|
46
48
|
>
|
|
47
|
-
<
|
|
48
|
-
className="h-5 w-5"
|
|
49
|
-
viewBox="0 0 24 24"
|
|
50
|
-
fill="none"
|
|
51
|
-
stroke="currentColor"
|
|
52
|
-
strokeWidth="2"
|
|
53
|
-
strokeLinecap="round"
|
|
54
|
-
strokeLinejoin="round"
|
|
55
|
-
>
|
|
56
|
-
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
|
|
57
|
-
<line x1="8" y1="21" x2="16" y2="21" />
|
|
58
|
-
<line x1="12" y1="17" x2="12" y2="21" />
|
|
59
|
-
<line x1="6" y1="8" x2="6" y2="8" />
|
|
60
|
-
<line x1="10" y1="8" x2="10" y2="8" />
|
|
61
|
-
</svg>
|
|
49
|
+
<IconCodePreview className="h-5 w-5" aria-hidden />
|
|
62
50
|
</button>
|
|
63
51
|
</div>
|
|
64
52
|
<hr />
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type ReactNode, useState } 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
|
+
collapsed?: boolean;
|
|
26
|
+
onCollapse?: (collapsed: boolean) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function Sidebar({ groups, className, header, footer, collapsed = false, onCollapse }: SidebarProps) {
|
|
30
|
+
return (
|
|
31
|
+
<nav className={cn("ph-sidebar flex flex-col h-full", collapsed && "ph-sidebar--collapsed", className)}>
|
|
32
|
+
{header && <div className="ph-sidebar__header px-3 py-3 border-b border-ph-border-subtle">{header}</div>}
|
|
33
|
+
<div className="ph-sidebar__body flex-1 overflow-y-auto px-2 py-3 space-y-5">
|
|
34
|
+
{groups.map((group) => (
|
|
35
|
+
<div key={group.label} className="ph-sidebar__group">
|
|
36
|
+
{!collapsed && (
|
|
37
|
+
<h3 className="ph-sidebar__group-label px-2 mb-1 text-[10px] font-semibold uppercase tracking-wider text-ph-mutedtext">
|
|
38
|
+
{group.label}
|
|
39
|
+
</h3>
|
|
40
|
+
)}
|
|
41
|
+
<div className="ph-sidebar__items space-y-0.5">
|
|
42
|
+
{group.items.map((item) => (
|
|
43
|
+
<SidebarItem key={item.label} {...item} collapsed={collapsed} />
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
{footer && <div className="ph-sidebar__footer px-3 py-3 border-t border-ph-border-subtle">{footer}</div>}
|
|
50
|
+
</nav>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function SidebarItem({ label, icon, badge, active, onClick, collapsed }: SidebarItemDef & { collapsed: boolean }) {
|
|
55
|
+
const Comp = onClick ? "button" : "a";
|
|
56
|
+
return (
|
|
57
|
+
<Comp
|
|
58
|
+
type={onClick ? "button" as const : undefined}
|
|
59
|
+
href={onClick ? undefined : undefined}
|
|
60
|
+
onClick={onClick}
|
|
61
|
+
className={cn(
|
|
62
|
+
"ph-sidebar__item flex items-center gap-2.5 w-full rounded-md px-2.5 py-2 text-sm text-left transition",
|
|
63
|
+
active
|
|
64
|
+
? "bg-ph-selected-bg text-ph-selected-text font-medium"
|
|
65
|
+
: "text-ph-subtle hover:bg-ph-muted hover:text-ph-ink",
|
|
66
|
+
collapsed && "justify-center px-2"
|
|
67
|
+
)}
|
|
68
|
+
title={collapsed ? label : undefined}
|
|
69
|
+
>
|
|
70
|
+
{icon && <span className="ph-sidebar__item-icon shrink-0 w-4 h-4 flex items-center justify-center">{icon}</span>}
|
|
71
|
+
{!collapsed && <span className="ph-sidebar__item-label truncate flex-1">{label}</span>}
|
|
72
|
+
{!collapsed && badge && <span className="ph-sidebar__item-badge shrink-0">{badge}</span>}
|
|
73
|
+
</Comp>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Sidebar.displayName = "Sidebar";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface SnackProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
type?: "regular" | "pill";
|
|
6
|
+
onClose?: () => void;
|
|
7
|
+
wrap?: boolean;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Snack = forwardRef<HTMLSpanElement, SnackProps>(function Snack({
|
|
12
|
+
type = "regular",
|
|
13
|
+
onClose,
|
|
14
|
+
wrap = false,
|
|
15
|
+
className,
|
|
16
|
+
children,
|
|
17
|
+
...props
|
|
18
|
+
}, ref) {
|
|
19
|
+
return (
|
|
20
|
+
<span
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cn(
|
|
23
|
+
"ph-snack",
|
|
24
|
+
type === "pill" && "ph-snack--pill",
|
|
25
|
+
!wrap && "truncate",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
title={typeof children === "string" ? children : undefined}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
<span className="ph-snack__text">{children}</span>
|
|
32
|
+
{onClose && (
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
35
|
+
onClick={(e) => { e.stopPropagation(); onClose(); }}
|
|
36
|
+
className="ph-snack__close"
|
|
37
|
+
aria-label="Remove"
|
|
38
|
+
>
|
|
39
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
40
|
+
<path d="M3 3l6 6M9 3l-6 6" />
|
|
41
|
+
</svg>
|
|
42
|
+
</button>
|
|
43
|
+
)}
|
|
44
|
+
</span>
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
Snack.displayName = "Snack";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
size?: "sm" | "md" | "lg";
|
|
6
|
+
textColored?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Spinner({ size = "sm", textColored = false, className, ...props }: SpinnerProps) {
|
|
10
|
+
return (
|
|
11
|
+
<span
|
|
12
|
+
className={cn(
|
|
13
|
+
"ph-spinner",
|
|
14
|
+
`ph-spinner--${size}`,
|
|
15
|
+
textColored && "ph-spinner--text-colored",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
role="status"
|
|
19
|
+
aria-label="Loading"
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="ph-spinner__svg">
|
|
23
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" opacity={0.2} />
|
|
24
|
+
<path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
|
|
25
|
+
</svg>
|
|
26
|
+
</span>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Spinner.displayName = "Spinner";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
|
|
4
|
+
export type SplotchColor = "purple" | "blue" | "green" | "black" | "white" | "orange" | "red" | "yellow";
|
|
5
|
+
|
|
6
|
+
export interface SplotchProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
color: SplotchColor;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const colorMap: Record<SplotchColor, string> = {
|
|
11
|
+
purple: "ph-splotch--purple",
|
|
12
|
+
blue: "ph-splotch--blue",
|
|
13
|
+
green: "ph-splotch--green",
|
|
14
|
+
black: "ph-splotch--black",
|
|
15
|
+
white: "ph-splotch--white",
|
|
16
|
+
orange: "ph-splotch--orange",
|
|
17
|
+
red: "ph-splotch--red",
|
|
18
|
+
yellow: "ph-splotch--yellow",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function Splotch({ color, className, ...props }: SplotchProps) {
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
className={cn("ph-splotch", colorMap[color], className)}
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<div className="ph-splotch__paint" />
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Splotch.displayName = "Splotch";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { cn } from "@/lib/cn";
|
|
4
|
+
import { IconCheck } from "@/components/icons";
|
|
4
5
|
|
|
5
6
|
interface Step {
|
|
6
7
|
id: string;
|
|
@@ -30,16 +31,14 @@ export function Stepper({ steps, currentStep, className }: StepperProps) {
|
|
|
30
31
|
<div
|
|
31
32
|
className={cn(
|
|
32
33
|
"flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-colors",
|
|
33
|
-
isCompleted && "bg-ph-success text-
|
|
34
|
-
isCurrent && !isError && "bg-ph-brand text-
|
|
35
|
-
isCurrent && isError && "bg-ph-danger text-
|
|
34
|
+
isCompleted && "bg-ph-success text-[var(--ph-on-success)]",
|
|
35
|
+
isCurrent && !isError && "bg-ph-brand text-[var(--ph-on-accent)] ring-4 ring-ph-brand/20",
|
|
36
|
+
isCurrent && isError && "bg-ph-danger text-[var(--ph-on-danger)]",
|
|
36
37
|
isPending && "bg-ph-muted text-ph-mutedtext"
|
|
37
38
|
)}
|
|
38
39
|
>
|
|
39
40
|
{isCompleted ? (
|
|
40
|
-
<
|
|
41
|
-
<path d="M5 13l4 4L19 7" />
|
|
42
|
-
</svg>
|
|
41
|
+
<IconCheck className="h-5 w-5" aria-hidden />
|
|
43
42
|
) : (
|
|
44
43
|
index + 1
|
|
45
44
|
)}
|