@vygruppen/spor-react 11.3.9 → 12.0.0

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 (322) hide show
  1. package/.turbo/turbo-build.log +32 -11
  2. package/.turbo/turbo-typegen.log +23 -0
  3. package/CHANGELOG.md +245 -0
  4. package/dist/index.d.mts +2552 -8319
  5. package/dist/index.d.ts +2552 -8319
  6. package/dist/index.js +9609 -8607
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +9487 -8454
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +21 -13
  11. package/src/accordion/Accordion.tsx +96 -45
  12. package/src/accordion/Expandable.tsx +54 -127
  13. package/src/accordion/helpers.ts +31 -0
  14. package/src/accordion/types.ts +60 -0
  15. package/src/alert/Alert.tsx +101 -0
  16. package/src/alert/AlertIcon.tsx +63 -45
  17. package/src/alert/ExpandableAlert.tsx +96 -64
  18. package/src/alert/ServiceAlert.tsx +127 -125
  19. package/src/alert/{index.tsx → index.ts} +1 -2
  20. package/src/breadcrumb/Breadcrumb.tsx +39 -24
  21. package/src/button/Button.tsx +86 -105
  22. package/src/button/ButtonGroup.tsx +45 -20
  23. package/src/button/Clipboard.tsx +82 -0
  24. package/src/button/CloseButton.tsx +4 -3
  25. package/src/button/FloatingActionButton.tsx +35 -41
  26. package/src/button/IconButton.tsx +34 -30
  27. package/src/button/index.tsx +1 -0
  28. package/src/color-mode/color-mode.tsx +75 -0
  29. package/src/color-mode/index.ts +1 -0
  30. package/src/datepicker/Calendar.tsx +17 -8
  31. package/src/datepicker/CalendarCell.tsx +20 -13
  32. package/src/datepicker/CalendarGrid.tsx +18 -10
  33. package/src/datepicker/CalendarHeader.tsx +2 -0
  34. package/src/datepicker/CalendarNavigationButton.tsx +1 -0
  35. package/src/datepicker/CalendarTriggerButton.tsx +43 -45
  36. package/src/datepicker/DateField.tsx +21 -12
  37. package/src/datepicker/DatePicker.tsx +61 -58
  38. package/src/datepicker/DateRangePicker.tsx +52 -58
  39. package/src/datepicker/DateTimeSegment.tsx +13 -5
  40. package/src/datepicker/RangeCalendar.tsx +13 -7
  41. package/src/datepicker/StyledField.tsx +25 -17
  42. package/src/datepicker/TimeField.tsx +10 -8
  43. package/src/datepicker/TimePicker.tsx +48 -45
  44. package/src/datepicker/types.ts +5 -0
  45. package/src/dialog/Dialog.tsx +56 -0
  46. package/src/dialog/Drawer.tsx +187 -0
  47. package/src/dialog/index.ts +2 -0
  48. package/src/dialog/types.ts +26 -0
  49. package/src/image/index.tsx +2 -2
  50. package/src/index.tsx +5 -3
  51. package/src/input/AttachedInputs.tsx +17 -42
  52. package/src/input/CardSelect.tsx +75 -162
  53. package/src/input/Checkbox.tsx +30 -6
  54. package/src/input/CheckboxGroup.tsx +25 -16
  55. package/src/input/ChoiceChip.tsx +58 -77
  56. package/src/input/Combobox.tsx +172 -172
  57. package/src/input/CountryCodeSelect.tsx +42 -28
  58. package/src/input/Dialog.tsx +1 -0
  59. package/src/input/Field.tsx +71 -0
  60. package/src/input/Fieldset.tsx +7 -0
  61. package/src/input/Input.tsx +68 -73
  62. package/src/input/InputGroup.tsx +66 -0
  63. package/src/input/ListBox.tsx +83 -70
  64. package/src/input/NativeSelect.tsx +68 -33
  65. package/src/input/NumericStepper.tsx +173 -171
  66. package/src/input/PasswordInput.tsx +99 -52
  67. package/src/input/PhoneNumberInput.tsx +69 -72
  68. package/src/input/Popover.tsx +1 -0
  69. package/src/input/Radio.tsx +37 -17
  70. package/src/input/SearchInput.tsx +24 -86
  71. package/src/input/Select.tsx +237 -0
  72. package/src/input/Switch.tsx +60 -18
  73. package/src/input/Textarea.tsx +53 -101
  74. package/src/input/{index.tsx → index.ts} +2 -8
  75. package/src/layout/PressableCard.tsx +12 -21
  76. package/src/layout/RadioCard.tsx +68 -100
  77. package/src/layout/Separator.tsx +32 -0
  78. package/src/layout/StaticCard.tsx +13 -33
  79. package/src/layout/index.tsx +3 -7
  80. package/src/linjetag/InfoTag.tsx +16 -9
  81. package/src/linjetag/LineIcon.tsx +74 -28
  82. package/src/linjetag/TravelTag.tsx +38 -27
  83. package/src/link/TextLink.tsx +25 -16
  84. package/src/list/index.tsx +24 -2
  85. package/src/loader/ClientOnly.tsx +8 -7
  86. package/src/loader/ColorInlineLoader.tsx +4 -3
  87. package/src/loader/ColorSpinner.tsx +5 -4
  88. package/src/loader/ContentLoader.tsx +6 -4
  89. package/src/loader/DarkFullScreenLoader.tsx +11 -3
  90. package/src/loader/DarkInlineLoader.tsx +5 -3
  91. package/src/loader/DarkSpinner.tsx +7 -3
  92. package/src/loader/LightFullScreenLoader.tsx +11 -3
  93. package/src/loader/LightInlineLoader.tsx +11 -3
  94. package/src/loader/LightSpinner.tsx +5 -3
  95. package/src/loader/Lottie.tsx +3 -3
  96. package/src/loader/ProgressBar.tsx +83 -84
  97. package/src/loader/ProgressLoader.tsx +120 -75
  98. package/src/loader/Skeleton.tsx +94 -19
  99. package/src/loader/index.tsx +0 -2
  100. package/src/loader/useHydrated.tsx +1 -0
  101. package/src/loader/useRotatingLabel.tsx +2 -1
  102. package/src/logo/CargonetLogo.tsx +89 -89
  103. package/src/logo/VyLogo.tsx +61 -42
  104. package/src/logo/VyLogoPride.tsx +137 -139
  105. package/src/media-controller/JumpButton.tsx +48 -38
  106. package/src/media-controller/PlayPauseButton.tsx +31 -29
  107. package/src/media-controller/SkipButton.tsx +38 -37
  108. package/src/nudge/Nudge.tsx +195 -123
  109. package/src/nudge/index.tsx +0 -1
  110. package/src/pagination/Pagination.tsx +221 -118
  111. package/src/pagination/types.ts +23 -0
  112. package/src/popover/index.tsx +67 -0
  113. package/src/progress-indicator/ProgressDot.tsx +11 -10
  114. package/src/progress-indicator/ProgressIndicator.tsx +28 -15
  115. package/src/provider/SporProvider.tsx +17 -14
  116. package/src/stepper/Stepper.tsx +88 -85
  117. package/src/stepper/StepperContext.tsx +2 -1
  118. package/src/stepper/StepperStep.tsx +28 -21
  119. package/src/tab/Tabs.tsx +62 -12
  120. package/src/tab/index.tsx +1 -9
  121. package/src/table/Table.tsx +35 -30
  122. package/src/table/index.tsx +11 -7
  123. package/src/theme/brand.ts +7 -0
  124. package/src/theme/index.ts +45 -37
  125. package/src/theme/recipes/attached-inputs.ts +43 -0
  126. package/src/theme/recipes/badge.ts +104 -0
  127. package/src/theme/recipes/button.ts +124 -0
  128. package/src/theme/recipes/choice-chip.ts +144 -0
  129. package/src/theme/recipes/close-button.ts +41 -0
  130. package/src/theme/recipes/code.ts +14 -0
  131. package/src/theme/recipes/group.ts +19 -0
  132. package/src/theme/recipes/index.ts +29 -0
  133. package/src/theme/recipes/input.ts +89 -0
  134. package/src/theme/recipes/link.ts +64 -0
  135. package/src/theme/recipes/nudge.ts +12 -0
  136. package/src/theme/recipes/pressable-card.ts +83 -0
  137. package/src/theme/recipes/progress-loader.ts +14 -0
  138. package/src/theme/recipes/separator.ts +85 -0
  139. package/src/theme/recipes/skeleton.ts +57 -0
  140. package/src/theme/recipes/static-card.ts +39 -0
  141. package/src/theme/recipes/textarea.ts +27 -0
  142. package/src/theme/semantic-tokens/colors.ts +22 -0
  143. package/src/theme/semantic-tokens/index.ts +24 -0
  144. package/src/theme/semantic-tokens/radii.ts +14 -0
  145. package/src/theme/semantic-tokens/shadows.ts +17 -0
  146. package/src/theme/slot-recipes/accordion.ts +131 -0
  147. package/src/theme/slot-recipes/alert-expandable.ts +133 -0
  148. package/src/theme/slot-recipes/alert-service.ts +66 -0
  149. package/src/theme/slot-recipes/alert.ts +72 -0
  150. package/src/theme/slot-recipes/anatomy.ts +269 -0
  151. package/src/theme/slot-recipes/breadcrumb.ts +61 -0
  152. package/src/theme/slot-recipes/checkbox.ts +89 -0
  153. package/src/theme/slot-recipes/datepicker.ts +214 -0
  154. package/src/theme/slot-recipes/dialog.ts +221 -0
  155. package/src/theme/slot-recipes/drawer.ts +205 -0
  156. package/src/theme/slot-recipes/field.ts +79 -0
  157. package/src/theme/slot-recipes/floating-action-button.ts +131 -0
  158. package/src/theme/slot-recipes/index.ts +65 -0
  159. package/src/theme/slot-recipes/info-tag.ts +62 -0
  160. package/src/theme/slot-recipes/line-icon.ts +140 -0
  161. package/src/theme/slot-recipes/list.ts +45 -0
  162. package/src/theme/slot-recipes/listbox.ts +72 -0
  163. package/src/theme/slot-recipes/media-controller-button.ts +131 -0
  164. package/src/theme/slot-recipes/native-select.ts +54 -0
  165. package/src/theme/slot-recipes/numeric-stepper.ts +65 -0
  166. package/src/theme/slot-recipes/pagination.ts +41 -0
  167. package/src/theme/slot-recipes/popover.ts +78 -0
  168. package/src/theme/slot-recipes/progress-bar.ts +39 -0
  169. package/src/theme/slot-recipes/progress-indicator.ts +22 -0
  170. package/src/theme/slot-recipes/radio-card.ts +112 -0
  171. package/src/theme/slot-recipes/radio.ts +80 -0
  172. package/src/theme/slot-recipes/select.ts +243 -0
  173. package/src/theme/slot-recipes/stepper.ts +92 -0
  174. package/src/theme/slot-recipes/switch.ts +147 -0
  175. package/src/theme/slot-recipes/table.ts +200 -0
  176. package/src/theme/slot-recipes/tabs.ts +169 -0
  177. package/src/theme/slot-recipes/toast.ts +56 -0
  178. package/src/theme/slot-recipes/travel-tag.ts +192 -0
  179. package/src/theme/tokens/animation-styles.ts +50 -0
  180. package/src/theme/tokens/animations.ts +22 -0
  181. package/src/theme/tokens/aspect-ratios.ts +22 -0
  182. package/src/theme/tokens/blurs.ts +28 -0
  183. package/src/theme/tokens/borders.ts +26 -0
  184. package/src/theme/{foundations → tokens}/breakpoints.ts +0 -1
  185. package/src/theme/tokens/colors.ts +10 -0
  186. package/src/theme/tokens/config.ts +10 -0
  187. package/src/theme/tokens/cursor.ts +28 -0
  188. package/src/theme/tokens/durations.ts +25 -0
  189. package/src/theme/tokens/easings.ts +16 -0
  190. package/src/theme/tokens/font-sizes.ts +30 -0
  191. package/src/theme/tokens/font-weights.ts +31 -0
  192. package/src/theme/tokens/fonts.ts +8 -0
  193. package/src/theme/tokens/global-css.ts +18 -0
  194. package/src/theme/tokens/index.ts +37 -0
  195. package/src/theme/tokens/keyframes.ts +255 -0
  196. package/src/theme/tokens/letter-spacings.ts +19 -0
  197. package/src/theme/tokens/line-heights.ts +19 -0
  198. package/src/theme/tokens/radii.ts +13 -0
  199. package/src/theme/tokens/sizes.ts +51 -0
  200. package/src/theme/tokens/spacing.ts +20 -0
  201. package/src/theme/tokens/text-styles.ts +89 -0
  202. package/src/theme/tokens/z-index.ts +17 -0
  203. package/src/theme/utils/accent-utils.ts +8 -21
  204. package/src/theme/utils/bg-utils.ts +4 -6
  205. package/src/theme/utils/brand-utils.ts +6 -19
  206. package/src/theme/utils/core-utils.ts +91 -0
  207. package/src/theme/utils/floating-utils.ts +20 -39
  208. package/src/theme/utils/ghost-utils.ts +7 -21
  209. package/src/theme/utils/input-utils.ts +32 -37
  210. package/src/theme/utils/outline-utils.ts +4 -11
  211. package/src/theme/utils/surface-utils.ts +5 -19
  212. package/src/theme/utils/types.ts +1 -0
  213. package/src/toast/index.tsx +1 -1
  214. package/src/toast/toast.tsx +105 -0
  215. package/src/transition/index.ts +2 -8
  216. package/src/typography/Badge.tsx +15 -61
  217. package/src/typography/Code.tsx +16 -28
  218. package/src/typography/Heading.tsx +34 -19
  219. package/src/typography/Text.tsx +9 -6
  220. package/src/typography/{index.tsx → index.ts} +1 -0
  221. package/src/util/externals.tsx +13 -27
  222. package/tsconfig.json +5 -1
  223. package/src/accordion/Accordion.test.tsx +0 -20
  224. package/src/alert/BaseAlert.test.tsx +0 -37
  225. package/src/alert/BaseAlert.tsx +0 -34
  226. package/src/alert/ClosableAlert.test.tsx +0 -37
  227. package/src/alert/ClosableAlert.tsx +0 -85
  228. package/src/alert/ExpandableAlert.test.tsx +0 -84
  229. package/src/alert/StaticAlert.tsx +0 -33
  230. package/src/button/Button.test.tsx +0 -23
  231. package/src/datepicker/TimePicker.test.tsx +0 -74
  232. package/src/input/FormControl.tsx +0 -2
  233. package/src/input/FormErrorMessage.tsx +0 -95
  234. package/src/input/FormLabel.tsx +0 -11
  235. package/src/input/InfoSelect.tsx +0 -274
  236. package/src/input/InputElement.tsx +0 -44
  237. package/src/input/RadioGroup.tsx +0 -47
  238. package/src/layout/Divider.tsx +0 -27
  239. package/src/layout/RadioCardGroup.tsx +0 -79
  240. package/src/layout/Stack.tsx +0 -42
  241. package/src/loader/SkeletonCircle.tsx +0 -13
  242. package/src/loader/SkeletonText.tsx +0 -14
  243. package/src/media-controller/index.test.tsx +0 -59
  244. package/src/modal/Drawer.tsx +0 -120
  245. package/src/modal/FullScreenDrawer.tsx +0 -239
  246. package/src/modal/Modal.tsx +0 -15
  247. package/src/modal/ModalHeader.tsx +0 -31
  248. package/src/modal/SimpleDrawer.tsx +0 -51
  249. package/src/modal/index.tsx +0 -5
  250. package/src/nudge/WizardNudge.tsx +0 -107
  251. package/src/theme/components/accordion.ts +0 -102
  252. package/src/theme/components/alert-expandable.ts +0 -125
  253. package/src/theme/components/alert-service.ts +0 -98
  254. package/src/theme/components/alert.ts +0 -71
  255. package/src/theme/components/badge.ts +0 -109
  256. package/src/theme/components/breadcrumb.ts +0 -60
  257. package/src/theme/components/button.ts +0 -125
  258. package/src/theme/components/card-select.ts +0 -117
  259. package/src/theme/components/checkbox.ts +0 -88
  260. package/src/theme/components/choice-chip.ts +0 -161
  261. package/src/theme/components/close-button.ts +0 -48
  262. package/src/theme/components/code.ts +0 -17
  263. package/src/theme/components/datepicker.ts +0 -198
  264. package/src/theme/components/divider.ts +0 -50
  265. package/src/theme/components/drawer.ts +0 -95
  266. package/src/theme/components/fab.ts +0 -109
  267. package/src/theme/components/form-label.ts +0 -17
  268. package/src/theme/components/form.ts +0 -27
  269. package/src/theme/components/index.ts +0 -45
  270. package/src/theme/components/info-select.ts +0 -85
  271. package/src/theme/components/info-tag.ts +0 -63
  272. package/src/theme/components/input.ts +0 -28
  273. package/src/theme/components/line-icon.ts +0 -129
  274. package/src/theme/components/link.ts +0 -78
  275. package/src/theme/components/list.ts +0 -23
  276. package/src/theme/components/listbox.ts +0 -77
  277. package/src/theme/components/media-controller-button.ts +0 -97
  278. package/src/theme/components/modal.ts +0 -96
  279. package/src/theme/components/numeric-stepper.ts +0 -65
  280. package/src/theme/components/pagination.ts +0 -74
  281. package/src/theme/components/popover.ts +0 -68
  282. package/src/theme/components/pressable-card.ts +0 -72
  283. package/src/theme/components/progress-bar.ts +0 -47
  284. package/src/theme/components/progress-indicator.ts +0 -44
  285. package/src/theme/components/radio-card.ts +0 -134
  286. package/src/theme/components/radio.ts +0 -68
  287. package/src/theme/components/select.ts +0 -74
  288. package/src/theme/components/skeleton.ts +0 -40
  289. package/src/theme/components/static-card.ts +0 -82
  290. package/src/theme/components/stepper.ts +0 -100
  291. package/src/theme/components/switch.ts +0 -112
  292. package/src/theme/components/table.ts +0 -161
  293. package/src/theme/components/tabs.ts +0 -135
  294. package/src/theme/components/textarea.ts +0 -33
  295. package/src/theme/components/toast.ts +0 -28
  296. package/src/theme/components/travel-tag.ts +0 -256
  297. package/src/theme/foundations/borders.ts +0 -11
  298. package/src/theme/foundations/colors.ts +0 -12
  299. package/src/theme/foundations/config.ts +0 -5
  300. package/src/theme/foundations/fontSizes.ts +0 -29
  301. package/src/theme/foundations/fontWeights.ts +0 -5
  302. package/src/theme/foundations/fonts.ts +0 -7
  303. package/src/theme/foundations/index.ts +0 -15
  304. package/src/theme/foundations/lineHeights.ts +0 -6
  305. package/src/theme/foundations/radii.ts +0 -12
  306. package/src/theme/foundations/shadows.ts +0 -8
  307. package/src/theme/foundations/sizes.ts +0 -36
  308. package/src/theme/foundations/spacing.ts +0 -31
  309. package/src/theme/foundations/styles.ts +0 -12
  310. package/src/theme/foundations/textStyles.ts +0 -74
  311. package/src/theme/foundations/zIndices.ts +0 -17
  312. package/src/theme/utils/base-utils.ts +0 -104
  313. package/src/theme/utils/focus-utils.ts +0 -10
  314. package/src/toast/ActionToast.test.tsx +0 -22
  315. package/src/toast/ActionToast.tsx +0 -28
  316. package/src/toast/BaseToast.test.tsx +0 -27
  317. package/src/toast/BaseToast.tsx +0 -75
  318. package/src/toast/ClosableToast.test.tsx +0 -17
  319. package/src/toast/ClosableToast.tsx +0 -40
  320. package/src/toast/useToast.tsx +0 -121
  321. package/src/tooltip/Tooltip.tsx +0 -70
  322. package/src/tooltip/index.tsx +0 -1
