@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.
Files changed (89) hide show
  1. package/dist/chunk-5HSOBJ4F.mjs +5968 -0
  2. package/dist/{index-D1RTT2Ap.d.mts → index-BgG8Homu.d.mts} +419 -2
  3. package/dist/{index-D1RTT2Ap.d.ts → index-BgG8Homu.d.ts} +419 -2
  4. package/dist/index.d.mts +198 -95
  5. package/dist/index.d.ts +198 -95
  6. package/dist/index.js +2995 -1136
  7. package/dist/index.mjs +629 -735
  8. package/dist/ui.d.mts +1 -1
  9. package/dist/ui.d.ts +1 -1
  10. package/dist/ui.js +1986 -156
  11. package/dist/ui.mjs +76 -3
  12. package/package.json +4 -14
  13. package/src/app/globals.css +976 -2
  14. package/src/components/charts/index.ts +9 -8
  15. package/src/components/charts/quill/BarChart.tsx +85 -0
  16. package/src/components/charts/quill/FunnelChart.tsx +49 -0
  17. package/src/components/charts/quill/InsightShell.tsx +27 -0
  18. package/src/components/charts/quill/MetricCard.tsx +44 -0
  19. package/src/components/charts/quill/PieChart.tsx +81 -0
  20. package/src/components/charts/quill/Sparkline.tsx +57 -0
  21. package/src/components/charts/quill/TimeSeriesLineChart.tsx +62 -0
  22. package/src/components/charts/quill/index.ts +9 -0
  23. package/src/components/icons/icons.tsx +2 -0
  24. package/src/components/icons/index.ts +3 -0
  25. package/src/components/icons/lemon-brand-icons.tsx +16 -0
  26. package/src/components/icons/lemon-category-icons.tsx +72 -0
  27. package/src/components/icons/lemon-icons.tsx +57 -0
  28. package/src/components/sections/IconShowcase.tsx +110 -7
  29. package/src/components/sections/NewComponentsShowcase.tsx +1052 -0
  30. package/src/components/sections/ProductLayoutsShowcase.tsx +57 -117
  31. package/src/components/sections/QuillChartShowcase.tsx +92 -0
  32. package/src/components/sections/QuillDashboardShowcase.tsx +149 -0
  33. package/src/components/sections/SidebarShowcase.tsx +152 -0
  34. package/src/components/sections/TableShowcase.tsx +2 -0
  35. package/src/components/sections/TooltipShowcase.tsx +19 -25
  36. package/src/components/ui/AlertDialog.tsx +62 -0
  37. package/src/components/ui/AppLayout.tsx +27 -0
  38. package/src/components/ui/Autocomplete.tsx +106 -0
  39. package/src/components/ui/Banner.tsx +63 -0
  40. package/src/components/ui/ButtonGroup.tsx +16 -0
  41. package/src/components/ui/Chip.tsx +46 -0
  42. package/src/components/ui/Collapsible.tsx +46 -0
  43. package/src/components/ui/Combobox.tsx +119 -0
  44. package/src/components/ui/ContextMenu.tsx +81 -0
  45. package/src/components/ui/DataTable.tsx +133 -0
  46. package/src/components/ui/DatePicker.tsx +96 -0
  47. package/src/components/ui/Dialog.tsx +75 -0
  48. package/src/components/ui/Divider.tsx +39 -0
  49. package/src/components/ui/Dot.tsx +32 -0
  50. package/src/components/ui/InputGroup.tsx +16 -0
  51. package/src/components/ui/Lettermark.tsx +47 -0
  52. package/src/components/ui/Link.tsx +32 -0
  53. package/src/components/ui/LoadingBar.tsx +25 -0
  54. package/src/components/ui/Menubar.tsx +66 -0
  55. package/src/components/ui/Metric.tsx +34 -0
  56. package/src/components/ui/NumberField.tsx +76 -0
  57. package/src/components/ui/Popover.tsx +73 -0
  58. package/src/components/ui/ProgressCircle.tsx +63 -0
  59. package/src/components/ui/Resizable.tsx +71 -0
  60. package/src/components/ui/Row.tsx +51 -0
  61. package/src/components/ui/ScrollArea.tsx +54 -0
  62. package/src/components/ui/Sidebar.tsx +77 -0
  63. package/src/components/ui/Snack.tsx +48 -0
  64. package/src/components/ui/Spinner.tsx +30 -0
  65. package/src/components/ui/Splotch.tsx +33 -0
  66. package/src/components/ui/Table.tsx +2 -0
  67. package/src/components/ui/Tag.tsx +65 -0
  68. package/src/components/ui/Toggle.tsx +62 -0
  69. package/src/components/ui/Tooltip.test.tsx +58 -0
  70. package/src/components/ui/Tooltip.tsx +211 -0
  71. package/src/components/ui/Widget.tsx +31 -0
  72. package/src/components/ui/index.ts +275 -21
  73. package/dist/chunk-FDMD3IIN.mjs +0 -3831
  74. package/src/components/charts/ph-insight-charts.tsx +0 -162
  75. package/src/components/dashboard/DashboardFiltersBar.tsx +0 -19
  76. package/src/components/dashboard/DashboardGrid.tsx +0 -23
  77. package/src/components/dashboard/DashboardInsightPlaceholder.tsx +0 -37
  78. package/src/components/dashboard/DashboardKpiCard.tsx +0 -25
  79. package/src/components/dashboard/DashboardToolbar.tsx +0 -23
  80. package/src/components/dashboard/DashboardWell.tsx +0 -10
  81. package/src/components/dashboard/InsightMiniCard.tsx +0 -26
  82. package/src/components/dashboard/InsightShell.tsx +0 -37
  83. package/src/components/dashboard/InsightShellHeader.tsx +0 -22
  84. package/src/components/dashboard/MiniTrendBars.tsx +0 -51
  85. package/src/components/dashboard/index.ts +0 -31
  86. package/src/components/dashboard/types.ts +0 -9
  87. package/src/components/sections/ChartShowcase.tsx +0 -190
  88. package/src/components/sections/DashboardShowcase.tsx +0 -191
  89. 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";
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import type { CSSProperties, ReactNode } from "react";
2
4
  import { cn } from "@/lib/cn";
3
5
 
@@ -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";