@xenide-io/the-old-ui-theme 0.3.0 → 0.3.2
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/chunk-5HSOBJ4F.mjs +5968 -0
- package/dist/{index-D1RTT2Ap.d.mts → index-BgG8Homu.d.mts} +419 -2
- package/dist/{index-D1RTT2Ap.d.ts → index-BgG8Homu.d.ts} +419 -2
- package/dist/index.d.mts +198 -95
- package/dist/index.d.ts +198 -95
- package/dist/index.js +2995 -1136
- package/dist/index.mjs +629 -735
- package/dist/ui.d.mts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +1986 -156
- package/dist/ui.mjs +76 -3
- package/package.json +4 -14
- package/src/app/globals.css +976 -2
- 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/icons.tsx +2 -0
- 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/IconShowcase.tsx +110 -7
- package/src/components/sections/NewComponentsShowcase.tsx +1052 -0
- package/src/components/sections/ProductLayoutsShowcase.tsx +57 -117
- package/src/components/sections/QuillChartShowcase.tsx +92 -0
- package/src/components/sections/QuillDashboardShowcase.tsx +149 -0
- package/src/components/sections/SidebarShowcase.tsx +152 -0
- package/src/components/sections/TableShowcase.tsx +2 -0
- package/src/components/sections/TooltipShowcase.tsx +19 -25
- package/src/components/ui/AlertDialog.tsx +62 -0
- package/src/components/ui/AppLayout.tsx +27 -0
- package/src/components/ui/Autocomplete.tsx +106 -0
- package/src/components/ui/Banner.tsx +63 -0
- package/src/components/ui/ButtonGroup.tsx +16 -0
- package/src/components/ui/Chip.tsx +46 -0
- package/src/components/ui/Collapsible.tsx +46 -0
- 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/InputGroup.tsx +16 -0
- package/src/components/ui/Lettermark.tsx +47 -0
- package/src/components/ui/Link.tsx +32 -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/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/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/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/index.ts +275 -21
- package/dist/chunk-FDMD3IIN.mjs +0 -3831
- 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,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";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
|
|
4
|
+
export type TagType = "default" | "primary" | "success" | "warning" | "danger" | "info" | "muted" | "highlight";
|
|
5
|
+
|
|
6
|
+
export interface TagProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
type?: TagType;
|
|
8
|
+
size?: "sm" | "md";
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
closable?: boolean;
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const typeClass: Record<TagType, string> = {
|
|
16
|
+
default: "ph-tag-default",
|
|
17
|
+
primary: "ph-tag-primary",
|
|
18
|
+
success: "ph-tag-success",
|
|
19
|
+
warning: "ph-tag-warning",
|
|
20
|
+
danger: "ph-tag-danger",
|
|
21
|
+
info: "ph-tag-info",
|
|
22
|
+
muted: "ph-tag-muted",
|
|
23
|
+
highlight: "ph-tag-highlight",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Tag = forwardRef<HTMLDivElement, TagProps>(function Tag({
|
|
27
|
+
type = "default",
|
|
28
|
+
size = "md",
|
|
29
|
+
icon,
|
|
30
|
+
closable = false,
|
|
31
|
+
onClose,
|
|
32
|
+
className,
|
|
33
|
+
children,
|
|
34
|
+
...props
|
|
35
|
+
}, ref) {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
ref={ref}
|
|
39
|
+
className={cn(
|
|
40
|
+
"ph-tag",
|
|
41
|
+
typeClass[type],
|
|
42
|
+
size === "sm" && "ph-tag-sm",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{icon && <span className="ph-tag__icon">{icon}</span>}
|
|
48
|
+
<span className="ph-tag__text">{children}</span>
|
|
49
|
+
{closable && (
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={(e) => { e.stopPropagation(); onClose?.(); }}
|
|
53
|
+
className="ph-tag__close"
|
|
54
|
+
aria-label="Remove"
|
|
55
|
+
>
|
|
56
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
57
|
+
<path d="M3 3l6 6M9 3l-6 6" />
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
Tag.displayName = "Tag";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface ToggleButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
+
pressed?: boolean;
|
|
6
|
+
onPressedChange?: (pressed: boolean) => void;
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ToggleButton = forwardRef<HTMLButtonElement, ToggleButtonProps>(function ToggleButton({
|
|
12
|
+
pressed = false,
|
|
13
|
+
onPressedChange,
|
|
14
|
+
icon,
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}, ref) {
|
|
19
|
+
return (
|
|
20
|
+
<button
|
|
21
|
+
ref={ref}
|
|
22
|
+
type="button"
|
|
23
|
+
aria-pressed={pressed}
|
|
24
|
+
onClick={() => onPressedChange?.(!pressed)}
|
|
25
|
+
className={cn(
|
|
26
|
+
"ph-toggle-btn",
|
|
27
|
+
pressed && "ph-toggle-btn--pressed",
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{icon && <span className="ph-toggle-btn__icon">{icon}</span>}
|
|
33
|
+
{children}
|
|
34
|
+
</button>
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
ToggleButton.displayName = "ToggleButton";
|
|
39
|
+
|
|
40
|
+
export interface ToggleGroupProps {
|
|
41
|
+
type?: "single" | "multiple";
|
|
42
|
+
value?: string | string[];
|
|
43
|
+
onValueChange?: (value: string | string[]) => void;
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ToggleGroup({
|
|
49
|
+
type = "single",
|
|
50
|
+
value,
|
|
51
|
+
onValueChange,
|
|
52
|
+
children,
|
|
53
|
+
className,
|
|
54
|
+
}: ToggleGroupProps) {
|
|
55
|
+
return (
|
|
56
|
+
<div className={cn("ph-toggle-group", className)} role="group">
|
|
57
|
+
{children}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ToggleGroup.displayName = "ToggleGroup";
|