@@ -1,48 +1,50 @@
1
+ "use client";
1
2
  import {
2
3
  chakra,
4
+ RecipeVariantProps,
3
5
  useControllableState,
4
- useFormControl,
5
- useMultiStyleConfig,
6
+ useSlotRecipe,
6
7
  } from "@chakra-ui/react";
7
- import React, { useRef } from "react";
8
- import {
9
- Box,
10
- BoxProps,
11
- Flex,
12
- IconButton,
13
- createTexts,
14
- useTranslation,
15
- } from "..";
8
+ import React, { PropsWithChildren, useRef } from "react";
9
+ import { BoxProps, createTexts, IconButton, useTranslation } from "..";
10
+ import { numericStepperRecipe } from "../theme/slot-recipes/numeric-stepper";
11
+ import { Field } from "./Field";
12
+
13
+ type NumericStepperVariants = RecipeVariantProps<typeof numericStepperRecipe>;
14
+
15
+ export type NumericStepperProps = BoxProps &
16
+ PropsWithChildren<NumericStepperVariants> & {
17
+ children: React.ReactNode;
18
+ /** The name of the input field */
19
+ name?: string;
20
+ /** The current value */
21
+ value?: number;
22
+ /** A default value, if uncontrolled */
23
+ defaultValue?: number;
24
+ /** Callback for when the value changes */
25
+ onChange?: (value: number) => void;
26
+ /** Optional minimum value. Defaults to 0 */
27
+ minValue?: number;
28
+ /** Optional maximum value. Defaults to 99 */
29
+ maxValue?: number;
30
+ /** Whether the stepper is disabled or not */
31
+ disabled?: boolean;
32
+ /** Whether to show input field or not */
33
+ withInput?: boolean;
34
+ /** The amount to increase/decrease when pressing +/- */
35
+ stepSize?: number;
36
+ /** Whether to show the number input when value is zero */
37
+ showZero?: boolean;
38
+ /** Name added to the aria-label of subtract and add buttons. */
39
+ ariaLabelContext?: { singular: string; plural: string };
40
+ } & Omit<BoxProps, "onChange">;
16
41
 
