@skybin-tech/nebula-ui 0.0.28 → 0.0.30

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 (273) hide show
  1. package/README.md +422 -356
  2. package/dist/cjs/_virtual/_rolldown/runtime.cjs +1 -1
  3. package/dist/cjs/components/Accordion/Accordion.cjs.map +1 -1
  4. package/dist/cjs/components/Alert/Alert.cjs.map +1 -1
  5. package/dist/cjs/components/Avatar/Avatar.cjs.map +1 -1
  6. package/dist/cjs/components/Badge/Badge.cjs.map +1 -1
  7. package/dist/cjs/components/Button/Button.cjs +1 -1
  8. package/dist/cjs/components/Button/Button.cjs.map +1 -1
  9. package/dist/cjs/components/Card/Card.cjs.map +1 -1
  10. package/dist/cjs/components/Checkbox/Checkbox.cjs.map +1 -1
  11. package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  12. package/dist/cjs/components/Form/Checkbox.cjs.map +1 -1
  13. package/dist/cjs/components/Form/Combobox.cjs +124 -0
  14. package/dist/cjs/components/Form/Combobox.cjs.map +1 -0
  15. package/dist/cjs/components/Form/CountryPicker.cjs +101 -0
  16. package/dist/cjs/components/Form/CountryPicker.cjs.map +1 -0
  17. package/dist/cjs/components/Form/CountrySelect.cjs +111 -0
  18. package/dist/cjs/components/Form/CountrySelect.cjs.map +1 -0
  19. package/dist/cjs/components/Form/DatePicker.cjs +122 -0
  20. package/dist/cjs/components/Form/DatePicker.cjs.map +1 -0
  21. package/dist/cjs/components/Form/FieldLayout.cjs +65 -0
  22. package/dist/cjs/components/Form/FieldLayout.cjs.map +1 -0
  23. package/dist/cjs/components/Form/Form.cjs +12 -9
  24. package/dist/cjs/components/Form/Form.cjs.map +1 -1
  25. package/dist/cjs/components/Form/FormSwitch.cjs.map +1 -1
  26. package/dist/cjs/components/Form/PhoneInput.cjs +170 -0
  27. package/dist/cjs/components/Form/PhoneInput.cjs.map +1 -0
  28. package/dist/cjs/components/Form/Radio.cjs.map +1 -1
  29. package/dist/cjs/components/Form/Select.cjs +35 -47
  30. package/dist/cjs/components/Form/Select.cjs.map +1 -1
  31. package/dist/cjs/components/Form/TextArea.cjs +1 -1
  32. package/dist/cjs/components/Form/TextArea.cjs.map +1 -1
  33. package/dist/cjs/components/Form/TextBox.cjs +50 -61
  34. package/dist/cjs/components/Form/TextBox.cjs.map +1 -1
  35. package/dist/cjs/components/Form/context.cjs +25 -11
  36. package/dist/cjs/components/Form/context.cjs.map +1 -1
  37. package/dist/cjs/components/Form/hooks.cjs.map +1 -1
  38. package/dist/cjs/components/Form/index.cjs +16 -0
  39. package/dist/cjs/components/Form/variants.cjs.map +1 -1
  40. package/dist/cjs/components/Input/Input.cjs +2 -2
  41. package/dist/cjs/components/Input/Input.cjs.map +1 -1
  42. package/dist/cjs/components/Label/Label.cjs.map +1 -1
  43. package/dist/cjs/components/Pagination/Pagination.cjs +4 -4
  44. package/dist/cjs/components/Pagination/Pagination.cjs.map +1 -1
  45. package/dist/cjs/components/Separator/Separator.cjs.map +1 -1
  46. package/dist/cjs/components/Switch/Switch.cjs.map +1 -1
  47. package/dist/cjs/components/Tabs/Tabs.cjs.map +1 -1
  48. package/dist/cjs/components/Textarea/Textarea.cjs.map +1 -1
  49. package/dist/cjs/components/Tooltip/Tooltip.cjs.map +1 -1
  50. package/dist/cjs/data/countries.cjs +1062 -0
  51. package/dist/cjs/data/countries.cjs.map +1 -0
  52. package/dist/cjs/hooks/useDebounce.cjs.map +1 -1
  53. package/dist/cjs/hooks/useToast.cjs.map +1 -1
  54. package/dist/cjs/hooks/useToggle.cjs.map +1 -1
  55. package/dist/cjs/index.cjs +64 -4
  56. package/dist/cjs/primitives/accordion.cjs.map +1 -1
  57. package/dist/cjs/primitives/alert-dialog.cjs +73 -0
  58. package/dist/cjs/primitives/alert-dialog.cjs.map +1 -0
  59. package/dist/cjs/primitives/alert.cjs.map +1 -1
  60. package/dist/cjs/primitives/avatar.cjs.map +1 -1
  61. package/dist/cjs/primitives/badge.cjs.map +1 -1
  62. package/dist/cjs/primitives/breadcrumb.cjs +73 -0
  63. package/dist/cjs/primitives/breadcrumb.cjs.map +1 -0
  64. package/dist/cjs/primitives/button.cjs +8 -7
  65. package/dist/cjs/primitives/button.cjs.map +1 -1
  66. package/dist/cjs/primitives/calendar.cjs.map +1 -1
  67. package/dist/cjs/primitives/card.cjs.map +1 -1
  68. package/dist/cjs/primitives/checkbox.cjs.map +1 -1
  69. package/dist/cjs/primitives/command.cjs.map +1 -1
  70. package/dist/cjs/primitives/dialog.cjs.map +1 -1
  71. package/dist/cjs/primitives/dropdown-menu.cjs.map +1 -1
  72. package/dist/cjs/primitives/input-otp.cjs +49 -0
  73. package/dist/cjs/primitives/input-otp.cjs.map +1 -0
  74. package/dist/cjs/primitives/label.cjs.map +1 -1
  75. package/dist/cjs/primitives/popover.cjs.map +1 -1
  76. package/dist/cjs/primitives/progress.cjs +24 -0
  77. package/dist/cjs/primitives/progress.cjs.map +1 -0
  78. package/dist/cjs/primitives/radio-group.cjs.map +1 -1
  79. package/dist/cjs/primitives/scroll-area.cjs.map +1 -1
  80. package/dist/cjs/primitives/select.cjs.map +1 -1
  81. package/dist/cjs/primitives/separator.cjs.map +1 -1
  82. package/dist/cjs/primitives/sheet.cjs +78 -0
  83. package/dist/cjs/primitives/sheet.cjs.map +1 -0
  84. package/dist/cjs/primitives/slider.cjs +23 -0
  85. package/dist/cjs/primitives/slider.cjs.map +1 -0
  86. package/dist/cjs/primitives/sonner.cjs.map +1 -1
  87. package/dist/cjs/primitives/switch.cjs.map +1 -1
  88. package/dist/cjs/primitives/table.cjs.map +1 -1
  89. package/dist/cjs/primitives/tabs.cjs.map +1 -1
  90. package/dist/cjs/primitives/textarea.cjs +1 -1
  91. package/dist/cjs/primitives/textarea.cjs.map +1 -1
  92. package/dist/cjs/primitives/textbox.cjs +1 -1
  93. package/dist/cjs/primitives/textbox.cjs.map +1 -1
  94. package/dist/cjs/primitives/toast.cjs.map +1 -1
  95. package/dist/cjs/primitives/toggle.cjs.map +1 -1
  96. package/dist/cjs/utils/cn.cjs.map +1 -1
  97. package/dist/components/Alert/Alert.js.map +1 -1
  98. package/dist/components/AlertDialog/AlertDialog.d.ts +2 -0
  99. package/dist/components/AlertDialog/AlertDialog.d.ts.map +1 -0
  100. package/dist/components/AlertDialog/index.d.ts +2 -0
  101. package/dist/components/AlertDialog/index.d.ts.map +1 -0
  102. package/dist/components/Avatar/Avatar.js.map +1 -1
  103. package/dist/components/Badge/Badge.js.map +1 -1
  104. package/dist/components/Breadcrumb/Breadcrumb.d.ts +2 -0
  105. package/dist/components/Breadcrumb/Breadcrumb.d.ts.map +1 -0
  106. package/dist/components/Breadcrumb/index.d.ts +2 -0
  107. package/dist/components/Breadcrumb/index.d.ts.map +1 -0
  108. package/dist/components/Button/Button.d.ts.map +1 -1
  109. package/dist/components/Button/Button.js +2 -2
  110. package/dist/components/Button/Button.js.map +1 -1
  111. package/dist/components/Card/Card.js.map +1 -1
  112. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  113. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  114. package/dist/components/Form/Checkbox.js.map +1 -1
  115. package/dist/components/Form/Combobox.d.ts +31 -0
  116. package/dist/components/Form/Combobox.d.ts.map +1 -0
  117. package/dist/components/Form/Combobox.js +124 -0
  118. package/dist/components/Form/Combobox.js.map +1 -0
  119. package/dist/components/Form/CountryPicker.d.ts +17 -0
  120. package/dist/components/Form/CountryPicker.d.ts.map +1 -0
  121. package/dist/components/Form/CountryPicker.js +101 -0
  122. package/dist/components/Form/CountryPicker.js.map +1 -0
  123. package/dist/components/Form/CountrySelect.d.ts +32 -0
  124. package/dist/components/Form/CountrySelect.d.ts.map +1 -0
  125. package/dist/components/Form/CountrySelect.js +111 -0
  126. package/dist/components/Form/CountrySelect.js.map +1 -0
  127. package/dist/components/Form/DatePicker.d.ts +27 -0
  128. package/dist/components/Form/DatePicker.d.ts.map +1 -0
  129. package/dist/components/Form/DatePicker.js +122 -0
  130. package/dist/components/Form/DatePicker.js.map +1 -0
  131. package/dist/components/Form/FieldLayout.d.ts +15 -0
  132. package/dist/components/Form/FieldLayout.d.ts.map +1 -0
  133. package/dist/components/Form/FieldLayout.js +65 -0
  134. package/dist/components/Form/FieldLayout.js.map +1 -0
  135. package/dist/components/Form/Form.d.ts +1 -1
  136. package/dist/components/Form/Form.d.ts.map +1 -1
  137. package/dist/components/Form/Form.js +12 -9
  138. package/dist/components/Form/Form.js.map +1 -1
  139. package/dist/components/Form/FormSwitch.js.map +1 -1
  140. package/dist/components/Form/PhoneInput.d.ts +38 -0
  141. package/dist/components/Form/PhoneInput.d.ts.map +1 -0
  142. package/dist/components/Form/PhoneInput.js +170 -0
  143. package/dist/components/Form/PhoneInput.js.map +1 -0
  144. package/dist/components/Form/Radio.js.map +1 -1
  145. package/dist/components/Form/Select.d.ts.map +1 -1
  146. package/dist/components/Form/Select.js +35 -47
  147. package/dist/components/Form/Select.js.map +1 -1
  148. package/dist/components/Form/TextArea.js +1 -1
  149. package/dist/components/Form/TextArea.js.map +1 -1
  150. package/dist/components/Form/TextBox.d.ts.map +1 -1
  151. package/dist/components/Form/TextBox.js +50 -61
  152. package/dist/components/Form/TextBox.js.map +1 -1
  153. package/dist/components/Form/context.d.ts.map +1 -1
  154. package/dist/components/Form/context.js +25 -11
  155. package/dist/components/Form/context.js.map +1 -1
  156. package/dist/components/Form/context.test.d.ts +2 -0
  157. package/dist/components/Form/context.test.d.ts.map +1 -0
  158. package/dist/components/Form/hooks.js.map +1 -1
  159. package/dist/components/Form/index.d.ts +12 -0
  160. package/dist/components/Form/index.d.ts.map +1 -1
  161. package/dist/components/Form/index.js +7 -1
  162. package/dist/components/Input/Input.js +2 -2
  163. package/dist/components/Input/Input.js.map +1 -1
  164. package/dist/components/InputOTP/InputOTP.d.ts +2 -0
  165. package/dist/components/InputOTP/InputOTP.d.ts.map +1 -0
  166. package/dist/components/InputOTP/index.d.ts +2 -0
  167. package/dist/components/InputOTP/index.d.ts.map +1 -0
  168. package/dist/components/Label/Label.js.map +1 -1
  169. package/dist/components/Pagination/Pagination.d.ts +2 -2
  170. package/dist/components/Pagination/Pagination.d.ts.map +1 -1
  171. package/dist/components/Pagination/Pagination.js +4 -4
  172. package/dist/components/Pagination/Pagination.js.map +1 -1
  173. package/dist/components/Progress/Progress.d.ts +2 -0
  174. package/dist/components/Progress/Progress.d.ts.map +1 -0
  175. package/dist/components/Progress/index.d.ts +2 -0
  176. package/dist/components/Progress/index.d.ts.map +1 -0
  177. package/dist/components/Separator/Separator.js.map +1 -1
  178. package/dist/components/Sheet/Sheet.d.ts +2 -0
  179. package/dist/components/Sheet/Sheet.d.ts.map +1 -0
  180. package/dist/components/Sheet/index.d.ts +2 -0
  181. package/dist/components/Sheet/index.d.ts.map +1 -0
  182. package/dist/components/Slider/Slider.d.ts +2 -0
  183. package/dist/components/Slider/Slider.d.ts.map +1 -0
  184. package/dist/components/Slider/index.d.ts +2 -0
  185. package/dist/components/Slider/index.d.ts.map +1 -0
  186. package/dist/components/Switch/Switch.js.map +1 -1
  187. package/dist/components/Tabs/Tabs.js.map +1 -1
  188. package/dist/components/Textarea/Textarea.js.map +1 -1
  189. package/dist/data/countries.d.ts +18 -0
  190. package/dist/data/countries.d.ts.map +1 -0
  191. package/dist/data/countries.js +1054 -0
  192. package/dist/data/countries.js.map +1 -0
  193. package/dist/hooks/useDebounce.js.map +1 -1
  194. package/dist/hooks/useToast.js.map +1 -1
  195. package/dist/hooks/useToggle.js.map +1 -1
  196. package/dist/index.d.ts +10 -2
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +16 -5
  199. package/dist/primitives/accordion.js.map +1 -1
  200. package/dist/primitives/alert-dialog.d.ts +21 -0
  201. package/dist/primitives/alert-dialog.d.ts.map +1 -0
  202. package/dist/primitives/alert-dialog.js +60 -0
  203. package/dist/primitives/alert-dialog.js.map +1 -0
  204. package/dist/primitives/alert.js.map +1 -1
  205. package/dist/primitives/avatar.js.map +1 -1
  206. package/dist/primitives/breadcrumb.d.ts +20 -0
  207. package/dist/primitives/breadcrumb.d.ts.map +1 -0
  208. package/dist/primitives/breadcrumb.js +65 -0
  209. package/dist/primitives/breadcrumb.js.map +1 -0
  210. package/dist/primitives/button.d.ts +4 -3
  211. package/dist/primitives/button.d.ts.map +1 -1
  212. package/dist/primitives/button.js +7 -7
  213. package/dist/primitives/button.js.map +1 -1
  214. package/dist/primitives/calendar.js.map +1 -1
  215. package/dist/primitives/card.js.map +1 -1
  216. package/dist/primitives/checkbox.js.map +1 -1
  217. package/dist/primitives/command.js.map +1 -1
  218. package/dist/primitives/dialog.js.map +1 -1
  219. package/dist/primitives/dropdown-menu.js.map +1 -1
  220. package/dist/primitives/index.d.ts +6 -0
  221. package/dist/primitives/index.d.ts.map +1 -1
  222. package/dist/primitives/input-otp.d.ts +35 -0
  223. package/dist/primitives/input-otp.d.ts.map +1 -0
  224. package/dist/primitives/input-otp.js +44 -0
  225. package/dist/primitives/input-otp.js.map +1 -0
  226. package/dist/primitives/input.d.ts +1 -3
  227. package/dist/primitives/input.d.ts.map +1 -1
  228. package/dist/primitives/label.js.map +1 -1
  229. package/dist/primitives/popover.js.map +1 -1
  230. package/dist/primitives/progress.d.ts +5 -0
  231. package/dist/primitives/progress.d.ts.map +1 -0
  232. package/dist/primitives/progress.js +21 -0
  233. package/dist/primitives/progress.js.map +1 -0
  234. package/dist/primitives/radio-group.js.map +1 -1
  235. package/dist/primitives/scroll-area.js.map +1 -1
  236. package/dist/primitives/select.js.map +1 -1
  237. package/dist/primitives/separator.js.map +1 -1
  238. package/dist/primitives/sheet.d.ts +26 -0
  239. package/dist/primitives/sheet.d.ts.map +1 -0
  240. package/dist/primitives/sheet.js +66 -0
  241. package/dist/primitives/sheet.js.map +1 -0
  242. package/dist/primitives/slider.d.ts +5 -0
  243. package/dist/primitives/slider.d.ts.map +1 -0
  244. package/dist/primitives/slider.js +20 -0
  245. package/dist/primitives/slider.js.map +1 -0
  246. package/dist/primitives/sonner.js.map +1 -1
  247. package/dist/primitives/switch.js.map +1 -1
  248. package/dist/primitives/table.js.map +1 -1
  249. package/dist/primitives/tabs.js.map +1 -1
  250. package/dist/primitives/textarea.js +1 -1
  251. package/dist/primitives/textarea.js.map +1 -1
  252. package/dist/primitives/textbox.js +1 -1
  253. package/dist/primitives/textbox.js.map +1 -1
  254. package/dist/primitives/toast.js.map +1 -1
  255. package/dist/primitives/toggle.js.map +1 -1
  256. package/package.json +168 -60
  257. package/src/styles/index.css +107 -0
  258. package/dist/cjs/primitives/input.cjs +0 -20
  259. package/dist/cjs/primitives/input.cjs.map +0 -1
  260. package/dist/primitives/input.js +0 -18
  261. package/dist/primitives/input.js.map +0 -1
  262. package/dist/stories/Button.d.ts +0 -15
  263. package/dist/stories/Button.d.ts.map +0 -1
  264. package/dist/stories/Button.stories.d.ts +0 -24
  265. package/dist/stories/Button.stories.d.ts.map +0 -1
  266. package/dist/stories/Header.d.ts +0 -12
  267. package/dist/stories/Header.d.ts.map +0 -1
  268. package/dist/stories/Header.stories.d.ts +0 -19
  269. package/dist/stories/Header.stories.d.ts.map +0 -1
  270. package/dist/stories/Page.d.ts +0 -3
  271. package/dist/stories/Page.d.ts.map +0 -1
  272. package/dist/stories/Page.stories.d.ts +0 -13
  273. package/dist/stories/Page.stories.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,356 +1,422 @@
