@exxatdesignux/ui 0.0.5

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 (59) hide show
  1. package/package.json +72 -0
  2. package/src/components/ui/avatar.tsx +384 -0
  3. package/src/components/ui/badge.tsx +49 -0
  4. package/src/components/ui/banner.tsx +364 -0
  5. package/src/components/ui/breadcrumb.tsx +120 -0
  6. package/src/components/ui/button.tsx +66 -0
  7. package/src/components/ui/calendar.tsx +220 -0
  8. package/src/components/ui/card.tsx +136 -0
  9. package/src/components/ui/chart.tsx +378 -0
  10. package/src/components/ui/checkbox.tsx +160 -0
  11. package/src/components/ui/coach-mark.tsx +361 -0
  12. package/src/components/ui/collapsible.tsx +33 -0
  13. package/src/components/ui/command.tsx +232 -0
  14. package/src/components/ui/date-picker-field.tsx +186 -0
  15. package/src/components/ui/dialog.tsx +171 -0
  16. package/src/components/ui/drag-handle-grip.tsx +10 -0
  17. package/src/components/ui/drawer.tsx +134 -0
  18. package/src/components/ui/dropdown-menu.tsx +422 -0
  19. package/src/components/ui/field.tsx +238 -0
  20. package/src/components/ui/form.tsx +137 -0
  21. package/src/components/ui/input-group.tsx +156 -0
  22. package/src/components/ui/input-mask.tsx +135 -0
  23. package/src/components/ui/input.tsx +22 -0
  24. package/src/components/ui/kbd.tsx +55 -0
  25. package/src/components/ui/label.tsx +25 -0
  26. package/src/components/ui/payment-card-fields.tsx +65 -0
  27. package/src/components/ui/popover.tsx +46 -0
  28. package/src/components/ui/radio-group.tsx +217 -0
  29. package/src/components/ui/select.tsx +191 -0
  30. package/src/components/ui/selection-tile-grid.tsx +246 -0
  31. package/src/components/ui/separator.tsx +28 -0
  32. package/src/components/ui/sheet.tsx +147 -0
  33. package/src/components/ui/sidebar.tsx +716 -0
  34. package/src/components/ui/skeleton.tsx +13 -0
  35. package/src/components/ui/sonner.tsx +39 -0
  36. package/src/components/ui/status-badge.tsx +109 -0
  37. package/src/components/ui/table.tsx +117 -0
  38. package/src/components/ui/tabs.tsx +90 -0
  39. package/src/components/ui/textarea.tsx +18 -0
  40. package/src/components/ui/tip.tsx +21 -0
  41. package/src/components/ui/toggle-group.tsx +89 -0
  42. package/src/components/ui/toggle-switch.tsx +31 -0
  43. package/src/components/ui/toggle.tsx +48 -0
  44. package/src/components/ui/tooltip.tsx +59 -0
  45. package/src/components/ui/view-segmented-control.tsx +160 -0
  46. package/src/globals.css +1795 -0
  47. package/src/hooks/.gitkeep +0 -0
  48. package/src/hooks/use-app-theme.ts +172 -0
  49. package/src/hooks/use-coach-mark.ts +342 -0
  50. package/src/hooks/use-mobile.ts +31 -0
  51. package/src/hooks/use-mod-key-label.ts +29 -0
  52. package/src/index.ts +55 -0
  53. package/src/lib/compose-refs.ts +15 -0
  54. package/src/lib/date-filter.ts +67 -0
  55. package/src/lib/utils.ts +6 -0
  56. package/src/theme/apply-windows-contrast-theme.ts +29 -0
  57. package/src/theme/windows-contrast-theme.json +147 -0
  58. package/src/theme.css +1130 -0
  59. package/src/types/react-payment-inputs.d.ts +20 -0