17
- type NumericStepperProps = {
18
- /** The name of the input field */
19
- name?: string;
20
- /** The current value */
21
- value?: number;
22
- /** A default value, if uncontrolled */
23
- defaultValue?: number;
24
- /** Callback for when the value changes */
25
- onChange?: (value: number) => void;
26
- /** Optional minimum value. Defaults to 0 */
27
- minValue?: number;
28
- /** Optional maximum value. Defaults to 99 */
29
- maxValue?: number;
30
- /** Whether the stepper is disabled or not */
31
- isDisabled?: boolean;
32
- /** Whether to show input field or not */
33
- withInput?: boolean;
34
- /** The amount to increase/decrease when pressing +/- */
35
- stepSize?: number;
36
- /** Whether to show the number input when value is zero */
37
- showZero?: boolean;
38
- /** Name added to the aria-label of subtract and add buttons. */
39
- ariaLabelContext?: { singular: string; plural: string };
40
- } & Omit<BoxProps, "onChange">;
41
42
  /** A simple stepper component for integer values
42
43
  *
43
44
  * Allows you to choose a given integer value, like for example the number of
44
45
  * adults on your journey.
45
46
  *
47
+ * @example
46
48
  * ```tsx
47
49
  * <NumericStepper value={value} onChange={setValue} />
48
50
  * ```
@@ -53,128 +55,137 @@ type NumericStepperProps = {
53
55
  * <NumericStepper value={value} onChange={setValue} minValue={1} maxValue={10} stepSize={3} />
54
56
  * ```
55
57
  *
56
- * You can use the NumericStepper inside of a FormControl component to get IDs etc linked up automatically:
58
+ * You can use the NumericStepper inside of a Field component to get IDs etc linked up automatically:
57
59
  *
58
60
  * ```tsx
59
- * <FormControl>
60
- * <FormLabel>Number of adults</FormLabel>
61
- * <NumericStepper />
62
- * </FormControl>
61
+ * <NumericStepper />
63
62
  * ```
63
+ * @see https://spor.vy.no/components/numeric-stepper
64
64
  */
