azamat-ui-kit-cli 0.2.2 → 0.3.4

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 (103) hide show
  1. package/README.md +11 -0
  2. package/dist/index.cjs +452 -0
  3. package/package.json +2 -2
  4. package/vendor/src/components/actions/action-menu.tsx +21 -18
  5. package/vendor/src/components/calendar/calendar.tsx +153 -102
  6. package/vendor/src/components/calendar/date-picker.tsx +24 -14
  7. package/vendor/src/components/calendar/date-range-picker.tsx +137 -58
  8. package/vendor/src/components/charts/charts.tsx +32 -21
  9. package/vendor/src/components/command/command-palette.tsx +68 -57
  10. package/vendor/src/components/data-table/data-table-bulk-actions.tsx +23 -20
  11. package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +21 -10
  12. package/vendor/src/components/data-table/data-table-pagination.tsx +6 -6
  13. package/vendor/src/components/data-table/data-table-toolbar.tsx +72 -44
  14. package/vendor/src/components/data-table/data-table.tsx +15 -11
  15. package/vendor/src/components/data-table/table-export-menu.tsx +1 -1
  16. package/vendor/src/components/data-table/table-import-button.tsx +1 -1
  17. package/vendor/src/components/display/data-state.tsx +20 -8
  18. package/vendor/src/components/display/index.ts +19 -15
  19. package/vendor/src/components/display/metric-card.tsx +35 -0
  20. package/vendor/src/components/display/progress-circle.tsx +24 -0
  21. package/vendor/src/components/display/smart-card.tsx +49 -27
  22. package/vendor/src/components/display/status-dot.tsx +45 -0
  23. package/vendor/src/components/display/user-card.tsx +30 -0
  24. package/vendor/src/components/feedback/alert.tsx +21 -11
  25. package/vendor/src/components/feedback/empty-state.tsx +2 -2
  26. package/vendor/src/components/feedback/loading-state.tsx +2 -2
  27. package/vendor/src/components/feedback/page-state.tsx +19 -15
  28. package/vendor/src/components/feedback/status-badge.tsx +43 -43
  29. package/vendor/src/components/form/form-app-input.tsx +147 -0
  30. package/vendor/src/components/form/form-date-input.tsx +16 -19
  31. package/vendor/src/components/form/form-field-shell.tsx +11 -8
  32. package/vendor/src/components/form/form-field-utils.ts +76 -0
  33. package/vendor/src/components/form/form-input.tsx +423 -44
  34. package/vendor/src/components/form/form-number-input.tsx +16 -15
  35. package/vendor/src/components/form/form-phone-input.tsx +15 -9
  36. package/vendor/src/components/form/form-search-input.tsx +16 -19
  37. package/vendor/src/components/form/form-select.tsx +4 -3
  38. package/vendor/src/components/form/public.ts +16 -14
  39. package/vendor/src/components/form/smart-form-shell.tsx +13 -12
  40. package/vendor/src/components/inputs/app-input.tsx +27 -0
  41. package/vendor/src/components/inputs/async-select.tsx +113 -84
  42. package/vendor/src/components/inputs/clearable-input.tsx +81 -61
  43. package/vendor/src/components/inputs/date-input.tsx +21 -17
  44. package/vendor/src/components/inputs/date-range-input.tsx +10 -10
  45. package/vendor/src/components/inputs/index.ts +1 -0
  46. package/vendor/src/components/inputs/input-decorator.tsx +101 -57
  47. package/vendor/src/components/inputs/masked-input.tsx +20 -20
  48. package/vendor/src/components/inputs/money-input.tsx +2 -2
  49. package/vendor/src/components/inputs/number-input.tsx +29 -19
  50. package/vendor/src/components/inputs/password-input.tsx +82 -45
  51. package/vendor/src/components/inputs/phone-input.tsx +24 -2
  52. package/vendor/src/components/inputs/quantity-input.tsx +2 -2
  53. package/vendor/src/components/inputs/search-input.tsx +54 -3
  54. package/vendor/src/components/inputs/simple-select.tsx +110 -22
  55. package/vendor/src/components/layout/app-shell.tsx +2 -2
  56. package/vendor/src/components/layout/index.ts +5 -4
  57. package/vendor/src/components/layout/page-header.tsx +79 -35
  58. package/vendor/src/components/layout/public.ts +12 -10
  59. package/vendor/src/components/layout/section-header.tsx +56 -0
  60. package/vendor/src/components/layout/stack.tsx +106 -0
  61. package/vendor/src/components/layout/stat-card.tsx +66 -29
  62. package/vendor/src/components/navigation/index.ts +1 -0
  63. package/vendor/src/components/navigation/nav-tabs.tsx +60 -0
  64. package/vendor/src/components/navigation/page-tabs.tsx +41 -26
  65. package/vendor/src/components/navigation/pagination.tsx +14 -10
  66. package/vendor/src/components/overlay/alert-dialog.tsx +65 -0
  67. package/vendor/src/components/overlay/drawer.tsx +71 -0
  68. package/vendor/src/components/overlay/index.ts +4 -2
  69. package/vendor/src/components/patterns/data-view.tsx +13 -8
  70. package/vendor/src/components/ui/badge.tsx +96 -52
  71. package/vendor/src/components/ui/button.tsx +99 -61
  72. package/vendor/src/components/ui/card.tsx +84 -25
  73. package/vendor/src/components/ui/checkbox.tsx +68 -68
  74. package/vendor/src/components/ui/command.tsx +32 -32
  75. package/vendor/src/components/ui/dialog.tsx +135 -138
  76. package/vendor/src/components/ui/dropdown-menu.tsx +21 -21
  77. package/vendor/src/components/ui/hover-card.tsx +49 -0
  78. package/vendor/src/components/ui/input-primitive.tsx +24 -0
  79. package/vendor/src/components/ui/input.tsx +191 -20
  80. package/vendor/src/components/ui/kbd.tsx +33 -0
  81. package/vendor/src/components/ui/popover.tsx +11 -11
  82. package/vendor/src/components/ui/radio-group.tsx +102 -0
  83. package/vendor/src/components/ui/right-click-menu.tsx +60 -0
  84. package/vendor/src/components/ui/scroll-box.tsx +27 -0
  85. package/vendor/src/components/ui/segmented-control.tsx +21 -17
  86. package/vendor/src/components/ui/select.tsx +187 -189
  87. package/vendor/src/components/ui/skeleton.tsx +2 -2
  88. package/vendor/src/components/ui/switch.tsx +60 -60
  89. package/vendor/src/components/ui/table.tsx +114 -114
  90. package/vendor/src/components/ui/tabs.tsx +2 -2
  91. package/vendor/src/components/ui/textarea.tsx +1 -1
  92. package/vendor/src/components/upload/file-dropzone.tsx +38 -0
  93. package/vendor/src/components/upload/file-upload.tsx +4 -4
  94. package/vendor/src/components/upload/image-upload.tsx +22 -19
  95. package/vendor/src/components/upload/index.ts +2 -0
  96. package/vendor/src/families/catalog.ts +1 -0
  97. package/vendor/src/families/docs-groups.ts +10 -1
  98. package/vendor/src/families/member-metadata.ts +24 -0
  99. package/vendor/src/families/member-snippets.ts +41 -2
  100. package/vendor/src/families/migration-map.ts +3 -0
  101. package/vendor/src/index.ts +23 -18
  102. package/vendor/templates/styles/globals.css +253 -0
  103. package/dist/index.js +0 -432