1
- # nebula-ui
2
-
3
- A React component library built on [shadcn/ui](https://ui.shadcn.com/) patterns, providing form-integrated components with automatic Zod validation, accessible primitives, and utility hooks.
4
-
5
- See [CHANGELOG.md](CHANGELOG.md) for release history.
6
-
7
- ## Installation
8
-
9
- ```bash
10
- npm install @skybin-tech/nebula-ui
11
- ```
12
-
13
- ### Peer dependencies
14
-
15
- ```bash
16
- npm install react react-dom react-hook-form @hookform/resolvers zod \
17
- @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-dropdown-menu \
18
- @radix-ui/react-label @radix-ui/react-radio-group @radix-ui/react-select \
19
- @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-switch \
20
- class-variance-authority clsx tailwind-merge lucide-react tailwindcss
21
- ```
22
-
23
- ### Styles
24
-
25
- Import the stylesheet once in your app entry point:
26
-
27
- ```ts
28
- import "@skybin-tech/nebula-ui/styles.css";
29
- ```
30
-
31
- ---
32
-
33
- ## Form system
34
-
35
- The form system is built around `react-hook-form` and `zod`. Wrap fields in `<Form>` and each field automatically registers its validation rules — no need to define a schema separately.
36
-
37
- ### Form
38
-
39
- The root form component. Provides context to all child fields and handles validation and submission.
40
-
41
- ```tsx
42
- import { Form, TextBox, Button } from "@skybin-tech/nebula-ui";
43
-
44
- <Form
45
- onSubmit={(data) => console.log(data)}
46
- defaultValues={{ username: "", email: "" }}
47
- >
48
- <TextBox name="username" label="Username" required minLength={3} maxLength={50} />
49
- <TextBox name="email" label="Email" type="email" required email />
50
- <Button type="submit">Submit</Button>
51
- </Form>
52
- ```
53
-
54
- You can also provide a base Zod schema that gets merged with field-level rules:
55
-
56
- ```tsx
57
- const baseSchema = z.object({ username: z.string(), email: z.string() });
58
-
59
- <Form schema={baseSchema} onSubmit={handleSubmit}>
60
- <TextBox name="username" required minLength={3} />
61
- <TextBox name="email" required email />
62
- </Form>
63
- ```
64
-
65
- **Props**
66
-
67
- | Prop | Type | Default | Description |
68
- |------|------|---------|-------------|
69
- | `onSubmit` | `SubmitHandler` | | Form submit handler |
70
- | `onError` | `SubmitErrorHandler` | — | Validation error handler |
71
- | `defaultValues` | `object` | — | Initial field values |
72
- | `values` | `object` | | Controlled field values |
73
- | `schema` | `ZodType` | — | Base Zod schema (merged with field rules) |
74
- | `config` | `FormConfig` | — | Form-wide configuration (size, layout, etc.) |
75
- | `form` | `UseFormReturn` | — | External RHF form instance |
76
- | `mode` | `string` | `"onBlur"` | When to trigger validation |
77
- | `reValidateMode` | `string` | `"onChange"` | When to re-validate after first submission |
78
-
79
- **FormConfig options**
80
-
81
- | Key | Type | Default | Description |
82
- |-----|------|---------|-------------|
83
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Default size for all fields |
84
- | `layout` | `"vertical" \| "horizontal" \| "inline"` | `"vertical"` | Layout direction |
85
- | `labelWidth` | `string \| number` | — | Label width for horizontal layout |
86
- | `disabled` | `boolean` | `false` | Disable all fields |
87
- | `colon` | `boolean` | `true` | Append `:` to labels |
88
- | `showInlineErrors` | `boolean` | `true` | Show validation errors inline |
89
- | `validateOnBlur` | `boolean` | `true` | Validate on blur |
90
- | `validateOnChange` | `boolean` | `false` | Validate on change |
91
-
92
- ---
93
-
94
- ### TextBox
95
-
96
- Single-line text input with full form integration.
97
-
98
- ```tsx
99
- <TextBox name="username" label="Username" required minLength={3} maxLength={50} />
100
- <TextBox name="email" label="Email" type="email" required email />
101
- <TextBox name="website" label="Website" url />
102
- <TextBox name="amount" label="Amount" type="number" minValue={0} maxValue={100} />
103
- <TextBox name="search" label="Search" prefix={<SearchIcon />} allowClear />
104
- ```
105
-
106
- **Validation props**
107
-
108
- | Prop | Type | Description |
109
- |------|------|-------------|
110
- | `required` | `boolean \| string` | Mark as required; string sets the error message |
111
- | `minLength` | `number \| { value, message }` | Minimum character count |
112
- | `maxLength` | `number \| { value, message }` | Maximum character count |
113
- | `minValue` | `number \| { value, message }` | Minimum numeric value |
114
- | `maxValue` | `number \| { value, message }` | Maximum numeric value |
115
- | `pattern` | `RegExp \| { value, message }` | Regex pattern |
116
- | `email` | `boolean \| string` | Email format validation |
117
- | `url` | `boolean \| string` | URL format validation |
118
- | `validate` | `(value) => boolean \| string` | Custom validation function |
119
-
120
- ---
121
-
122
- ### TextArea
123
-
124
- Multi-line text input with optional character count display.
125
-
126
- ```tsx
127
- <TextArea name="description" label="Description" required minLength={10} />
128
- <TextArea name="bio" label="Bio" showCount maxCount={500} maxLength={500} />
129
- ```
130
-
131
- Supports the same validation props as `TextBox` (except `minValue`, `maxValue`, `email`, `url`, `pattern`).
132
-
133
- ---
134
-
135
- ### Select
136
-
137
- Dropdown select with support for an `options` array or custom `children`. Import as `FormSelect` from the main package (or `Select` from `@skybin-tech/nebula-ui/components/Form`).
138
-
139
- ```tsx
140
- import { Form, FormSelect } from "@skybin-tech/nebula-ui";
141
-
142
- <FormSelect
143
- name="country"
144
- label="Country"
145
- required
146
- options={[
147
- { label: "USA", value: "us" },
148
- { label: "Canada", value: "ca" },
149
- ]}
150
- />
151
-
152
- // Or with custom children
153
- <FormSelect name="role" label="Role" required>
154
- <SelectItem value="admin">Admin</SelectItem>
155
- <SelectItem value="user">User</SelectItem>
156
- </FormSelect>
157
- ```
158
-
159
- ---
160
-
161
- ### Checkbox
162
-
163
- Boolean checkbox field. When `required` is set, the checkbox must be checked to pass validation.
164
-
165
- ```tsx
166
- <Checkbox name="terms" label="I agree to the terms and conditions" required />
167
- <Checkbox name="newsletter" label="Subscribe to newsletter" />
168
- ```
169
-
170
- ---
171
-
172
- ### RadioGroup
173
-
174
- A group of mutually exclusive radio buttons. Accepts an `options` array or custom `RadioItem` children.
175
-
176
- ```tsx
177
- <RadioGroup
178
- name="gender"
179
- label="Gender"
180
- required
181
- options={[
182
- { label: "Male", value: "male" },
183
- { label: "Female", value: "female" },
184
- { label: "Other", value: "other" },
185
- ]}
186
- />
187
-
188
- // Horizontal layout
189
- <RadioGroup name="size" label="Size" direction="horizontal" options={[...]} />
190
- ```
191
-
192
- ---
193
-
194
- ### Switch
195
-
196
- Toggle switch backed by a boolean form field.
197
-
198
- ```tsx
199
- <Switch name="notifications" label="Enable notifications" />
200
- <Switch name="terms" label="Accept terms" required="You must accept the terms" />
201
- <Switch name="darkMode" label="Dark mode" checkedText="On" uncheckedText="Off" />
202
- ```
203
-
204
- ---
205
-
206
- ## Display components
207
-
208
- ### Button
209
-
210
- A styled button with multiple variants, sizes, and a built-in loading state.
211
-
212
- ```tsx
213
- import { Button } from "@skybin-tech/nebula-ui";
214
-
215
- <Button variant="primary" size="md">Save</Button>
216
- <Button variant="outline" loading>Saving…</Button>
217
- <Button variant="danger" fullWidth>Delete</Button>
218
- ```
219
-
220
- | Variant | Description |
221
- |---------|-------------|
222
- | `primary` | Filled primary colour (default) |
223
- | `secondary` | Filled secondary colour |
224
- | `outline` | Transparent with primary border |
225
- | `ghost` | Transparent, no border |
226
- | `danger` | Destructive / red |
227
-
228
- Sizes: `sm`, `md` (default), `lg`.
229
-
230
- ---
231
-
232
- ### Card
233
-
234
- Composable card layout components.
235
-
236
- ```tsx
237
- import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@skybin-tech/nebula-ui";
238
-
239
- <Card>
240
- <CardHeader>
241
- <CardTitle>Title</CardTitle>
242
- <CardDescription>Subtitle</CardDescription>
243
- </CardHeader>
244
- <CardContent>Content goes here</CardContent>
245
- <CardFooter>Footer</CardFooter>
246
- </Card>
247
- ```
248
-
249
- ---
250
-
251
- ### Badge
252
-
253
- Small status or label indicator.
254
-
255
- ```tsx
256
- import { Badge } from "@skybin-tech/nebula-ui";
257
-
258
- <Badge variant="default">New</Badge>
259
- <Badge variant="secondary">Beta</Badge>
260
- <Badge variant="destructive">Error</Badge>
261
- <Badge variant="outline">Draft</Badge>
262
- ```
263
-
264
- ---
265
-
266
- ### Avatar
267
-
268
- User avatar with image and fallback initials.
269
-
270
- ```tsx
271
- import { Avatar, AvatarImage, AvatarFallback } from "@skybin-tech/nebula-ui";
272
-
273
- <Avatar>
274
- <AvatarImage src="/avatar.jpg" alt="John Doe" />
275
- <AvatarFallback>JD</AvatarFallback>
276
- </Avatar>
277
- ```
278
-
279
- ---
280
-
281
- ### DropdownMenu
282
-
283
- Accessible dropdown built on Radix UI.
284
-
285
- ```tsx
286
- import {
287
- DropdownMenu,
288
- DropdownMenuTrigger,
289
- DropdownMenuContent,
290
- DropdownMenuItem,
291
- DropdownMenuSeparator,
292
- } from "@skybin-tech/nebula-ui";
293
-
294
- <DropdownMenu>
295
- <DropdownMenuTrigger asChild>
296
- <Button variant="outline">Options</Button>
297
- </DropdownMenuTrigger>
298
- <DropdownMenuContent>
299
- <DropdownMenuItem>Edit</DropdownMenuItem>
300
- <DropdownMenuItem>Duplicate</DropdownMenuItem>
301
- <DropdownMenuSeparator />
302
- <DropdownMenuItem>Delete</DropdownMenuItem>
303
- </DropdownMenuContent>
304
- </DropdownMenu>
305
- ```
306
-
307
- ---
308
-
309
- ### Separator
310
-
311
- A horizontal or vertical divider line.
312
-
313
- ```tsx
314
- import { Separator } from "@skybin-tech/nebula-ui";
315
-
316
- <Separator />
317
- <Separator orientation="vertical" />
318
- ```
319
-
320
- ---
321
-
322
- ## Hooks
323
-
324
- ### useDebounce
325
-
326
- Debounces a value, returning the latest value only after the specified delay has elapsed with no further changes.
327
-
328
- ```tsx
329
- import { useDebounce } from "@skybin-tech/nebula-ui";
330
-
331
- const debouncedSearch = useDebounce(searchTerm, 300);
332
- ```
333
-
334
- ### useToggle
335
-
336
- Returns a boolean state and a stable toggle function.
337
-
338
- ```tsx
339
- import { useToggle } from "@skybin-tech/nebula-ui";
340
-
341
- const [isOpen, toggleOpen] = useToggle(false);
342
- ```
343
-
344
- ---
345
-
346
- ## Utilities
347
-
348
- ### cn
349
-
350
- Merges Tailwind CSS class names, handling conflicts via `tailwind-merge`.
351
-
352
- ```tsx
353
- import { cn } from "@skybin-tech/nebula-ui";
354
-
355
- <div className={cn("p-4 text-sm", isActive && "bg-primary text-white")} />
356
- ```
1
+ # nebula-ui
2
+
3
+ A React component library built on [shadcn/ui](https://ui.shadcn.com/) patterns, providing form-integrated components with automatic Zod validation, accessible primitives, and utility hooks.
4
+
5
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @skybin-tech/nebula-ui
11
+ ```
12
+
13
+ ### Peer dependencies
14
+
15
+ ```bash
16
+ npm install react react-dom react-hook-form @hookform/resolvers zod \
17
+ @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-dropdown-menu \
18
+ @radix-ui/react-label @radix-ui/react-radio-group @radix-ui/react-select \
19
+ @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-switch \
20
+ class-variance-authority clsx tailwind-merge lucide-react tailwindcss
21
+ ```
22
+
23
+ ### Styles
24
+
25
+ Import the Nebula UI theme tokens once in your app entry point:
26
+
27
+ ```ts
28
+ import "@skybin-tech/nebula-ui/styles.css";
29
+ ```
30
+
31
+ Your app must also use Tailwind CSS v3/v4 with the standard shadcn CSS variables (`--background`, `--primary`, `--border`, etc.). See [src/styles/index.css](src/styles/index.css) for the token set shipped with this package.
32
+
33
+ ### Toast notifications
34
+
35
+ Use **Sonner** for app toasts:
36
+
37
+ ```tsx
38
+ import { Toaster } from "@skybin-tech/nebula-ui";
39
+
40
+ // in root layout
41
+ <Toaster />
42
+ ```
43
+
44
+ Radix Toast primitives remain exported for advanced custom toast UIs, but Sonner is the recommended default.
45
+
46
+ ---
47
+
48
+ ## Form system
49
+
50
+ The form system is built around `react-hook-form` and `zod`. Wrap fields in `<Form>` and each field automatically registers its validation rules — no need to define a schema separately.
51
+
52
+ ### Form
53
+
54
+ The root form component. Provides context to all child fields and handles validation and submission.
55
+
56
+ ```tsx
57
+ import { Form, TextBox, Button } from "@skybin-tech/nebula-ui";
58
+
59
+ <Form
60
+ onSubmit={(data) => console.log(data)}
61
+ defaultValues={{ username: "", email: "" }}
62
+ >
63
+ <TextBox name="username" label="Username" required minLength={3} maxLength={50} />
64
+ <TextBox name="email" label="Email" type="email" required email />
65
+ <Button type="submit">Submit</Button>
66
+ </Form>
67
+ ```
68
+
69
+ You can also provide a base Zod schema that gets merged with field-level rules:
70
+
71
+ ```tsx
72
+ const baseSchema = z.object({ username: z.string(), email: z.string() });
73
+
74
+ <Form schema={baseSchema} onSubmit={handleSubmit}>
75
+ <TextBox name="username" required minLength={3} />
76
+ <TextBox name="email" required email />
77
+ </Form>
78
+ ```
79
+
80
+ **Props**
81
+
82
+ | Prop | Type | Default | Description |
83
+ |------|------|---------|-------------|
84
+ | `onSubmit` | `SubmitHandler` | | Form submit handler |
85
+ | `onError` | `SubmitErrorHandler` | — | Validation error handler |
86
+ | `defaultValues` | `object` | | Initial field values |
87
+ | `values` | `object` | | Controlled field values |
88
+ | `schema` | `ZodType` | | Base Zod schema (merged with field rules) |
89
+ | `config` | `FormConfig` | | Form-wide configuration (size, layout, etc.) |
90
+ | `form` | `UseFormReturn` | | External RHF form instance |
91
+ | `mode` | `string` | `"onBlur"` | When to trigger validation |
92
+ | `reValidateMode` | `string` | `"onChange"` | When to re-validate after first submission |
93
+
94
+ **FormConfig options**
95
+
96
+ | Key | Type | Default | Description |
97
+ |-----|------|---------|-------------|
98
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Default size for all fields |
99
+ | `layout` | `"vertical" \| "horizontal" \| "inline"` | `"vertical"` | Layout direction |
100
+ | `labelWidth` | `string \| number` | — | Label width for horizontal layout |
101
+ | `disabled` | `boolean` | `false` | Disable all fields |
102
+ | `colon` | `boolean` | `true` | Append `:` to labels |
103
+ | `showInlineErrors` | `boolean` | `true` | Show validation errors inline |
104
+ | `validateOnBlur` | `boolean` | `true` | Validate on blur |
105
+ | `validateOnChange` | `boolean` | `false` | Validate on change |
106
+
107
+ ---
108
+
109
+ ### TextBox
110
+
111
+ Single-line text input with full form integration.
112
+
113
+ ```tsx
114
+ <TextBox name="username" label="Username" required minLength={3} maxLength={50} />
115
+ <TextBox name="email" label="Email" type="email" required email />
116
+ <TextBox name="website" label="Website" url />
117
+ <TextBox name="amount" label="Amount" type="number" minValue={0} maxValue={100} />
118
+ <TextBox name="search" label="Search" prefix={<SearchIcon />} allowClear />
119
+ ```
120
+
121
+ **Validation props**
122
+
123
+ | Prop | Type | Description |
124
+ |------|------|-------------|
125
+ | `required` | `boolean \| string` | Mark as required; string sets the error message |
126
+ | `minLength` | `number \| { value, message }` | Minimum character count |
127
+ | `maxLength` | `number \| { value, message }` | Maximum character count |
128
+ | `minValue` | `number \| { value, message }` | Minimum numeric value |
129
+ | `maxValue` | `number \| { value, message }` | Maximum numeric value |
130
+ | `pattern` | `RegExp \| { value, message }` | Regex pattern |
131
+ | `email` | `boolean \| string` | Email format validation |
132
+ | `url` | `boolean \| string` | URL format validation |
133
+ | `validate` | `(value) => boolean \| string` | Custom validation function |
134
+
135
+ ---
136
+
137
+ ### TextArea
138
+
139
+ Multi-line text input with optional character count display.
140
+
141
+ ```tsx
142
+ <TextArea name="description" label="Description" required minLength={10} />
143
+ <TextArea name="bio" label="Bio" showCount maxCount={500} maxLength={500} />
144
+ ```
145
+
146
+ Supports the same validation props as `TextBox` (except `minValue`, `maxValue`, `email`, `url`, `pattern`).
147
+
148
+ ---
149
+
150
+ ### Select
151
+
152
+ Dropdown select with support for an `options` array or custom `children`. Import as `FormSelect` from the main package (or `Select` from `@skybin-tech/nebula-ui/components/Form`).
153
+
154
+ ```tsx
155
+ import { Form, FormSelect } from "@skybin-tech/nebula-ui";
156
+
157
+ <FormSelect
158
+ name="country"
159
+ label="Country"
160
+ required
161
+ options={[
162
+ { label: "USA", value: "us" },
163
+ { label: "Canada", value: "ca" },
164
+ ]}
165
+ />
166
+
167
+ // Or with custom children
168
+ <FormSelect name="role" label="Role" required>
169
+ <SelectItem value="admin">Admin</SelectItem>
170
+ <SelectItem value="user">User</SelectItem>
171
+ </FormSelect>
172
+ ```
173
+
174
+ ---
175
+
176
+ ### CountrySelect
177
+
178
+ Searchable country picker with flag emoji and country name. Stores ISO alpha-2 codes (`"IN"`, `"US"`).
179
+
180
+ ```tsx
181
+ <CountrySelect
182
+ name="country"
183
+ label="Country"
184
+ required
185
+ priorityCountries={["IN", "US", "GB"]}
186
+ />
187
+ ```
188
+
189
+ ---
190
+
191
+ ### PhoneInput
192
+
193
+ International mobile number input with country picker, read-only dial code, and national number field. Stores E.164 values (`"+919876543210"`).
194
+
195
+ ```tsx
196
+ <PhoneInput
197
+ name="phone"
198
+ label="Mobile number"
199
+ defaultCountry="IN"
200
+ required="Please enter your mobile number"
201
+ minLength={10}
202
+ />
203
+ ```
204
+
205
+ Supports the same `required` prop as `TextBox` — pass `true` or a custom error message string. Optional `minLength` / `maxLength` apply to the national number digits.
206
+
207
+ ---
208
+
209
+ ### DatePicker / Combobox
210
+
211
+ Form-integrated date picker and searchable combobox:
212
+
213
+ ```tsx
214
+ <DatePicker name="birthDate" label="Date of birth" required />
215
+ <Combobox
216
+ name="department"
217
+ label="Department"
218
+ options={[
219
+ { label: "Engineering", value: "eng" },
220
+ { label: "Sales", value: "sales" },
221
+ ]}
222
+ />
223
+ ```
224
+
225
+ ---
226
+
227
+ ### Checkbox
228
+
229
+ Boolean checkbox field. When `required` is set, the checkbox must be checked to pass validation.
230
+
231
+ ```tsx
232
+ <Checkbox name="terms" label="I agree to the terms and conditions" required />
233
+ <Checkbox name="newsletter" label="Subscribe to newsletter" />
234
+ ```
235
+
236
+ ---
237
+
238
+ ### RadioGroup
239
+
240
+ A group of mutually exclusive radio buttons. Accepts an `options` array or custom `RadioItem` children.
241
+
242
+ ```tsx
243
+ <RadioGroup
244
+ name="gender"
245
+ label="Gender"
246
+ required
247
+ options={[
248
+ { label: "Male", value: "male" },
249
+ { label: "Female", value: "female" },
250
+ { label: "Other", value: "other" },
251
+ ]}
252
+ />
253
+
254
+ // Horizontal layout
255
+ <RadioGroup name="size" label="Size" direction="horizontal" options={[...]} />
256
+ ```
257
+
258
+ ---
259
+
260
+ ### Switch
261
+
262
+ Toggle switch backed by a boolean form field.
263
+
264
+ ```tsx
265
+ <Switch name="notifications" label="Enable notifications" />
266
+ <Switch name="terms" label="Accept terms" required="You must accept the terms" />
267
+ <Switch name="darkMode" label="Dark mode" checkedText="On" uncheckedText="Off" />
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Display components
273
+
274
+ ### Button
275
+
276
+ A styled button with multiple variants, sizes, and a built-in loading state.
277
+
278
+ ```tsx
279
+ import { Button } from "@skybin-tech/nebula-ui";
280
+
281
+ <Button variant="primary" size="md">Save</Button>
282
+ <Button variant="outline" loading>Saving…</Button>
283
+ <Button variant="danger" fullWidth>Delete</Button>
284
+ ```
285
+
286
+ | Variant | Description |
287
+ |---------|-------------|
288
+ | `primary` | Filled primary colour (default) |
289
+ | `secondary` | Filled secondary colour |
290
+ | `outline` | Transparent with primary border |
291
+ | `ghost` | Transparent, no border |
292
+ | `danger` | Destructive / red |
293
+
294
+ Sizes: `sm`, `md` (default), `lg`.
295
+
296
+ ---
297
+
298
+ ### Card
299
+
300
+ Composable card layout components.
301
+
302
+ ```tsx
303
+ import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@skybin-tech/nebula-ui";
304
+
305
+ <Card>
306
+ <CardHeader>
307
+ <CardTitle>Title</CardTitle>
308
+ <CardDescription>Subtitle</CardDescription>
309
+ </CardHeader>
310
+ <CardContent>Content goes here</CardContent>
311
+ <CardFooter>Footer</CardFooter>
312
+ </Card>
313
+ ```
314
+
315
+ ---
316
+
317
+ ### Badge
318
+
319
+ Small status or label indicator.
320
+
321
+ ```tsx
322
+ import { Badge } from "@skybin-tech/nebula-ui";
323
+
324
+ <Badge variant="default">New</Badge>
325
+ <Badge variant="secondary">Beta</Badge>
326
+ <Badge variant="destructive">Error</Badge>
327
+ <Badge variant="outline">Draft</Badge>
328
+ ```
329
+
330
+ ---
331
+
332
+ ### Avatar
333
+
334
+ User avatar with image and fallback initials.
335
+
336
+ ```tsx
337
+ import { Avatar, AvatarImage, AvatarFallback } from "@skybin-tech/nebula-ui";
338
+
339
+ <Avatar>
340
+ <AvatarImage src="/avatar.jpg" alt="John Doe" />
341
+ <AvatarFallback>JD</AvatarFallback>
342
+ </Avatar>
343
+ ```
344
+
345
+ ---
346
+
347
+ ### DropdownMenu
348
+
349
+ Accessible dropdown built on Radix UI.
350
+
351
+ ```tsx
352
+ import {
353
+ DropdownMenu,
354
+ DropdownMenuTrigger,
355
+ DropdownMenuContent,
356
+ DropdownMenuItem,
357
+ DropdownMenuSeparator,
358
+ } from "@skybin-tech/nebula-ui";
359
+
360
+ <DropdownMenu>
361
+ <DropdownMenuTrigger asChild>
362
+ <Button variant="outline">Options</Button>
363
+ </DropdownMenuTrigger>
364
+ <DropdownMenuContent>
365
+ <DropdownMenuItem>Edit</DropdownMenuItem>
366
+ <DropdownMenuItem>Duplicate</DropdownMenuItem>
367
+ <DropdownMenuSeparator />
368
+ <DropdownMenuItem>Delete</DropdownMenuItem>
369
+ </DropdownMenuContent>
370
+ </DropdownMenu>
371
+ ```
372
+
373
+ ---
374
+
375
+ ### Separator
376
+
377
+ A horizontal or vertical divider line.
378
+
379
+ ```tsx
380
+ import { Separator } from "@skybin-tech/nebula-ui";
381
+
382
+ <Separator />
383
+ <Separator orientation="vertical" />
384
+ ```
385
+
386
+ ---
387
+
388
+ ## Hooks
389
+
390
+ ### useDebounce
391
+
392
+ Debounces a value, returning the latest value only after the specified delay has elapsed with no further changes.
393
+
394
+ ```tsx
395
+ import { useDebounce } from "@skybin-tech/nebula-ui";
396
+
397
+ const debouncedSearch = useDebounce(searchTerm, 300);
398
+ ```
399
+
400
+ ### useToggle
401
+
402
+ Returns a boolean state and a stable toggle function.
403
+
404
+ ```tsx
405
+ import { useToggle } from "@skybin-tech/nebula-ui";
406
+
407
+ const [isOpen, toggleOpen] = useToggle(false);
408
+ ```
409
+
410
+ ---
411
+
412
+ ## Utilities
413
+
414
+ ### cn
415
+
416
+ Merges Tailwind CSS class names, handling conflicts via `tailwind-merge`.
417
+
418
+ ```tsx
419
+ import { cn } from "@skybin-tech/nebula-ui";
420
+
421
+ <div className={cn("p-4 text-sm", isActive && "bg-primary text-white")} />
422
+ ```