65
- export function NumericStepper({
66
- name: nameProp,
67
- id: idProp,
68
- value: valueProp,
69
- defaultValue = 1,
70
- onChange: onChangeProp,
71
- minValue = 0,
72
- maxValue = 99,
73
- isDisabled,
74
- withInput = true,
75
- stepSize = 1,
76
- showZero = false,
77
- ariaLabelContext = { singular: "", plural: "" },
78
- ...boxProps
79
- }: NumericStepperProps) {
80
- const addButtonRef = useRef<HTMLButtonElement>(null);
81
- const { t } = useTranslation();
82
- const styles = useMultiStyleConfig("NumericStepper", {});
83
- const [value, onChange] = useControllableState<number>({
84
- value: valueProp,
85
- onChange: onChangeProp,
86
- defaultValue,
87
- });
88
- const formControlProps = useFormControl({ id: idProp, isDisabled });
89
- const clampedStepSize = Math.max(Math.min(stepSize, 10), 1);
90
65
 
91
- const focusOnAddButton = () => {
92
- addButtonRef.current?.focus();
93
- };
66
+ export const NumericStepper = React.forwardRef<
67
+ HTMLDivElement,
68
+ NumericStepperProps
69
+ >(
70
+ (
71
+ {
72
+ name: nameProp,
73
+ id: idProp,
74
+ value: valueProp,
75
+ defaultValue = 1,
76
+ onChange: onChangeProp,
77
+ minValue = 0,
78
+ maxValue = 99,
79
+ disabled,
80
+ withInput = true,
81
+ stepSize = 1,
82
+ showZero = false,
83
+ ariaLabelContext = { singular: "", plural: "" },
84
+ }: NumericStepperProps,
85
+ ref,
86
+ ) => {
87
+ const addButtonRef = useRef<HTMLButtonElement>(null);
88
+ const { t } = useTranslation();
89
+ const recipe = useSlotRecipe({ recipe: numericStepperRecipe });
90
+ const styles = recipe();
91
+ const [value, onChange] = useControllableState<number>({
92
+ value: valueProp,
93
+ onChange: onChangeProp,
94
+ defaultValue,
95
+ });
96
+ const clampedStepSize = Math.max(Math.min(stepSize, 10), 1);
94
97
 
95
- return (
96
- <Flex __css={styles.container} {...boxProps}>
97
- <VerySmallButton
98
- icon={<SubtractIcon stepLabel={clampedStepSize} />}
99
- aria-label={t(
100
- texts.decrementButtonAriaLabel(
101
- clampedStepSize,
102
- stepSize == 1 ? ariaLabelContext.singular : ariaLabelContext.plural,
103
- ),
104
- )}
105
- onClick={() => {
106
- onChange(Math.max(value - clampedStepSize, minValue));
107
- if (Math.max(value - clampedStepSize, minValue) <= minValue) {
108
- focusOnAddButton();
109
- }
110
- }}
111
- visibility={value <= minValue ? "hidden" : "visible"}
112
- isDisabled={formControlProps.disabled}
113
- id={value <= minValue ? undefined : formControlProps.id}
114
- />
115
- {withInput ? (
116
- <chakra.input
117
- type="number"
118
- min={minValue}
119
- max={maxValue}
120
- name={nameProp}
121
- value={value}
122
- {...formControlProps}
123
- id={!showZero && value === 0 ? undefined : formControlProps.id}
124
- sx={styles.input}
125
- width={`${Math.max(value.toString().length + 1, 3)}ch`}
126
- visibility={!showZero && value === 0 ? "hidden" : "visible"}
127
- aria-live="assertive"
128
- aria-label={
129
- ariaLabelContext.plural !== ""
130
- ? t(texts.currentNumberAriaLabel(ariaLabelContext.plural))
131
- : ""
132
- }
133
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
134
- const numericInput = Number(e.target.value);
135
- if (Number.isNaN(numericInput)) {
136
- return;
137
- }
138
- onChange(Math.max(Math.min(numericInput, maxValue), minValue));
139
- if (
140
- !showZero &&
141
- Math.max(Math.min(numericInput, maxValue), minValue) === 0
142
- ) {
98
+ const focusOnAddButton = () => {
99
+ addButtonRef.current?.focus();
100
+ };
101
+
102
+ return (
103
+ <Field css={styles.root} flexDirection="row" width="auto">
104
+ <VerySmallButton
105
+ icon={<SubtractIcon stepLabel={clampedStepSize} />}
106
+ aria-label={t(
107
+ texts.decrementButtonAriaLabel(
108
+ clampedStepSize,
109
+ stepSize === 1
110
+ ? ariaLabelContext.singular
111
+ : ariaLabelContext.plural,
112
+ ),
113
+ )}
114
+ onClick={() => {
115
+ onChange(Math.max(value - clampedStepSize, minValue));
116
+ if (Math.max(value - clampedStepSize, minValue) <= minValue) {
143
117
  focusOnAddButton();
144
118
  }
145
119
  }}
120
+ visibility={value <= minValue ? "hidden" : "visible"}
121
+ disabled={disabled}
122
+ id={value <= minValue ? undefined : idProp}
146
123
  />
147
- ) : (
148
- <chakra.text
149
- sx={styles.text}
150
- visibility={!showZero && value === 0 ? "hidden" : "visible"}
151
- aria-live="assertive"
152
- aria-label={
153
- ariaLabelContext.plural !== ""
154
- ? t(texts.currentNumberAriaLabel(ariaLabelContext.plural))
155
- : ""
156
- }
157
- >
158
- {value}
159
- </chakra.text>
160
- )}
161
- <VerySmallButton
162
- ref={addButtonRef}
163
- icon={<AddIcon stepLabel={clampedStepSize} />}
164
- aria-label={t(
165
- texts.incrementButtonAriaLabel(
166
- clampedStepSize,
167
- stepSize == 1 ? ariaLabelContext.singular : ariaLabelContext.plural,
168
- ),
124
+ {withInput ? (
125
+ <chakra.input
126
+ min={minValue}
127
+ max={maxValue}
128
+ name={nameProp}
129
+ value={value}
130
+ disabled={disabled}
131
+ id={!showZero && value === 0 ? undefined : idProp}
132
+ css={styles.input}
133
+ width={`${Math.max(value.toString().length + 1, 3)}ch`}
134
+ visibility={!showZero && value === 0 ? "hidden" : "visible"}
135
+ aria-live="assertive"
136
+ aria-label={
137
+ ariaLabelContext.plural !== ""
138
+ ? t(texts.currentNumberAriaLabel(ariaLabelContext.plural))
139
+ : ""
140
+ }
141
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
142
+ const numericInput = Number(e.target.value);
143
+ if (Number.isNaN(numericInput)) {
144
+ return;
145
+ }
146
+ onChange(Math.max(Math.min(numericInput, maxValue), minValue));
147
+ if (
148
+ !showZero &&
149
+ Math.max(Math.min(numericInput, maxValue), minValue) === 0
150
+ ) {
151
+ focusOnAddButton();
152
+ }
153
+ }}
154
+ />
155
+ ) : (
156
+ <chakra.text
157
+ css={styles}
158
+ visibility={!showZero && value === 0 ? "hidden" : "visible"}
159
+ aria-live="assertive"
160
+ aria-label={
161
+ ariaLabelContext.plural !== ""
162
+ ? t(texts.currentNumberAriaLabel(ariaLabelContext.plural))
163
+ : ""
164
+ }
165
+ >
166
+ {value}
167
+ </chakra.text>
169
168
  )}
170
- onClick={() => onChange(Math.min(value + clampedStepSize, maxValue))}
171
- visibility={value >= maxValue ? "hidden" : "visible"}
172
- isDisabled={formControlProps.disabled}
173
- id={value >= maxValue ? undefined : formControlProps.id}
174
- />
175
- </Flex>
176
- );
177
- }
169
+ <VerySmallButton
170
+ ref={addButtonRef}
171
+ icon={<AddIcon stepLabel={clampedStepSize} />}
172
+ aria-label={t(
173
+ texts.incrementButtonAriaLabel(
174
+ clampedStepSize,
175
+ stepSize === 1
176
+ ? ariaLabelContext.singular
177
+ : ariaLabelContext.plural,
178
+ ),
179
+ )}
180
+ onClick={() => onChange(Math.min(value + clampedStepSize, maxValue))}
181
+ visibility={value >= maxValue ? "hidden" : "visible"}
182
+ disabled={disabled}
183
+ id={value >= maxValue ? undefined : idProp}
184
+ />
185
+ </Field>
186
+ );
187
+ },
188
+ );
178
189
 
179
190
  type VerySmallButtonProps = {
180
191
  /** The icon to render */
@@ -186,18 +197,23 @@ type VerySmallButtonProps = {
186
197
  /** Whether or not the button is hidden */
187
198
  visibility?: "visible" | "hidden";
188
199
  /** Whether or not the button is disabled */
189
- isDisabled?: boolean;
200
+ disabled?: boolean;
190
201
  /** The ID of the button */
191
202
  id?: string;
192
203
  };
204
+
193
205
  /** Internal override for extra small icon buttons */
194
- const VerySmallButton = React.forwardRef((props: VerySmallButtonProps, ref) => {
195
- const styles = useMultiStyleConfig("NumericStepper", {});
206
+ const VerySmallButton = React.forwardRef<
207
+ HTMLButtonElement,
208
+ VerySmallButtonProps
209
+ >((props, ref) => {
210
+ const recipe = useSlotRecipe({ recipe: numericStepperRecipe });
211
+ const styles = recipe({ colorPalette: "default" });
196
212
  return (
197
213
  <IconButton
198
214
  variant="primary"
199
215
  size="xs"
200
- sx={styles.button}
216
+ css={styles.button}
201
217
  ref={ref}
202
218
  {...props}
203
219
  />
@@ -206,16 +222,9 @@ const VerySmallButton = React.forwardRef((props: VerySmallButtonProps, ref) => {
206
222
 
207
223
  type IconPropTypes = BoxProps & { stepLabel: number };
208
224
 
209
- const SubtractIcon = ({ stepLabel, ...props }: IconPropTypes) => (
225
+ const SubtractIcon = ({ stepLabel }: IconPropTypes) => (
210
226
  <>
211
- <Box
212
- as="svg"
213
- viewBox="0 0 30 30"
214
- width="24"
215
- height="24"
216
- stroke="currentColor"
217
- {...props}
218
- >
227
+ <chakra.svg as="svg" viewBox="0 0 30 30" stroke="currentColor">
219
228
  <line
220
229
  x1="9"
221
230
  y1="15"
@@ -223,24 +232,18 @@ const SubtractIcon = ({ stepLabel, ...props }: IconPropTypes) => (
223
232
  y2="15"
224
233
  strokeWidth="1.5"
225
234
  strokeLinecap="round"
235
+ preserveAspectRatio="xMidYMid meet"
226
236
  />
227
- </Box>
237
+ </chakra.svg>
228
238
  {stepLabel > 1 && (
229
239
  <chakra.span paddingRight="1">{stepLabel.toString()}</chakra.span>
230
240
  )}
231
241
  </>
232
242
  );
233
243
 
234
- const AddIcon = ({ stepLabel, ...props }: IconPropTypes) => (
244
+ const AddIcon = ({ stepLabel }: IconPropTypes) => (
235
245
  <>
236
- <Box
237
- as="svg"
238
- viewBox="0 0 30 30"
239
- width="24"
240
- height="24"
241
- stroke="currentColor"
242
- {...props}
243
- >
246
+ <chakra.svg as="svg" viewBox="0 0 30 30" stroke="currentColor">
244
247
  <line
245
248
  x1="9"
246
249
  y1="15"
@@ -257,8 +260,7 @@ const AddIcon = ({ stepLabel, ...props }: IconPropTypes) => (
257
260
  strokeWidth="1.5"
258
261
  strokeLinecap="round"
259
262
  />
260
- </Box>
261
-
263
+ </chakra.svg>
262
264
  {stepLabel > 1 && (
263
265
  <chakra.span paddingRight="1">{stepLabel.toString()}</chakra.span>
264
266
  )}
@@ -1,61 +1,108 @@
1
- import {
2
- Button,
3
- Input as ChakraInput,
4
- forwardRef,
5
- useDisclosure,
6
- useFormControlContext,
7
- } from "@chakra-ui/react";
8
- import React, { useId } from "react";
9
- import {
10
- FormLabel,
11
- InputGroup,
12
- InputLeftElement,
13
- InputProps,
14
- InputRightElement,
15
- } from ".";
1
+ "use client";
2
+
3
+ import { Button, useControllableState } from "@chakra-ui/react";
4
+ import React, { forwardRef } from "react";
5
+ import { ButtonProps, Input, InputProps } from "..";
16
6
  import { createTexts, useTranslation } from "..";
7
+ import { InputGroupProps } from "./InputGroup";
8
+
9
+ export interface PasswordVisibilityProps {
10
+ /** Default visibility state */
11
+ defaultVisible?: boolean;
12
+ /** Visibility state */
13
+ visible?: boolean;
14
+ /** Callback for when the visibility state changes */
15
+ onVisibleChange?: (visible: boolean) => void;
16
+ }
17
+
18
+ export interface PasswordInputProps
19
+ extends InputProps,
20
+ PasswordVisibilityProps {
21
+ rootProps?: InputGroupProps;
22
+ }
23
+
24
+ /**
25
+ * A password input field with a visibility toggle.
26
+ *
27
+ * ```tsx
28
+ * <PasswordInput label="Password" />
29
+ * ```
30
+ *
31
+ * You can also control the visibility state:
32
+ *
33
+ * ```tsx
34
+ * <PasswordInput label="Password" visible={visible} onVisibleChange={setVisible} />
35
+ * ```
36
+ *
37
+ * You can also set the default visibility state:
38
+ *
39
+ * ```tsx
40
+ * <PasswordInput label="Password" defaultVisible />
41
+ * ```
42
+ *
43
+ * @see https://spor.vy.no/components/password-input
44
+ */
45
+
46
+ export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
47
+ (props, ref) => {
48
+ const {
49
+ rootProps,
50
+ defaultVisible,
51
+ visible: visibleProp,
52
+ onVisibleChange,
53
+ label,
54
+ startElement,
55
+ ...rest
56
+ } = props;
57
+
58
+ const [visible, setVisible] = useControllableState({
59
+ value: visibleProp,
60
+ defaultValue: defaultVisible || false,
61
+ onChange: onVisibleChange,
62
+ });
17
63
 
18
- export type PasswordInputProps = InputProps;
19
- export const PasswordInput = forwardRef<PasswordInputProps, "input">(
20
- ({ leftIcon, id, label, size, ...props }, ref) => {
21
- const { isOpen: isShowingPassword, onToggle } = useDisclosure();
22
64
  const { t } = useTranslation();
23
- const formControlProps = useFormControlContext();
24
- const autoGeneratedId = `password-input-${useId()}`;
25
- const inputId = id ?? formControlProps?.id ?? autoGeneratedId;
65
+
26
66
  return (
27
- <InputGroup position="relative">
28
- {leftIcon && (
29
- <InputLeftElement pointerEvents="none">{leftIcon}</InputLeftElement>
30
- )}
31
- <ChakraInput
32
- {...props}
33
- id={inputId}
34
- placeholder=" " // This is needed to make the label work as expected
35
- type={isShowingPassword ? "text" : "password"}
36
- paddingRight={10}
37
- paddingLeft={leftIcon ? 7 : undefined}
38
- ref={ref}
39
- data-attachable
40
- />
41
- <FormLabel htmlFor={inputId} pointerEvents="none">
42
- {label}
43
- </FormLabel>
44
- <InputRightElement width="fit-content">
45
- <Button
67
+ <Input
68
+ ref={ref}
69
+ startElement={startElement && startElement}
70
+ label={label}
71
+ type={visible ? "text" : "password"}
72
+ endElement={
73
+ <VisibilityTrigger
46
74
  variant="ghost"
47
- type="button"
48
- fontWeight="normal"
49
- size="sm"
50
- onClick={onToggle}
51
- borderRadius="sm"
52
- marginRight={1}
53
- isDisabled={props.disabled || props.isDisabled}
75
+ disabled={rest.disabled}
76
+ onPointerDown={(e) => {
77
+ if (rest.disabled) return;
78
+ if (e.button !== 0) return;
79
+ e.preventDefault();
80
+ setVisible(!visible);
81
+ }}
54
82
  >
55
- {isShowingPassword ? t(texts.hidePassword) : t(texts.showPassword)}
56
- </Button>
57
- </InputRightElement>
58
- </InputGroup>
83
+ {visible ? t(texts.hidePassword) : t(texts.showPassword)}
84
+ </VisibilityTrigger>
85
+ }
86
+ {...rest}
87
+ />
88
+ );
89
+ },
90
+ );
91
+
92
+ const VisibilityTrigger = React.forwardRef<HTMLButtonElement, ButtonProps>(
93
+ (props, ref) => {
94
+ return (
95
+ <Button
96
+ ref={ref}
97
+ type="button"
98
+ fontWeight="normal"
99
+ size="sm"
100
+ borderRadius="sm"
101
+ marginRight={1}
102
+ {...props}
103
+ >
104
+ {props.children}
105
+ </Button>
59
106
  );
60
107
  },
61
108
  );