@@ -1,29 +1,33 @@
1
- import * as React from "react"
2
-
3
- import { createInputChangeHandler, getInputValue } from "@/components/inputs/input-value"
4
- import { Input } from "@/components/ui/input"
1
+ import * as React from "react"
2
+ import { CalendarDaysIcon } from "lucide-react"
3
+
4
+ import { InputDecorator } from "@/components/inputs/input-decorator"
5
+ import { createInputChangeHandler, getInputValue } from "@/components/inputs/input-value"
5
6
 
6
7
  export type DateInputProps = Omit<
7
- React.ComponentProps<typeof Input>,
8
+ React.ComponentProps<typeof InputDecorator>,
8
9
  "type" | "value" | "onChange"
9
10
  > & {
10
11
  value?: string | null
11
12
  onChange?: React.ChangeEventHandler<HTMLInputElement>
12
13
  onValueChange?: (value: string) => void
14
+ showIcon?: boolean
15
+ icon?: React.ReactNode
13
16
  }
14
17
 
15
- const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
16
- ({ value, onChange, onValueChange, ...props }, ref) => {
17
- const handleChange = createInputChangeHandler({ onChange, onValueChange })
18
-
19
- return (
20
- <Input
21
- ref={ref}
22
- type="date"
23
- value={getInputValue(value)}
24
- onChange={handleChange}
25
- {...props}
26
- />
18
+ const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
19
+ ({ value, onChange, onValueChange, showIcon = true, icon, leading, ...props }, ref) => {
20
+ const handleChange = createInputChangeHandler({ onChange, onValueChange })
21
+
22
+ return (
23
+ <InputDecorator
24
+ ref={ref}
25
+ type="date"
26
+ value={getInputValue(value)}
27
+ leading={showIcon ? icon ?? leading ?? <CalendarDaysIcon /> : leading}
28
+ onChange={handleChange}
29
+ {...props}
30
+ />
27
31
  )
28
32
  }
29
33
  )
@@ -46,20 +46,20 @@ function DateRangeInput({
46
46
  {...props}
47
47
  >
48
48
  <DateInput
49
- value={value?.from ?? ""}
50
- placeholder={fromPlaceholder}
51
- className={inputClassName}
52
- onValueChange={(from) => nextValue({ from })}
49
+ value={value?.from ?? ""}
50
+ placeholder={fromPlaceholder}
51
+ className={inputClassName}
52
+ onValueChange={(from: string) => nextValue({ from })}
53
53
  {...fromInputProps}
54
54
  />
55
55
  <span className="hidden text-sm text-muted-foreground sm:inline-flex">{separator}</span>
56
56
  <DateInput
57
- value={value?.to ?? ""}
58
- placeholder={toPlaceholder}
59
- className={inputClassName}
60
- onValueChange={(to) => nextValue({ to })}
61
- {...toInputProps}
62
- />
57
+ value={value?.to ?? ""}
58
+ placeholder={toPlaceholder}
59
+ className={inputClassName}
60
+ onValueChange={(to: string) => nextValue({ to })}
61
+ {...toInputProps}
62
+ />
63
63
  </div>
64
64
  )
65
65
  }
@@ -1,3 +1,4 @@
1
+ export * from './app-input'
1
2
  export * from './simple-select'
2
3
  export * from './async-select'
3
4
  export * from './clearable-input'
@@ -1,64 +1,108 @@
1
1
  import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
2
3
 
3
- import { Input } from "@/components/ui/input"
4
+ import { InputPrimitive } from "@/components/ui/input-primitive"
4
5
  import { cn } from "@/lib/utils"
5
-
6
- type InputDecoratorProps = Omit<React.ComponentProps<typeof Input>, "value"> & {
7
- value?: string | number | null
8
- leading?: React.ReactNode
9
- trailing?: React.ReactNode
10
- wrapperClassName?: string
11
- inputClassName?: string
12
- }
13
-
14
- const InputDecorator = React.forwardRef<HTMLInputElement, InputDecoratorProps>(
15
- (
16
- {
17
- className,
18
- value,
19
- leading,
20
- trailing,
21
- wrapperClassName,
22
- inputClassName,
23
- ...props
24
- },
25
- ref
26
- ) => {
27
- const hasLeading = Boolean(leading)
28
- const hasTrailing = Boolean(trailing)
29
-
30
- return (
31
- <div
32
- data-slot="input-decorator"
33
- className={cn("relative flex w-full items-center", wrapperClassName)}
34
- >
35
- {hasLeading && (
36
- <span className="pointer-events-none absolute left-2.5 flex text-muted-foreground [&_svg]:size-4">
37
- {leading}
38
- </span>
39
- )}
40
-
41
- <Input
6
+
7
+ const inputDecoratorVariants = cva("relative flex w-full items-center", {
8
+ variants: {
9
+ density: {
10
+ compact: "text-xs",
11
+ default: "text-sm",
12
+ comfortable: "text-base",
13
+ },
14
+ tone: {
15
+ neutral: "",
16
+ info: "[&_[data-slot=input]]:border-blue-500/30 [&_[data-slot=input]]:focus-visible:ring-blue-500/20",
17
+ success: "[&_[data-slot=input]]:border-emerald-500/30 [&_[data-slot=input]]:focus-visible:ring-emerald-500/20",
18
+ warning: "[&_[data-slot=input]]:border-amber-500/35 [&_[data-slot=input]]:focus-visible:ring-amber-500/20",
19
+ danger: "[&_[data-slot=input]]:border-destructive/40 [&_[data-slot=input]]:focus-visible:ring-destructive/20",
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ density: "default",
24
+ tone: "neutral",
25
+ },
26
+ })
27
+
28
+ export type InputDecoratorProps = Omit<React.ComponentProps<typeof InputPrimitive>, "value"> &
29
+ VariantProps<typeof inputDecoratorVariants> & {
30
+ value?: string | number | null
31
+ leading?: React.ReactNode
32
+ trailing?: React.ReactNode
33
+ leadingPointerEvents?: boolean
34
+ trailingPointerEvents?: boolean
35
+ wrapperClassName?: string
36
+ inputClassName?: string
37
+ leadingClassName?: string
38
+ trailingClassName?: string
39
+ }
40
+
41
+ const InputDecorator = React.forwardRef<HTMLInputElement, InputDecoratorProps>(
42
+ (
43
+ {
44
+ className,
45
+ value,
46
+ leading,
47
+ trailing,
48
+ leadingPointerEvents = false,
49
+ trailingPointerEvents = true,
50
+ wrapperClassName,
51
+ inputClassName,
52
+ leadingClassName,
53
+ trailingClassName,
54
+ density,
55
+ tone,
56
+ ...props
57
+ },
58
+ ref
59
+ ) => {
60
+ const hasLeading = Boolean(leading)
61
+ const hasTrailing = Boolean(trailing)
62
+
63
+ return (
64
+ <div
65
+ data-slot="input-decorator"
66
+ data-has-leading={hasLeading || undefined}
67
+ data-has-trailing={hasTrailing || undefined}
68
+ className={cn(inputDecoratorVariants({ density, tone }), wrapperClassName)}
69
+ >
70
+ {hasLeading && (
71
+ <span
72
+ data-slot="input-leading"
73
+ className={cn(
74
+ "absolute left-2.5 z-10 flex items-center text-muted-foreground [&_svg]:size-4",
75
+ !leadingPointerEvents && "pointer-events-none",
76
+ leadingClassName
77
+ )}
78
+ >
79
+ {leading}
80
+ </span>
81
+ )}
82
+
83
+ <InputPrimitive
42
84
  ref={ref}
43
85
  value={value == null ? "" : String(value)}
44
- className={cn(
45
- hasLeading && "pl-8",
46
- hasTrailing && "pr-9",
47
- inputClassName,
48
- className
49
- )}
86
+ className={cn(hasLeading && "pl-8", hasTrailing && "pr-9", inputClassName, className)}
50
87
  {...props}
51
88
  />
52
-
53
- {hasTrailing && (
54
- <span className="absolute right-2 flex items-center gap-1">
55
- {trailing}
56
- </span>
57
- )}
58
- </div>
59
- )
60
- }
61
- )
62
- InputDecorator.displayName = "InputDecorator"
63
-
64
- export { InputDecorator }
89
+
90
+ {hasTrailing && (
91
+ <span
92
+ data-slot="input-trailing"
93
+ className={cn(
94
+ "absolute right-2 z-10 flex items-center gap-1 text-muted-foreground",
95
+ !trailingPointerEvents && "pointer-events-none",
96
+ trailingClassName
97
+ )}
98
+ >
99
+ {trailing}
100
+ </span>
101
+ )}
102
+ </div>
103
+ )
104
+ }
105
+ )
106
+ InputDecorator.displayName = "InputDecorator"
107
+
108
+ export { InputDecorator, inputDecoratorVariants }
@@ -1,10 +1,10 @@
1
- import * as React from "react"
2
-
3
- import { getInputValue, setInputElementValue } from "@/components/inputs/input-value"
4
- import { Input } from "@/components/ui/input"
1
+ import * as React from "react"
2
+
3
+ import { InputDecorator } from "@/components/inputs/input-decorator"
4
+ import { getInputValue, setInputElementValue } from "@/components/inputs/input-value"
5
5
 
6
6
  export type MaskedInputProps = Omit<
7
- React.ComponentProps<typeof Input>,
7
+ React.ComponentProps<typeof InputDecorator>,
8
8
  "value" | "onChange"
9
9
  > & {
10
10
  value?: string
@@ -31,21 +31,21 @@ const MaskedInput = React.forwardRef<HTMLInputElement, MaskedInputProps>(
31
31
  mask = defaultMask,
32
32
  unmask = defaultUnmask,
33
33
  ...props
34
- },
35
- ref
36
- ) => {
37
- const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
38
- const rawValue = unmask(event.target.value)
39
- const maskedValue = mask(rawValue)
40
-
41
- setInputElementValue(event, maskedValue)
42
- onChange?.(event)
43
- onValueChange?.(maskedValue, rawValue)
44
- }
45
-
46
- return <Input ref={ref} value={getInputValue(value)} onChange={handleChange} {...props} />
47
- }
48
- )
34
+ },
35
+ ref
36
+ ) => {
37
+ const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
38
+ const rawValue = unmask(event.target.value)
39
+ const maskedValue = mask(rawValue)
40
+
41
+ setInputElementValue(event, maskedValue)
42
+ onChange?.(event)
43
+ onValueChange?.(maskedValue, rawValue)
44
+ }
45
+
46
+ return <InputDecorator ref={ref} value={getInputValue(value)} onChange={handleChange} {...props} />
47
+ }
48
+ )
49
49
  MaskedInput.displayName = "MaskedInput"
