@gunjo/ui 0.0.1-alpha.1 → 0.0.1-alpha.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 (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +90 -0
  3. package/README.md +52 -91
  4. package/package.json +47 -6
  5. package/src/components/display/Accordion.tsx +185 -0
  6. package/src/components/display/AccordionGroup.tsx +155 -0
  7. package/src/components/display/ActionDataTable.tsx +413 -0
  8. package/src/components/display/ActivityTimelineCard.tsx +483 -0
  9. package/src/components/display/AnalyticsCard.tsx +167 -0
  10. package/src/components/display/AssetCard.tsx +242 -0
  11. package/src/components/display/AssetGrid.tsx +164 -0
  12. package/src/components/display/Avatar.tsx +127 -0
  13. package/src/components/display/AvatarGroup.tsx +131 -0
  14. package/src/components/{atoms → display}/Badge.tsx +3 -3
  15. package/src/components/display/BarChart.tsx +247 -0
  16. package/src/components/{molecules → display}/Card.tsx +1 -1
  17. package/src/components/display/Carousel.tsx +593 -0
  18. package/src/components/display/ChartLegend.tsx +124 -0
  19. package/src/components/display/ChatMessage.tsx +382 -0
  20. package/src/components/display/ChoroplethMap.tsx +613 -0
  21. package/src/components/display/Code.tsx +42 -0
  22. package/src/components/display/CodeBlock.tsx +338 -0
  23. package/src/components/display/ColorSwatch.tsx +71 -0
  24. package/src/components/display/ConcentricProgressCard.tsx +545 -0
  25. package/src/components/display/DataTable.tsx +522 -0
  26. package/src/components/display/DistributionBar.tsx +102 -0
  27. package/src/components/display/DocNote.tsx +36 -0
  28. package/src/components/display/DonutChart.tsx +257 -0
  29. package/src/components/display/EmptyState.tsx +44 -0
  30. package/src/components/display/FileTree.tsx +180 -0
  31. package/src/components/display/GaugeChart.tsx +219 -0
  32. package/src/components/display/HeatmapChart.tsx +266 -0
  33. package/src/components/display/Icon.tsx +66 -0
  34. package/src/components/display/ImagePreview.tsx +140 -0
  35. package/src/components/{atoms → display}/Img.tsx +46 -12
  36. package/src/components/display/LabeledDonutCard.tsx +475 -0
  37. package/src/components/display/LineChart.tsx +464 -0
  38. package/src/components/{molecules → display}/List.tsx +20 -13
  39. package/src/components/display/MarkdownRenderer.tsx +157 -0
  40. package/src/components/display/MetadataList.tsx +81 -0
  41. package/src/components/display/MiniDistributionBarCard.tsx +314 -0
  42. package/src/components/display/PieChart.tsx +234 -0
  43. package/src/components/display/QuadrantMatrix.tsx +330 -0
  44. package/src/components/display/RadarChart.tsx +335 -0
  45. package/src/components/display/RadialBarChart.tsx +264 -0
  46. package/src/components/display/RetentionCohortCard.tsx +350 -0
  47. package/src/components/display/RibbonChart.tsx +618 -0
  48. package/src/components/display/SearchableAccordion.tsx +270 -0
  49. package/src/components/display/SegmentTimelineCard.tsx +452 -0
  50. package/src/components/display/SegmentedGaugeCard.tsx +607 -0
  51. package/src/components/display/Spacer.tsx +51 -0
  52. package/src/components/display/SparklineChart.tsx +394 -0
  53. package/src/components/display/StackedBarChart.tsx +393 -0
  54. package/src/components/display/Statistic.tsx +70 -0
  55. package/src/components/{molecules → display}/Table.tsx +22 -7
  56. package/src/components/display/Tag.tsx +80 -0
  57. package/src/components/display/TagEditor.tsx +141 -0
  58. package/src/components/display/Timeline.tsx +121 -0
  59. package/src/components/{atoms → display}/ToolPill.tsx +42 -18
  60. package/src/components/display/TreeView.tsx +226 -0
  61. package/src/components/display/chart-tooltip.tsx +423 -0
  62. package/src/components/display/chart-utils.ts +71 -0
  63. package/src/components/display/circular-chart-utils.ts +147 -0
  64. package/src/components/display/generated/default-variant-keys.ts +90 -0
  65. package/src/components/display/generated/variant-keys.ts +169 -0
  66. package/src/components/{atoms → feedback}/Alert.tsx +12 -5
  67. package/src/components/feedback/Banner.tsx +90 -0
  68. package/src/components/{molecules → feedback}/NotificationCenter.tsx +64 -31
  69. package/src/components/feedback/ProgressWidget.tsx +44 -0
  70. package/src/components/{atoms → feedback}/Spinner.tsx +2 -2
  71. package/src/components/{molecules → feedback}/StatusBar.tsx +4 -4
  72. package/src/components/feedback/StatusScreen.tsx +148 -0
  73. package/src/components/{molecules → feedback}/Stepper.tsx +10 -5
  74. package/src/components/feedback/Toast.tsx +108 -0
  75. package/src/components/feedback/ToastProvider.tsx +78 -0
  76. package/src/components/feedback/generated/default-variant-keys.ts +16 -0
  77. package/src/components/feedback/generated/variant-keys.ts +21 -0
  78. package/src/components/generated/component-manifest.ts +1568 -454
  79. package/src/components/generated/component-style-hints.ts +1958 -718
  80. package/src/components/{atoms → inputs}/ButtonVariants.ts +13 -3
  81. package/src/components/inputs/Calendar.tsx +212 -0
  82. package/src/components/inputs/ChatComposer.tsx +75 -0
  83. package/src/components/inputs/ChatInput.tsx +528 -0
  84. package/src/components/{atoms → inputs}/Checkbox.tsx +2 -2
  85. package/src/components/inputs/Combobox.tsx +175 -0
  86. package/src/components/inputs/CopyButton.tsx +187 -0
  87. package/src/components/inputs/DatePicker.tsx +519 -0
  88. package/src/components/inputs/DateRangePicker.tsx +878 -0
  89. package/src/components/inputs/EditableField.tsx +182 -0
  90. package/src/components/{organisms → inputs}/FileUploader.tsx +24 -9
  91. package/src/components/inputs/FilterButton.tsx +163 -0
  92. package/src/components/{molecules → inputs}/Form.tsx +20 -3
  93. package/src/components/{atoms → inputs}/Input.tsx +2 -0
  94. package/src/components/inputs/InputOTP.tsx +75 -0
  95. package/src/components/inputs/Mention.tsx +279 -0
  96. package/src/components/inputs/NumberInput.tsx +109 -0
  97. package/src/components/inputs/PasswordGroup.tsx +138 -0
  98. package/src/components/inputs/PasswordInput.tsx +74 -0
  99. package/src/components/inputs/PasswordRequirementList.tsx +96 -0
  100. package/src/components/inputs/PasswordStrengthMeter.tsx +93 -0
  101. package/src/components/inputs/PhoneInput.tsx +99 -0
  102. package/src/components/inputs/PostalCodeInput.tsx +98 -0
  103. package/src/components/inputs/RangeSlider.tsx +129 -0
  104. package/src/components/inputs/SearchInput.tsx +76 -0
  105. package/src/components/inputs/Select.tsx +39 -0
  106. package/src/components/{atoms → inputs}/Slider.tsx +18 -5
  107. package/src/components/{molecules → inputs}/SortButton.tsx +5 -2
  108. package/src/components/{atoms → inputs}/Switch.tsx +15 -4
  109. package/src/components/inputs/TagInput.tsx +114 -0
  110. package/src/components/{atoms → inputs}/Textarea.tsx +1 -0
  111. package/src/components/inputs/TimePicker.tsx +150 -0
  112. package/src/components/inputs/Toggle.tsx +48 -0
  113. package/src/components/{atoms → inputs}/ToggleGroup.tsx +2 -2
  114. package/src/components/inputs/TooltipButton.tsx +148 -0
  115. package/src/components/inputs/VoiceInputButton.tsx +317 -0
  116. package/src/components/inputs/calendar-holidays.ts +56 -0
  117. package/src/components/inputs/generated/default-variant-keys.ts +32 -0
  118. package/src/components/{atoms → inputs}/generated/variant-keys.ts +19 -27
  119. package/src/components/layout/AspectRatio.tsx +12 -0
  120. package/src/components/layout/AssetInspectorPanel.tsx +416 -0
  121. package/src/components/layout/Cluster.tsx +56 -0
  122. package/src/components/layout/CollapsiblePanelToggle.tsx +94 -0
  123. package/src/components/layout/Container.tsx +43 -0
  124. package/src/components/layout/DeviceFrame.tsx +227 -0
  125. package/src/components/layout/Grid.tsx +65 -0
  126. package/src/components/layout/HStack.tsx +73 -0
  127. package/src/components/{organisms → layout}/InspectorPanel.tsx +6 -5
  128. package/src/components/layout/MarqueeFrame.tsx +158 -0
  129. package/src/components/layout/Resizable.tsx +94 -0
  130. package/src/components/layout/ScrollArea.tsx +71 -0
  131. package/src/components/{organisms → layout}/SpatialCanvas.tsx +12 -7
  132. package/src/components/layout/VStack.tsx +69 -0
  133. package/src/components/layout/generated/default-variant-keys.ts +16 -0
  134. package/src/components/layout/generated/variant-keys.ts +21 -0
  135. package/src/components/{molecules → navigation}/Breadcrumb.tsx +5 -4
  136. package/src/components/navigation/Command.tsx +266 -0
  137. package/src/components/navigation/CommandPalette.tsx +83 -0
  138. package/src/components/navigation/DocumentPager.tsx +171 -0
  139. package/src/components/navigation/Footer.tsx +88 -0
  140. package/src/components/navigation/Header.tsx +80 -0
  141. package/src/components/{molecules → navigation}/Menubar.tsx +45 -12
  142. package/src/components/navigation/NavigationMenu.tsx +128 -0
  143. package/src/components/navigation/PageAside.tsx +84 -0
  144. package/src/components/{molecules → navigation}/Pagination.tsx +60 -7
  145. package/src/components/{organisms → navigation}/RightRail.tsx +1 -1
  146. package/src/components/navigation/Sidebar.tsx +223 -0
  147. package/src/components/navigation/SidebarItem.tsx +160 -0
  148. package/src/components/{molecules → navigation}/Tabs.tsx +2 -2
  149. package/src/components/navigation/TextLink.tsx +71 -0
  150. package/src/components/navigation/generated/default-variant-keys.ts +12 -0
  151. package/src/components/navigation/generated/variant-keys.ts +13 -0
  152. package/src/components/overlay/AIChatInput.tsx +5 -0
  153. package/src/components/overlay/AIChatMessage.tsx +6 -0
  154. package/src/components/overlay/AlertDialog.tsx +145 -0
  155. package/src/components/overlay/ChatPanel.tsx +180 -0
  156. package/src/components/{molecules → overlay}/ContextMenu.tsx +65 -29
  157. package/src/components/{molecules → overlay}/Dialog.tsx +21 -13
  158. package/src/components/overlay/Drawer.tsx +131 -0
  159. package/src/components/{molecules → overlay}/DropdownMenu.tsx +52 -17
  160. package/src/components/overlay/FloatingPanel.tsx +90 -0
  161. package/src/components/overlay/HoverCard.tsx +36 -0
  162. package/src/components/overlay/MediaLightbox.tsx +403 -0
  163. package/src/components/overlay/MediaPickerDialog.tsx +198 -0
  164. package/src/components/overlay/Modal.tsx +103 -0
  165. package/src/components/overlay/OnboardingFlow.tsx +172 -0
  166. package/src/components/overlay/Popover.tsx +36 -0
  167. package/src/components/overlay/ShareModal.tsx +324 -0
  168. package/src/components/{molecules → overlay}/Sheet.tsx +76 -19
  169. package/src/components/overlay/Tooltip.tsx +130 -0
  170. package/src/components/overlay/generated/default-variant-keys.ts +14 -0
  171. package/src/components/overlay/generated/variant-keys.ts +17 -0
  172. package/src/components/patterns/BlogTemplate.tsx +46 -0
  173. package/src/components/{templates → patterns}/DashboardTemplate.tsx +2 -2
  174. package/src/components/patterns/DocsTemplate.tsx +41 -0
  175. package/src/components/{templates → patterns}/MediaLibraryTemplate.tsx +1 -1
  176. package/src/components/patterns/OnboardingTemplate.tsx +32 -0
  177. package/src/components/patterns/PricingTemplate.tsx +106 -0
  178. package/src/globals.css +173 -22
  179. package/src/index.ts +177 -76
  180. package/tailwind-theme-extend.cjs +48 -3
  181. package/design/atoms-metadata.json +0 -82
  182. package/design/molecules-metadata.json +0 -130
  183. package/design/organisms-metadata.json +0 -38
  184. package/design/templates-metadata.json +0 -38
  185. package/src/components/atoms/Avatar.tsx +0 -57
  186. package/src/components/atoms/Select.tsx +0 -28
  187. package/src/components/atoms/generated/default-variant-keys.ts +0 -36
  188. package/src/components/molecules/AIChatInput.tsx +0 -140
  189. package/src/components/molecules/AIChatMessage.tsx +0 -109
  190. package/src/components/molecules/Accordion.tsx +0 -99
  191. package/src/components/molecules/Calendar.tsx +0 -60
  192. package/src/components/molecules/Carousel.tsx +0 -261
  193. package/src/components/molecules/Command.tsx +0 -152
  194. package/src/components/molecules/FilterButton.tsx +0 -133
  195. package/src/components/molecules/HoverCard.tsx +0 -29
  196. package/src/components/molecules/Modal.tsx +0 -66
  197. package/src/components/molecules/Popover.tsx +0 -31
  198. package/src/components/molecules/ProgressWidget.tsx +0 -40
  199. package/src/components/molecules/Resizable.tsx +0 -47
  200. package/src/components/molecules/ScrollArea.tsx +0 -48
  201. package/src/components/molecules/SidebarItem.tsx +0 -134
  202. package/src/components/molecules/Toast.tsx +0 -57
  203. package/src/components/molecules/Tooltip.tsx +0 -30
  204. package/src/components/molecules/generated/default-variant-keys.ts +0 -22
  205. package/src/components/molecules/generated/variant-keys.ts +0 -33
  206. package/src/components/organisms/CommandPalette.tsx +0 -58
  207. package/src/components/organisms/FloatingPanel.tsx +0 -46
  208. package/src/components/organisms/ShareModal.tsx +0 -182
  209. package/src/components/organisms/ToastProvider.tsx +0 -49
  210. /package/src/components/{atoms → display}/Kbd.tsx +0 -0
  211. /package/src/components/{atoms → display}/Separator.tsx +0 -0
  212. /package/src/components/{atoms → display}/Skeleton.tsx +0 -0
  213. /package/src/components/{atoms → feedback}/Progress.tsx +0 -0
  214. /package/src/components/{atoms → inputs}/Button.tsx +0 -0
  215. /package/src/components/{atoms → inputs}/Label.tsx +0 -0
  216. /package/src/components/{atoms → inputs}/RadioGroup.tsx +0 -0
  217. /package/src/components/{organisms → navigation}/AppRail.tsx +0 -0
  218. /package/src/components/{templates → patterns}/AuthTemplate.tsx +0 -0
  219. /package/src/components/{templates → patterns}/BannalyzeTemplate.tsx +0 -0
  220. /package/src/components/{templates → patterns}/ChatTemplate.tsx +0 -0
  221. /package/src/components/{templates → patterns}/EditorTemplate.tsx +0 -0
  222. /package/src/components/{templates → patterns}/KanbanTemplate.tsx +0 -0
  223. /package/src/components/{templates → patterns}/LandingTemplate.tsx +0 -0
  224. /package/src/components/{templates → patterns}/SettingsTemplate.tsx +0 -0
@@ -0,0 +1,96 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ IconCheck as Check,
6
+ IconCircle as Circle,
7
+ IconX as X,
8
+ } from "@tabler/icons-react"
9
+
10
+ import { cn } from "../../lib/utils"
11
+
12
+ export interface PasswordRequirement {
13
+ id: string
14
+ label: React.ReactNode
15
+ met?: boolean
16
+ description?: React.ReactNode
17
+ }
18
+
19
+ export interface PasswordRequirementListProps
20
+ extends React.HTMLAttributes<HTMLUListElement> {
21
+ requirements: PasswordRequirement[]
22
+ metLabel?: string
23
+ unmetLabel?: string
24
+ pendingLabel?: string
25
+ }
26
+
27
+ const PasswordRequirementList = React.forwardRef<
28
+ HTMLUListElement,
29
+ PasswordRequirementListProps
30
+ >(
31
+ (
32
+ {
33
+ className,
34
+ requirements,
35
+ metLabel = "Requirement met",
36
+ unmetLabel = "Requirement not met",
37
+ pendingLabel = "Requirement pending",
38
+ ...props
39
+ },
40
+ ref
41
+ ) => {
42
+ return (
43
+ <ul
44
+ ref={ref}
45
+ className={cn("grid gap-1.5 text-sm", className)}
46
+ data-slot="password-requirement-list"
47
+ {...props}
48
+ >
49
+ {requirements.map((requirement) => {
50
+ const state =
51
+ requirement.met === undefined
52
+ ? "pending"
53
+ : requirement.met
54
+ ? "met"
55
+ : "unmet"
56
+ const label =
57
+ state === "met"
58
+ ? metLabel
59
+ : state === "unmet"
60
+ ? unmetLabel
61
+ : pendingLabel
62
+ const Icon =
63
+ state === "met" ? Check : state === "unmet" ? X : Circle
64
+
65
+ return (
66
+ <li
67
+ key={requirement.id}
68
+ className={cn(
69
+ "grid grid-cols-[auto_minmax(0,1fr)] items-start gap-2",
70
+ state === "met" && "text-success-strong",
71
+ state === "unmet" && "text-destructive-strong",
72
+ state === "pending" && "text-muted-foreground"
73
+ )}
74
+ >
75
+ <Icon
76
+ className="mt-0.5 h-4 w-4 shrink-0"
77
+ aria-label={label}
78
+ />
79
+ <span className="min-w-0">
80
+ <span>{requirement.label}</span>
81
+ {requirement.description ? (
82
+ <span className="block text-xs text-muted-foreground">
83
+ {requirement.description}
84
+ </span>
85
+ ) : null}
86
+ </span>
87
+ </li>
88
+ )
89
+ })}
90
+ </ul>
91
+ )
92
+ }
93
+ )
94
+ PasswordRequirementList.displayName = "PasswordRequirementList"
95
+
96
+ export { PasswordRequirementList }
@@ -0,0 +1,93 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ export interface PasswordStrengthMeterProps
8
+ extends React.HTMLAttributes<HTMLDivElement> {
9
+ score: number
10
+ maxScore?: number
11
+ label?: React.ReactNode
12
+ description?: React.ReactNode
13
+ valueLabel?: React.ReactNode
14
+ }
15
+
16
+ function getStrengthClass(score: number, maxScore: number) {
17
+ const ratio = maxScore <= 0 ? 0 : score / maxScore
18
+ if (ratio >= 0.75) return "bg-success"
19
+ if (ratio >= 0.5) return "bg-warning"
20
+ if (ratio > 0) return "bg-destructive"
21
+ return "bg-muted"
22
+ }
23
+
24
+ const PasswordStrengthMeter = React.forwardRef<
25
+ HTMLDivElement,
26
+ PasswordStrengthMeterProps
27
+ >(
28
+ (
29
+ {
30
+ className,
31
+ score,
32
+ maxScore = 4,
33
+ label = "Password strength",
34
+ description,
35
+ valueLabel,
36
+ ...props
37
+ },
38
+ ref
39
+ ) => {
40
+ const normalizedMax = Math.max(1, maxScore)
41
+ const normalizedScore = Math.max(0, Math.min(score, normalizedMax))
42
+ const segments = Array.from({ length: normalizedMax }, (_, index) => index)
43
+ const meterId = React.useId()
44
+
45
+ return (
46
+ <div
47
+ ref={ref}
48
+ className={cn("grid gap-2", className)}
49
+ data-slot="password-strength-meter"
50
+ {...props}
51
+ >
52
+ <div className="flex min-w-0 items-center justify-between gap-3">
53
+ <span id={meterId} className="text-sm font-medium">
54
+ {label}
55
+ </span>
56
+ {valueLabel ? (
57
+ <span className="text-xs font-medium text-muted-foreground">
58
+ {valueLabel}
59
+ </span>
60
+ ) : null}
61
+ </div>
62
+ <div
63
+ className="grid gap-1"
64
+ style={{ gridTemplateColumns: `repeat(${normalizedMax}, minmax(0, 1fr))` }}
65
+ role="meter"
66
+ aria-labelledby={meterId}
67
+ aria-valuemin={0}
68
+ aria-valuemax={normalizedMax}
69
+ aria-valuenow={normalizedScore}
70
+ >
71
+ {segments.map((segment) => (
72
+ <span
73
+ key={segment}
74
+ className={cn(
75
+ "h-1.5 rounded-full",
76
+ segment < normalizedScore
77
+ ? getStrengthClass(normalizedScore, normalizedMax)
78
+ : "bg-muted"
79
+ )}
80
+ aria-hidden="true"
81
+ />
82
+ ))}
83
+ </div>
84
+ {description ? (
85
+ <p className="text-xs text-muted-foreground">{description}</p>
86
+ ) : null}
87
+ </div>
88
+ )
89
+ }
90
+ )
91
+ PasswordStrengthMeter.displayName = "PasswordStrengthMeter"
92
+
93
+ export { PasswordStrengthMeter }
@@ -0,0 +1,99 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
7
+ import { Input, type InputProps } from "./Input"
8
+
9
+ export interface PhoneInputProps
10
+ extends Omit<InputProps, "type" | "value" | "defaultValue" | "onChange"> {
11
+ value?: string
12
+ defaultValue?: string
13
+ onValueChange?: (value: string) => void
14
+ countryCallingCode?: string
15
+ countryLabel?: React.ReactNode
16
+ formatValue?: (value: string) => string
17
+ disabledReason?: React.ReactNode
18
+ }
19
+
20
+ function formatJapanesePhone(value: string) {
21
+ const digits = value.replace(/\D/g, "").slice(0, 11)
22
+ if (digits.length <= 3) return digits
23
+ if (digits.length <= 7) return `${digits.slice(0, 3)}-${digits.slice(3)}`
24
+ return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`
25
+ }
26
+
27
+ const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
28
+ (
29
+ {
30
+ className,
31
+ value,
32
+ defaultValue,
33
+ onValueChange,
34
+ countryCallingCode = "+81",
35
+ countryLabel = "Japan",
36
+ formatValue = formatJapanesePhone,
37
+ disabled,
38
+ disabledReason,
39
+ ...props
40
+ },
41
+ ref
42
+ ) => {
43
+ const isControlled = value !== undefined
44
+ const [internalValue, setInternalValue] = React.useState(defaultValue ?? "")
45
+ const resolvedValue = isControlled ? value : internalValue
46
+
47
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
48
+ const next = formatValue(event.target.value)
49
+ if (!isControlled) {
50
+ setInternalValue(next)
51
+ }
52
+ onValueChange?.(next)
53
+ }
54
+
55
+ const control = (
56
+ <div
57
+ className={cn(
58
+ "inline-flex w-[280px] max-w-full items-stretch rounded-lg border border-input bg-transparent shadow-sm focus-within:ring-1 focus-within:ring-ring",
59
+ disabled && "opacity-50",
60
+ className
61
+ )}
62
+ data-slot="phone-input"
63
+ >
64
+ <span className="inline-flex min-w-16 items-center justify-center border-r border-input px-3 text-sm text-muted-foreground">
65
+ <span className="sr-only">{countryLabel}</span>
66
+ {countryCallingCode}
67
+ </span>
68
+ <Input
69
+ ref={ref}
70
+ type="tel"
71
+ inputMode="tel"
72
+ value={resolvedValue}
73
+ onChange={handleChange}
74
+ disabled={disabled}
75
+ className="h-9 min-w-0 flex-1 border-0 shadow-none focus-visible:ring-0"
76
+ {...props}
77
+ />
78
+ </div>
79
+ )
80
+
81
+ if (disabled && disabledReason) {
82
+ return (
83
+ <Tooltip>
84
+ <TooltipTrigger asChild>
85
+ <span className="inline-flex max-w-full" tabIndex={0}>
86
+ {control}
87
+ </span>
88
+ </TooltipTrigger>
89
+ <TooltipContent>{disabledReason}</TooltipContent>
90
+ </Tooltip>
91
+ )
92
+ }
93
+
94
+ return control
95
+ }
96
+ )
97
+ PhoneInput.displayName = "PhoneInput"
98
+
99
+ export { PhoneInput, formatJapanesePhone }
@@ -0,0 +1,98 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
7
+ import { Input, type InputProps } from "./Input"
8
+
9
+ export interface PostalCodeInputProps
10
+ extends Omit<InputProps, "type" | "value" | "defaultValue" | "onChange" | "prefix"> {
11
+ value?: string
12
+ defaultValue?: string
13
+ onValueChange?: (value: string) => void
14
+ prefix?: React.ReactNode
15
+ formatValue?: (value: string) => string
16
+ disabledReason?: React.ReactNode
17
+ }
18
+
19
+ function formatJapanesePostalCode(value: string) {
20
+ const digits = value.replace(/\D/g, "").slice(0, 7)
21
+ if (digits.length <= 3) return digits
22
+ return `${digits.slice(0, 3)}-${digits.slice(3)}`
23
+ }
24
+
25
+ const PostalCodeInput = React.forwardRef<
26
+ HTMLInputElement,
27
+ PostalCodeInputProps
28
+ >(
29
+ (
30
+ {
31
+ className,
32
+ value,
33
+ defaultValue,
34
+ onValueChange,
35
+ prefix = "〒",
36
+ formatValue = formatJapanesePostalCode,
37
+ disabled,
38
+ disabledReason,
39
+ ...props
40
+ },
41
+ ref
42
+ ) => {
43
+ const isControlled = value !== undefined
44
+ const [internalValue, setInternalValue] = React.useState(defaultValue ?? "")
45
+ const resolvedValue = isControlled ? value : internalValue
46
+
47
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
48
+ const next = formatValue(event.target.value)
49
+ if (!isControlled) {
50
+ setInternalValue(next)
51
+ }
52
+ onValueChange?.(next)
53
+ }
54
+
55
+ const control = (
56
+ <div
57
+ className={cn(
58
+ "inline-flex w-[220px] max-w-full items-stretch rounded-lg border border-input bg-transparent shadow-sm focus-within:ring-1 focus-within:ring-ring",
59
+ disabled && "opacity-50",
60
+ className
61
+ )}
62
+ data-slot="postal-code-input"
63
+ >
64
+ <span className="inline-flex items-center justify-center border-r border-input px-3 text-sm text-muted-foreground">
65
+ {prefix}
66
+ </span>
67
+ <Input
68
+ ref={ref}
69
+ type="text"
70
+ inputMode="numeric"
71
+ value={resolvedValue}
72
+ onChange={handleChange}
73
+ disabled={disabled}
74
+ className="h-9 min-w-0 flex-1 border-0 shadow-none focus-visible:ring-0"
75
+ {...props}
76
+ />
77
+ </div>
78
+ )
79
+
80
+ if (disabled && disabledReason) {
81
+ return (
82
+ <Tooltip>
83
+ <TooltipTrigger asChild>
84
+ <span className="inline-flex max-w-full" tabIndex={0}>
85
+ {control}
86
+ </span>
87
+ </TooltipTrigger>
88
+ <TooltipContent>{disabledReason}</TooltipContent>
89
+ </Tooltip>
90
+ )
91
+ }
92
+
93
+ return control
94
+ }
95
+ )
96
+ PostalCodeInput.displayName = "PostalCodeInput"
97
+
98
+ export { PostalCodeInput, formatJapanesePostalCode }
@@ -0,0 +1,129 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ export interface RangeSliderProps
8
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue" | "onChange"> {
9
+ value?: [number, number]
10
+ defaultValue?: [number, number]
11
+ onValueChange?: (value: [number, number]) => void
12
+ min?: number
13
+ max?: number
14
+ step?: number
15
+ minLabel?: string
16
+ maxLabel?: string
17
+ disabled?: boolean
18
+ trackClassName?: string
19
+ rangeClassName?: string
20
+ thumbClassName?: string
21
+ }
22
+
23
+ function clamp(value: number, min: number, max: number) {
24
+ return Math.min(max, Math.max(min, value))
25
+ }
26
+
27
+ function roundToStep(value: number, step: number) {
28
+ const precision = String(step).split(".")[1]?.length ?? 0
29
+ return Number((Math.round(value / step) * step).toFixed(precision))
30
+ }
31
+
32
+ const RangeSlider = React.forwardRef<HTMLDivElement, RangeSliderProps>(
33
+ (
34
+ {
35
+ className,
36
+ value,
37
+ defaultValue,
38
+ onValueChange,
39
+ min = 0,
40
+ max = 100,
41
+ step = 1,
42
+ minLabel = "Minimum value",
43
+ maxLabel = "Maximum value",
44
+ disabled,
45
+ trackClassName,
46
+ rangeClassName,
47
+ thumbClassName,
48
+ ...props
49
+ },
50
+ ref
51
+ ) => {
52
+ const isControlled = value !== undefined
53
+ const [internalValue, setInternalValue] = React.useState<[number, number]>(
54
+ defaultValue ?? [min, max]
55
+ )
56
+ const currentValue = value ?? internalValue
57
+ const minValue = clamp(Math.min(currentValue[0], currentValue[1]), min, max)
58
+ const maxValue = clamp(Math.max(currentValue[0], currentValue[1]), min, max)
59
+ const minPercent = ((minValue - min) / (max - min)) * 100
60
+ const maxPercent = ((maxValue - min) / (max - min)) * 100
61
+
62
+ const commit = (nextValue: [number, number]) => {
63
+ const next: [number, number] = [
64
+ clamp(roundToStep(Math.min(nextValue[0], nextValue[1]), step), min, max),
65
+ clamp(roundToStep(Math.max(nextValue[0], nextValue[1]), step), min, max),
66
+ ]
67
+ if (!isControlled) setInternalValue(next)
68
+ onValueChange?.(next)
69
+ }
70
+
71
+ return (
72
+ <div
73
+ ref={ref}
74
+ className={cn(
75
+ "relative flex h-7 w-[240px] min-w-0 touch-none select-none items-center",
76
+ disabled && "pointer-events-none opacity-50",
77
+ className
78
+ )}
79
+ data-slot="range-slider"
80
+ {...props}
81
+ >
82
+ <div
83
+ className={cn("absolute left-0 right-0 h-2 rounded-full bg-input", trackClassName)}
84
+ aria-hidden="true"
85
+ />
86
+ <div
87
+ className={cn("absolute h-2 rounded-full bg-primary", rangeClassName)}
88
+ style={{ left: `${minPercent}%`, right: `${100 - maxPercent}%` }}
89
+ aria-hidden="true"
90
+ />
91
+ <input
92
+ type="range"
93
+ min={min}
94
+ max={max}
95
+ step={step}
96
+ value={minValue}
97
+ disabled={disabled}
98
+ onChange={(event) => commit([Number(event.currentTarget.value), maxValue])}
99
+ className={cn(
100
+ "pointer-events-none absolute inset-0 z-20 h-7 w-full appearance-none bg-transparent",
101
+ "[&::-moz-range-thumb]:pointer-events-auto [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-foreground [&::-moz-range-thumb]:bg-background",
102
+ "[&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-foreground [&::-webkit-slider-thumb]:bg-background",
103
+ thumbClassName
104
+ )}
105
+ aria-label={minLabel}
106
+ />
107
+ <input
108
+ type="range"
109
+ min={min}
110
+ max={max}
111
+ step={step}
112
+ value={maxValue}
113
+ disabled={disabled}
114
+ onChange={(event) => commit([minValue, Number(event.currentTarget.value)])}
115
+ className={cn(
116
+ "pointer-events-none absolute inset-0 z-30 h-7 w-full appearance-none bg-transparent",
117
+ "[&::-moz-range-thumb]:pointer-events-auto [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-foreground [&::-moz-range-thumb]:bg-background",
118
+ "[&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-foreground [&::-webkit-slider-thumb]:bg-background",
119
+ thumbClassName
120
+ )}
121
+ aria-label={maxLabel}
122
+ />
123
+ </div>
124
+ )
125
+ }
126
+ )
127
+ RangeSlider.displayName = "RangeSlider"
128
+
129
+ export { RangeSlider }
@@ -0,0 +1,76 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { IconSearch as Search, IconX as X } from "@tabler/icons-react";
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
8
+
9
+ export interface SearchInputProps
10
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "onChange"> {
11
+ value?: string
12
+ onValueChange?: (value: string) => void
13
+ /** Show a clear (×) button when value is non-empty. Default true. */
14
+ clearable?: boolean
15
+ clearLabel?: string
16
+ }
17
+
18
+ const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
19
+ (
20
+ {
21
+ className,
22
+ value,
23
+ onValueChange,
24
+ clearable = true,
25
+ clearLabel = "Clear search",
26
+ disabled,
27
+ placeholder = "Search...",
28
+ ...props
29
+ },
30
+ ref
31
+ ) => {
32
+ const showClear = clearable && !!value && !disabled
33
+
34
+ return (
35
+ <div
36
+ className={cn(
37
+ "relative inline-flex items-center w-full",
38
+ disabled && "opacity-50 pointer-events-none",
39
+ className
40
+ )}
41
+ data-slot="search-input"
42
+ >
43
+ <Search className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
44
+ <input
45
+ ref={ref}
46
+ type="search"
47
+ value={value ?? ""}
48
+ onChange={(e) => onValueChange?.(e.target.value)}
49
+ disabled={disabled}
50
+ placeholder={placeholder}
51
+ className="flex h-9 w-full rounded-md border border-input bg-transparent pl-9 pr-9 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring [&::-webkit-search-cancel-button]:hidden"
52
+ {...props}
53
+ />
54
+ {showClear ? (
55
+ <Tooltip>
56
+ <TooltipTrigger asChild>
57
+ <button
58
+ type="button"
59
+ tabIndex={-1}
60
+ onClick={() => onValueChange?.("")}
61
+ className="absolute right-2 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded text-muted-foreground hover:text-foreground"
62
+ aria-label={clearLabel}
63
+ >
64
+ <X className="h-3.5 w-3.5" />
65
+ </button>
66
+ </TooltipTrigger>
67
+ <TooltipContent>{clearLabel}</TooltipContent>
68
+ </Tooltip>
69
+ ) : null}
70
+ </div>
71
+ )
72
+ }
73
+ )
74
+ SearchInput.displayName = "SearchInput"
75
+
76
+ export { SearchInput }
@@ -0,0 +1,39 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../lib/utils"
3
+ import { IconChevronDown as ChevronDown } from "@tabler/icons-react";
4
+
5
+ export interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> { }
6
+
7
+ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
8
+ ({ className, children, ...props }, ref) => {
9
+ const isFullWidth =
10
+ typeof className === "string" &&
11
+ className.split(/\s+/).includes("w-full")
12
+
13
+ return (
14
+ <div
15
+ className={cn(
16
+ "relative inline-block max-w-full align-middle",
17
+ isFullWidth && "block w-full"
18
+ )}
19
+ data-slot="select"
20
+ >
21
+ <select
22
+ className={cn(
23
+ "inline-flex h-9 w-[200px] max-w-full appearance-none items-center justify-between rounded-lg border border-input bg-transparent px-3 py-2 pr-9 text-sm font-normal text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground disabled:opacity-50",
24
+ className
25
+ )}
26
+ ref={ref}
27
+ {...props}
28
+ data-slot="select-control"
29
+ >
30
+ {children}
31
+ </select>
32
+ <ChevronDown className="pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
33
+ </div>
34
+ )
35
+ }
36
+ )
37
+ Select.displayName = "Select"
38
+
39
+ export { Select }
@@ -3,18 +3,31 @@
3
3
  import * as React from "react"
4
4
  import { cn } from "../../lib/utils"
5
5
 
6
- export interface SliderProps extends React.InputHTMLAttributes<HTMLInputElement> { }
6
+ export interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "defaultValue"> {
7
+ value?: number | string
8
+ defaultValue?: number | string
9
+ onValueChange?: (value: number) => void
10
+ }
7
11
 
8
12
  const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
9
- ({ className, ...props }, ref) => (
10
- <div className="relative flex w-[200px] touch-none select-none items-center">
13
+ ({ className, onChange, onValueChange, ...props }, ref) => (
14
+ <div
15
+ className={cn(
16
+ "relative flex w-[200px] touch-none select-none items-center",
17
+ className
18
+ )}
19
+ data-slot="slider"
20
+ >
11
21
  <input
12
22
  type="range"
13
23
  ref={ref}
24
+ onChange={(event) => {
25
+ onChange?.(event)
26
+ onValueChange?.(Number(event.currentTarget.value))
27
+ }}
14
28
  className={cn(
15
29
  "h-5 w-full cursor-pointer appearance-none rounded-[10px] bg-input disabled:cursor-not-allowed disabled:opacity-50",
16
- "[&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-foreground [&::-webkit-slider-thumb]:bg-background [&::-webkit-slider-thumb]:transition-colors [&::-webkit-slider-thumb]:focus-visible:outline-none [&::-webkit-slider-thumb]:focus-visible:ring-1 [&::-webkit-slider-thumb]:focus-visible:ring-ring [&::-webkit-slider-thumb]:focus-visible:ring-offset-1 [&::-webkit-slider-thumb]:focus-visible:ring-offset-background [&::-webkit-slider-thumb]:disabled:pointer-events-none [&::-webkit-slider-thumb]:disabled:opacity-50",
17
- className
30
+ "[&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-foreground [&::-webkit-slider-thumb]:bg-background [&::-webkit-slider-thumb]:transition-colors [&::-webkit-slider-thumb]:focus-visible:outline-none [&::-webkit-slider-thumb]:focus-visible:ring-1 [&::-webkit-slider-thumb]:focus-visible:ring-ring [&::-webkit-slider-thumb]:focus-visible:ring-offset-1 [&::-webkit-slider-thumb]:focus-visible:ring-offset-background [&::-webkit-slider-thumb]:disabled:pointer-events-none [&::-webkit-slider-thumb]:disabled:opacity-50"
18
31
  )}
19
32
  {...props}
20
33
  />