@@ -0,0 +1,160 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Segmented “view tabs” control — same visual language as `ListPageTemplate` views toolbar
5
+ * (`bg-muted/60` pill). Uses `role="radiogroup"` + `role="radio"` for exclusive choice (1.3.1).
6
+ *
7
+ * Keyboard: Arrow Left/Right (or Up/Down), Home, End — see onRadioKeyDown.
8
+ */
9
+
10
+ import * as React from "react"
11
+ import { cn } from "../../lib/utils"
12
+ import { Tip } from "./tip"
13
+
14
+ export function viewSegmentedToolbarClass(className?: string) {
15
+ return cn(
16
+ "inline-flex items-center gap-0.5 rounded-lg bg-muted/60 p-[3px]",
17
+ className,
18
+ )
19
+ }
20
+
21
+ export function viewSegmentedButtonClass(
22
+ isActive: boolean,
23
+ opts?: { iconOnly?: boolean },
24
+ ) {
25
+ return cn(
26
+ "inline-flex items-center rounded-md transition-all whitespace-nowrap",
27
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
28
+ opts?.iconOnly
29
+ ? "size-8 min-h-8 min-w-8 shrink-0 justify-center p-0 text-xs"
30
+ : "gap-1.5 px-2.5 py-1 text-xs min-h-8",
31
+ isActive
32
+ ? "bg-background text-foreground font-medium shadow-sm"
33
+ : "text-muted-foreground hover:text-interactive-hover-foreground",
34
+ )
35
+ }
36
+
37
+ export interface ViewSegmentOption<T extends string = string> {
38
+ value: T
39
+ label: string
40
+ /** Full `className` for Font Awesome icon (e.g. `fa-light fa-chart-bar`) */
41
+ icon?: string
42
+ }
43
+
44
+ export interface ViewSegmentedControlProps<T extends string = string> {
45
+ value: T
46
+ onValueChange: (value: T) => void
47
+ options: readonly ViewSegmentOption<T>[]
48
+ /** Accessible name for the group (required — names the radiogroup) */
49
+ "aria-label": string
50
+ /** Optional description id for `aria-describedby` (e.g. helper text) */
51
+ "aria-describedby"?: string
52
+ /** Icon-only triggers (labels in `sr-only` or visible text) */
53
+ iconOnly?: boolean
54
+ className?: string
55
+ /** Tooltip on each segment (defaults to `iconOnly` — recommended for icon-only) */
56
+ showTooltips?: boolean
57
+ /** Tooltip position */
58
+ tooltipSide?: "top" | "bottom" | "left" | "right"
59
+ }
60
+
61
+ export function ViewSegmentedControl<T extends string>({
62
+ value,
63
+ onValueChange,
64
+ options,
65
+ "aria-label": ariaLabel,
66
+ "aria-describedby": ariaDescribedBy,
67
+ iconOnly = false,
68
+ className,
69
+ showTooltips,
70
+ tooltipSide = "top",
71
+ }: ViewSegmentedControlProps<T>) {
72
+ const tips = showTooltips ?? iconOnly
73
+ const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([])
74
+
75
+ React.useLayoutEffect(() => {
76
+ itemRefs.current = itemRefs.current.slice(0, options.length)
77
+ }, [options.length])
78
+
79
+ const focusIndex = React.useCallback(
80
+ (index: number) => {
81
+ const len = options.length
82
+ if (len === 0) return
83
+ const i = ((index % len) + len) % len
84
+ onValueChange(options[i].value)
85
+ requestAnimationFrame(() => {
86
+ itemRefs.current[i]?.focus()
87
+ })
88
+ },
89
+ [onValueChange, options],
90
+ )
91
+
92
+ const onRadioKeyDown = React.useCallback(
93
+ (e: React.KeyboardEvent<HTMLButtonElement>, index: number) => {
94
+ const len = options.length
95
+ if (len === 0) return
96
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
97
+ e.preventDefault()
98
+ focusIndex(index + 1)
99
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
100
+ e.preventDefault()
101
+ focusIndex(index - 1)
102
+ } else if (e.key === "Home") {
103
+ e.preventDefault()
104
+ focusIndex(0)
105
+ } else if (e.key === "End") {
106
+ e.preventDefault()
107
+ focusIndex(len - 1)
108
+ }
109
+ },
110
+ [focusIndex, options.length],
111
+ )
112
+
113
+ return (
114
+ <div
115
+ role="radiogroup"
116
+ aria-label={ariaLabel}
117
+ aria-describedby={ariaDescribedBy}
118
+ data-slot="view-segmented-toolbar"
119
+ className={cn(viewSegmentedToolbarClass(), "w-fit min-w-0 shrink-0", className)}
120
+ >
121
+ {options.map((opt, index) => {
122
+ const isActive = opt.value === value
123
+ const tabIndex = isActive ? 0 : -1
124
+
125
+ const button = (
126
+ <button
127
+ ref={el => {
128
+ itemRefs.current[index] = el
129
+ }}
130
+ type="button"
131
+ role="radio"
132
+ aria-checked={isActive}
133
+ aria-label={iconOnly ? opt.label : undefined}
134
+ tabIndex={tabIndex}
135
+ onKeyDown={e => onRadioKeyDown(e, index)}
136
+ onClick={() => onValueChange(opt.value)}
137
+ data-slot="view-segmented-item"
138
+ className={viewSegmentedButtonClass(isActive, { iconOnly })}
139
+ >
140
+ {opt.icon ? (
141
+ <i
142
+ className={cn(opt.icon, iconOnly ? "text-[13px]" : "text-xs")}
143
+ aria-hidden="true"
144
+ />
145
+ ) : null}
146
+ {!iconOnly ? opt.label : null}
147
+ </button>
148
+ )
149
+
150
+ return tips ? (
151
+ <Tip key={opt.value} label={opt.label} side={tooltipSide}>
152
+ {button}
153
+ </Tip>
154
+ ) : (
155
+ <React.Fragment key={opt.value}>{button}</React.Fragment>
156
+ )
157
+ })}
158
+ </div>
159
+ )
160
+ }