50
50
 
51
51
  export { MaskedInput }
@@ -3,7 +3,7 @@ import * as React from "react"
3
3
  import { InputChrome } from "@/components/inputs/input-chrome"
4
4
  import { getInputValue } from "@/components/inputs/input-value"
5
5
  import { parseMoneyLikeInput } from "@/components/inputs/numeric-value"
6
- import { Input } from "@/components/ui/input"
6
+ import { InputPrimitive } from "@/components/ui/input-primitive"
7
7
  import { cn } from "@/lib/utils"
8
8
 
9
9
  export type MoneyInputProps = Omit<
@@ -55,7 +55,7 @@ function MoneyInput({
55
55
  ) : null
56
56
  }
57
57
  >
58
- <Input
58
+ <InputPrimitive
59
59
  value={getInputValue(value)}
60
60
  onChange={handleChange}
61
61
  inputMode={inputMode}
@@ -1,11 +1,11 @@
1
- import * as React from "react"
2
-
3
- import { getInputValue } from "@/components/inputs/input-value"
4
- import { clampNumericValue, parseDecimalInput } from "@/components/inputs/numeric-value"
5
- import { Input } from "@/components/ui/input"
1
+ import * as React from "react"
2
+
3
+ import { InputDecorator } from "@/components/inputs/input-decorator"
4
+ import { getInputValue } from "@/components/inputs/input-value"
5
+ import { clampNumericValue, parseDecimalInput } from "@/components/inputs/numeric-value"
6
6
 
7
7
  export type NumberInputProps = Omit<
8
- React.ComponentProps<typeof Input>,
8
+ React.ComponentProps<typeof InputDecorator>,
9
9
  "type" | "value" | "onChange" | "min" | "max" | "step"
10
10
  > & {
11
11
  value?: number | string | null
@@ -16,15 +16,17 @@ export type NumberInputProps = Omit<
16
16
  max?: number
17
17
  step?: number
18
18
  allowEmpty?: boolean
19
+ prefix?: React.ReactNode
20
+ suffix?: React.ReactNode
21
+ }
22
+
23
+ function parseNumberInput(value: string) {
24
+ return parseDecimalInput(value)
19
25
  }
20
26
 
21
- function parseNumberInput(value: string) {
22
- return parseDecimalInput(value)
23
- }
24
-
25
- function clampNumber(value: number, min?: number, max?: number) {
26
- return clampNumericValue(value, min, max)
27
- }
27
+ function clampNumber(value: number, min?: number, max?: number) {
28
+ return clampNumericValue(value, min, max)
29
+ }
28
30
 
29
31
  const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
30
32
  (
@@ -37,13 +39,17 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
37
39
  max,
38
40
  step,
39
41
  allowEmpty = true,
42
+ prefix,
43
+ suffix,
44
+ leading,
45
+ trailing,
40
46
  inputMode = "decimal",
41
47
  onBlur,
42
48
  ...props
43
- },
44
- ref
45
- ) => {
46
- const stringValue = getInputValue(value)
49
+ },
50
+ ref
51
+ ) => {
52
+ const stringValue = getInputValue(value)
47
53
 
48
54
  const emitValue = (rawValue: string) => {
49
55
  const parsed = parseNumberInput(rawValue)
@@ -60,14 +66,16 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
60
66
 
61
67
  const handleBlur: React.FocusEventHandler<HTMLInputElement> = (event) => {
62
68
  if (!allowEmpty && !event.target.value.trim()) {
63
- emitValue(String(min ?? 0))
69
+ const fallbackValue = String(min ?? 0)
70
+ event.currentTarget.value = fallbackValue
71
+ emitValue(fallbackValue)
64
72
  }
65
73
 
66
74
  onBlur?.(event)
67
75
  }
68
76
 
69
77
  return (
70
- <Input
78
+ <InputDecorator
71
79
  ref={ref}
72
80
  type="text"
73
81
  value={stringValue}
@@ -75,6 +83,8 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
75
83
  min={min}
76
84
  max={max}
77
85
  step={step}
86
+ leading={prefix ?? leading}
87
+ trailing={suffix ?? trailing}
78
88
  onChange={handleChange}
79
89
  onBlur={handleBlur}
80
90
  {...props}
@@ -1,19 +1,21 @@
1
- import * as React from "react"
2
- import { EyeIcon, EyeOffIcon } from "lucide-react"
3
-
4
- import { InputDecorator } from "@/components/inputs/input-decorator"
1
+ import * as React from "react"
2
+ import { EyeIcon, EyeOffIcon } from "lucide-react"
5
3
 
6
- export type PasswordInputProps = Omit<
7
- React.ComponentProps<typeof InputDecorator>,
8
- "type" | "value" | "onChange"
9
- > & {
10
- value?: string | null
4
+ import { InputDecorator } from "@/components/inputs/input-decorator"
5
+
6
+ export type PasswordInputProps = Omit<
7
+ React.ComponentProps<typeof InputDecorator>,
8
+ "type" | "value" | "onChange"
9
+ > & {
10
+ value?: string | null
11
11
  onChange?: React.ChangeEventHandler<HTMLInputElement>
12
12
  onValueChange?: (value: string) => void
13
13
  visible?: boolean
14
14
  defaultVisible?: boolean
15
15
  onVisibleChange?: (visible: boolean) => void
16
16
  showToggle?: boolean
17
+ showCapsLockWarning?: boolean
18
+ capsLockLabel?: string
17
19
  wrapperClassName?: string
18
20
  inputClassName?: string
19
21
  showLabel?: string
@@ -22,23 +24,30 @@ export type PasswordInputProps = Omit<
22
24
 
23
25
  const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
24
26
  (
25
- {
26
- value,
27
- onChange,
28
- onValueChange,
27
+ {
28
+ value,
29
+ onChange,
30
+ onValueChange,
29
31
  visible,
30
32
  defaultVisible = false,
31
- onVisibleChange,
32
- showToggle = true,
33
- showLabel = "Show password",
34
- hideLabel = "Hide password",
35
- disabled,
33
+ onVisibleChange,
34
+ showToggle = true,
35
+ showCapsLockWarning = true,
36
+ capsLockLabel = "Caps Lock is on",
37
+ showLabel = "Show password",
38
+ hideLabel = "Hide password",
39
+ disabled,
40
+ autoComplete = "current-password",
41
+ trailing,
42
+ onKeyDown,
43
+ onKeyUp,
36
44
  ...props
37
45
  },
38
46
  ref
39
47
  ) => {
40
48
  const isControlled = visible !== undefined
41
49
  const [internalVisible, setInternalVisible] = React.useState(defaultVisible)
50
+ const [capsLockOn, setCapsLockOn] = React.useState(false)
42
51
  const currentVisible = isControlled ? visible : internalVisible
43
52
 
44
53
  const setVisibleState = (nextVisible: boolean) => {
@@ -53,33 +62,61 @@ const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
53
62
  onChange?.(event)
54
63
  onValueChange?.(event.target.value)
55
64
  }
56
-
57
- return (
58
- <InputDecorator
59
- data-slot="password-input"
60
- ref={ref}
61
- type={currentVisible ? "text" : "password"}
62
- value={value ?? ""}
63
- disabled={disabled}
64
- onChange={handleChange}
65
- trailing={
66
- showToggle ? (
67
- <button
68
- type="button"
69
- disabled={disabled}
70
- aria-label={currentVisible ? hideLabel : showLabel}
71
- className="rounded-sm p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
72
- onClick={() => setVisibleState(!currentVisible)}
73
- >
74
- {currentVisible ? <EyeOffIcon className="size-4" /> : <EyeIcon className="size-4" />}
75
- </button>
76
- ) : null
77
- }
78
- {...props}
79
- />
80
- )
81
- }
82
- )
65
+
66
+ const updateCapsLock = (event: React.KeyboardEvent<HTMLInputElement>) => {
67
+ if (!showCapsLockWarning) return
68
+ setCapsLockOn(event.getModifierState("CapsLock"))
69
+ }
70
+
71
+ const handleKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
72
+ updateCapsLock(event)
73
+ onKeyDown?.(event)
74
+ }
75
+
76
+ const handleKeyUp: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
77
+ updateCapsLock(event)
78
+ onKeyUp?.(event)
79
+ }
80
+
81
+ const trailingContent = (
82
+ <>
83
+ {trailing}
84
+ {showCapsLockWarning && capsLockOn ? (
85
+ <span data-slot="password-caps-lock" className="hidden text-xs text-amber-600 sm:inline" aria-live="polite">
86
+ {capsLockLabel}
87
+ </span>
88
+ ) : null}
89
+ {showToggle ? (
90
+ <button
91
+ type="button"
92
+ disabled={disabled}
93
+ aria-label={currentVisible ? hideLabel : showLabel}
94
+ className="rounded-sm p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
95
+ onClick={() => setVisibleState(!currentVisible)}
96
+ >
97
+ {currentVisible ? <EyeOffIcon className="size-4" /> : <EyeIcon className="size-4" />}
98
+ </button>
99
+ ) : null}
100
+ </>
101
+ )
102
+
103
+ return (
104
+ <InputDecorator
105
+ data-slot="password-input"
106
+ ref={ref}
107
+ type={currentVisible ? "text" : "password"}
108
+ value={value ?? ""}
109
+ disabled={disabled}
110
+ autoComplete={autoComplete}
111
+ onChange={handleChange}
112
+ onKeyDown={handleKeyDown}
113
+ onKeyUp={handleKeyUp}
114
+ trailing={trailingContent}
115
+ {...props}
116
+ />
117
+ )
118
+ }
119
+ )
83
120
  PasswordInput.displayName = "PasswordInput"
84
121
 
85
122
  export { PasswordInput }
@@ -1,10 +1,14 @@
1
1
  import * as React from "react"
2
+ import { PhoneIcon } from "lucide-react"
2
3
 
3
4
  import { MaskedInput, type MaskedInputProps } from "@/components/inputs/masked-input"
4
5
 
5
6
  export type PhoneInputProps = Omit<MaskedInputProps, "mask" | "unmask" | "inputMode"> & {
6
7
  countryCode?: string
7
8
  maxDigits?: number
9
+ showCountryCode?: boolean
10
+ showIcon?: boolean
11
+ icon?: React.ReactNode
8
12
  }
9
13
 
10
14
  function onlyDigits(value: string) {
@@ -28,14 +32,32 @@ function formatPhoneDigits(digits: string, countryCode = "+998", maxDigits = 12)
28
32
  }
29
33
 
30
34
  const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
31
- ({ countryCode = "+998", maxDigits = 12, onValueChange, ...props }, ref) => {
35
+ (
36
+ {
37
+ countryCode = "+998",
38
+ maxDigits = 12,
39
+ showCountryCode = false,
40
+ showIcon = false,
41
+ icon,
42
+ leading,
43
+ trailing,
44
+ onValueChange,
45
+ ...props
46
+ },
47
+ ref
48
+ ) => {
49
+ const leadingContent = showIcon ? icon ?? leading ?? <PhoneIcon /> : leading
50
+ const trailingContent = showCountryCode ? <span className="font-mono text-xs">{countryCode}</span> : trailing
51
+
32
52
  return (
33
53
  <MaskedInput
34
54
  ref={ref}
35
55
  inputMode="tel"
56
+ leading={leadingContent}
57
+ trailing={trailingContent}
36
58
  mask={(rawValue) => formatPhoneDigits(rawValue, countryCode, maxDigits)}
37
59
  unmask={onlyDigits}
38
- onValueChange={(maskedValue, rawValue) => onValueChange?.(maskedValue, rawValue)}
60
+ onValueChange={(maskedValue: string, rawValue: string) => onValueChange?.(maskedValue, rawValue)}
39
61
  {...props}
40
62
  />
41
63
  )
@@ -5,7 +5,7 @@ import { InputChrome } from "@/components/inputs/input-chrome"
5
5
  import { getInputValue } from "@/components/inputs/input-value"
6
6
  import { clampNumericValue, parseDecimalInput } from "@/components/inputs/numeric-value"
7
7
  import { Button } from "@/components/ui/button"
8
- import { Input } from "@/components/ui/input"
8
+ import { InputPrimitive } from "@/components/ui/input-primitive"
9
9
  import { cn } from "@/lib/utils"
10
10
 
11
11
  export type QuantityInputProps = Omit<
@@ -98,7 +98,7 @@ function QuantityInput({
98
98
  ) : null
99
99
  }
100
100
  >
101
- <Input
101
+ <InputPrimitive
102
102
  value={getInputValue(value)}
103
103
  onChange={handleInputChange}
104
104
  inputMode={inputMode}