@xjur-ui/react 1.0.4 → 2.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 (114) hide show
  1. package/.turbo/turbo-build.log +98 -42
  2. package/CHANGELOG.md +51 -0
  3. package/README.md +146 -0
  4. package/dist/chunk-CBFC4QME.js +2 -0
  5. package/dist/chunk-CBFC4QME.js.map +1 -0
  6. package/dist/chunk-EIG6CBTQ.js +2 -0
  7. package/dist/chunk-EIG6CBTQ.js.map +1 -0
  8. package/dist/chunk-FASEHVMD.js +2 -0
  9. package/dist/chunk-FASEHVMD.js.map +1 -0
  10. package/dist/chunk-KIFR5VIE.js +2 -0
  11. package/dist/chunk-KIFR5VIE.js.map +1 -0
  12. package/dist/chunk-KS6WGBHQ.js +2 -0
  13. package/dist/chunk-KS6WGBHQ.js.map +1 -0
  14. package/dist/chunk-NELCN6E2.js +2 -0
  15. package/dist/chunk-NELCN6E2.js.map +1 -0
  16. package/dist/chunk-NPTLORW5.js +2 -0
  17. package/dist/chunk-NPTLORW5.js.map +1 -0
  18. package/dist/chunk-RWMV7APX.js +2 -0
  19. package/dist/chunk-RWMV7APX.js.map +1 -0
  20. package/dist/chunk-SHHXKVVR.js +2 -0
  21. package/dist/chunk-SHHXKVVR.js.map +1 -0
  22. package/dist/chunk-TB6KI6CL.js +2 -0
  23. package/dist/chunk-TB6KI6CL.js.map +1 -0
  24. package/dist/components/avatar-group.js +2 -179
  25. package/dist/components/avatar-group.js.map +1 -0
  26. package/dist/components/avatar.js +2 -59
  27. package/dist/components/avatar.js.map +1 -0
  28. package/dist/components/badge.js +2 -111
  29. package/dist/components/badge.js.map +1 -0
  30. package/dist/components/button.js +2 -98
  31. package/dist/components/button.js.map +1 -0
  32. package/dist/components/calendar.js +2 -751
  33. package/dist/components/calendar.js.map +1 -0
  34. package/dist/components/checkbox.js +2 -153
  35. package/dist/components/checkbox.js.map +1 -0
  36. package/dist/components/chip.d.ts +3 -1
  37. package/dist/components/chip.js +2 -256
  38. package/dist/components/chip.js.map +1 -0
  39. package/dist/components/collapsible.js +2 -41
  40. package/dist/components/collapsible.js.map +1 -0
  41. package/dist/components/date-picker.d.ts +8 -3
  42. package/dist/components/date-picker.js +2 -232
  43. package/dist/components/date-picker.js.map +1 -0
  44. package/dist/components/divider.js +2 -29
  45. package/dist/components/divider.js.map +1 -0
  46. package/dist/components/dropdown-menu.js +2 -295
  47. package/dist/components/dropdown-menu.js.map +1 -0
  48. package/dist/components/icon.js +2 -177
  49. package/dist/components/icon.js.map +1 -0
  50. package/dist/components/input.d.ts +9 -4
  51. package/dist/components/input.js +2 -315
  52. package/dist/components/input.js.map +1 -0
  53. package/dist/components/list.js +2 -179
  54. package/dist/components/list.js.map +1 -0
  55. package/dist/components/logo.js +2 -324
  56. package/dist/components/logo.js.map +1 -0
  57. package/dist/components/otp-input.js +2 -47
  58. package/dist/components/otp-input.js.map +1 -0
  59. package/dist/components/radio.js +2 -156
  60. package/dist/components/radio.js.map +1 -0
  61. package/dist/components/rich-editor.d.ts +2 -1
  62. package/dist/components/rich-editor.js +2 -723
  63. package/dist/components/rich-editor.js.map +1 -0
  64. package/dist/components/search-input.js +2 -274
  65. package/dist/components/search-input.js.map +1 -0
  66. package/dist/components/select.d.ts +6 -3
  67. package/dist/components/select.js +2 -550
  68. package/dist/components/select.js.map +1 -0
  69. package/dist/components/sheet.js +2 -251
  70. package/dist/components/sheet.js.map +1 -0
  71. package/dist/components/skeleton.js +2 -16
  72. package/dist/components/skeleton.js.map +1 -0
  73. package/dist/components/status.d.ts +14 -0
  74. package/dist/components/status.js +2 -0
  75. package/dist/components/status.js.map +1 -0
  76. package/dist/components/switch.js +2 -36
  77. package/dist/components/switch.js.map +1 -0
  78. package/dist/components/table.js +2 -196
  79. package/dist/components/table.js.map +1 -0
  80. package/dist/components/tabs.d.ts +9 -6
  81. package/dist/components/tabs.js +2 -54
  82. package/dist/components/tabs.js.map +1 -0
  83. package/dist/components/tag.d.ts +3 -1
  84. package/dist/components/tag.js +2 -189
  85. package/dist/components/tag.js.map +1 -0
  86. package/dist/components/tooltip.js +2 -58
  87. package/dist/components/tooltip.js.map +1 -0
  88. package/dist/components/typography.js +2 -78
  89. package/dist/components/typography.js.map +1 -0
  90. package/dist/hooks/index.d.ts +7 -0
  91. package/dist/hooks/index.js +2 -0
  92. package/dist/hooks/index.js.map +1 -0
  93. package/dist/index.css +2 -2318
  94. package/dist/index.css.map +1 -0
  95. package/dist/logo-PQRZORCS.js +3 -0
  96. package/dist/logo-PQRZORCS.js.map +1 -0
  97. package/package.json +42 -7
  98. package/src/components/calendar.tsx +7 -6
  99. package/src/components/chip.tsx +11 -3
  100. package/src/components/date-picker.tsx +125 -22
  101. package/src/components/icon.tsx +1 -1
  102. package/src/components/input.tsx +124 -60
  103. package/src/components/logo.tsx +25 -20
  104. package/src/components/rich-editor.tsx +4 -3
  105. package/src/components/select.tsx +104 -26
  106. package/src/components/status.tsx +43 -0
  107. package/src/components/tabs.tsx +56 -37
  108. package/src/components/tag.tsx +11 -3
  109. package/src/index.css +2 -2
  110. package/src/resources/logo/index.tsx +33 -0
  111. package/tsup.config.ts +23 -5
  112. package/dist/index.js +0 -0
  113. package/src/index.ts +0 -1
  114. package/src/resources/logo/index.ts +0 -3
@@ -1,29 +1,86 @@
1
+ import { createContext, useContext } from 'react'
1
2
  import type { ComponentProps } from 'react'
2
3
  import * as RadixPopover from '@radix-ui/react-popover'
3
- import { cx } from '@xjur-ui/util'
4
+ import { tv, twJoin, twMerge } from '@xjur-ui/util'
4
5
  import { Typography, type TypographyProps } from './typography'
5
6
 
6
- export function DatePickerRoot(
7
- props: ComponentProps<typeof RadixPopover.Root>
8
- ) {
9
- return <RadixPopover.Root {...props} />
7
+ const DatePickerContext = createContext({
8
+ disabled: false,
9
+ variant: 'filled' as 'filled' | 'outlined',
10
+ hasError: false
11
+ })
12
+
13
+ function useDatePicker() {
14
+ const context = useContext(DatePickerContext)
15
+ if (!context) {
16
+ throw new Error('useDatePicker must be used within a DatePickerRoot')
17
+ }
18
+ return context
19
+ }
20
+
21
+ export function DatePickerRoot({
22
+ disabled = false,
23
+ variant = 'filled',
24
+ hasError = false,
25
+ ...props
26
+ }: ComponentProps<typeof RadixPopover.Root> & {
27
+ disabled?: boolean
28
+ variant?: 'filled' | 'outlined'
29
+ hasError?: boolean
30
+ }) {
31
+ return (
32
+ <DatePickerContext.Provider value={{ disabled, variant, hasError }}>
33
+ <RadixPopover.Root {...props} />
34
+ </DatePickerContext.Provider>
35
+ )
10
36
  }
11
37
 
12
38
  export function DatePickerTrigger({
13
39
  className,
14
40
  ...props
15
41
  }: ComponentProps<typeof RadixPopover.Trigger>) {
42
+ const { disabled, variant, hasError } = useDatePicker()
43
+
44
+ const trigger = tv({
45
+ base: twMerge(
46
+ 'border border-transparent group h-large-4x rounded-small-1x gap-small py-small_1x-alt px-small relative flex w-full items-center cursor-pointer',
47
+ 'data-[state=open]:border-primary-idle',
48
+ 'has-[input:not(:placeholder-shown)]:text-primary-idle',
49
+ 'has-[input:not(:placeholder-shown)]:border-primary-idle',
50
+ 'focus-within:border-primary-idle focus-within:hover:border-primary-idle',
51
+ 'hover:border-border-layer-1'
52
+ ),
53
+ variants: {
54
+ variant: {
55
+ filled: 'bg-layer-2-hover',
56
+ outlined:
57
+ 'bg-layer-2-idle border-border-layer-2 hover:border-border-layer-3',
58
+ disabled:
59
+ 'hover:border-transparent border-none bg-layer-2-idle cursor-text select-text'
60
+ },
61
+ state: {
62
+ idle: '',
63
+ error: twMerge(
64
+ 'focus-within:border-danger-idle hover:border-danger-idle border-danger-idle focus-within:hover:border-danger-idle',
65
+ 'data-[state=open]:border-danger-idle has-[input:not(:placeholder-shown)]:border-danger-idle'
66
+ )
67
+ }
68
+ },
69
+ defaultVariants: {
70
+ variant: 'filled',
71
+ state: 'idle'
72
+ }
73
+ })
74
+
16
75
  return (
17
76
  <RadixPopover.Trigger
18
- className={cx(
19
- className,
20
- 'group h-large-4x rounded-small-1x gap-small py-small_1x-alt px-small relative flex w-full cursor-pointer items-center',
21
- 'bg-layer-2-idle border-border-layer-2 hover:border-border-layer-3 border',
22
- 'data-[state=open]:border-primary-idle',
23
- 'has-[input:not(:placeholder-shown)]:text-primary-idle',
24
- 'has-[input:not(:placeholder-shown)]:border-primary-idle'
25
- )}
77
+ className={trigger({
78
+ variant: disabled ? 'disabled' : variant,
79
+ state: hasError ? 'error' : 'idle',
80
+ className
81
+ })}
26
82
  {...props}
83
+ disabled={disabled}
27
84
  />
28
85
  )
29
86
  }
@@ -39,7 +96,7 @@ export function DatePickerContent({
39
96
  <RadixPopover.Portal>
40
97
  <RadixPopover.Content
41
98
  align={align}
42
- className={cx(
99
+ className={twJoin(
43
100
  className,
44
101
  'group z-10 max-h-[var(--radix-popover-content-available-height)] max-w-[var(--radix-popover-content-available-width)] min-w-[var(--radix-popover-trigger-width)]'
45
102
  )}
@@ -68,7 +125,7 @@ export function DatePickerValue({
68
125
  <input
69
126
  {...props}
70
127
  aria-readonly={true}
71
- className={cx('pointer-events-none', className)}
128
+ className={twJoin('pointer-events-none', className)}
72
129
  hidden={true}
73
130
  placeholder={props.placeholder ?? ' '}
74
131
  readOnly={true}
@@ -84,20 +141,22 @@ export function DatePickerLabel({
84
141
  children,
85
142
  ...props
86
143
  }: TypographyProps & { htmlFor?: string }) {
144
+ const { variant, disabled, hasError } = useDatePicker()
145
+
87
146
  return (
88
147
  <Typography
89
148
  asChild={true}
90
- className={cx(
91
- 'inline-block max-w-full truncate text-left font-normal',
149
+ className={twJoin(
150
+ 'group inline-block max-w-full truncate text-left font-normal',
92
151
  'left-small right-small pointer-events-none absolute top-1/2 -translate-y-1/2',
93
152
  'ease transition-all duration-250',
94
153
  // Content Open
95
- 'group-data-[state=open]:text-primary-idle',
154
+ 'group-data-[state=open]:text-primary-idle group-data-[state=open]:data-[error=true]:text-danger-label-active',
96
155
  'group-data-[state=open]:-top-[3px]',
97
156
  'group-data-[state=open]:text-size-label-sm-1x',
98
157
  'group-data-[state=open]:font-medium',
99
158
  // Filled
100
- 'group-has-[input:not(:placeholder-shown)]:text-primary-idle',
159
+ 'group-has-[input:not(:placeholder-shown)]:text-primary-idle group-has-[input:not(:placeholder-shown)]:data-[error=true]:text-danger-label-active',
101
160
  'group-has-[input:not(:placeholder-shown)]:-top-[3px]',
102
161
  'group-has-[input:not(:placeholder-shown)]:text-size-label-sm-1x',
103
162
  'group-has-[input:not(:placeholder-shown)]:font-medium',
@@ -113,13 +172,24 @@ export function DatePickerLabel({
113
172
  'group-data-[state=open]:animate-in',
114
173
  'group-data-[state=closed]:animate-out',
115
174
  'group-has-[input:not(:placeholder-shown)]:animate-out',
175
+ 'data-[disabled=true]:hidden',
176
+
116
177
  className
117
178
  )}
179
+ data-disabled={disabled}
180
+ data-error={hasError}
181
+ data-variant={variant}
118
182
  weight="medium"
119
183
  {...props}
120
184
  >
121
185
  <label htmlFor={htmlFor}>
122
- <span className="bg-layer-2-idle rounded-small-1x max-w-full">
186
+ <span
187
+ className={twJoin(
188
+ 'rounded-small-1x max-w-full',
189
+ 'group-data-[variant=outlined]:bg-layer-2-idle',
190
+ 'group-data-[variant=filled]:bg-layer-2-hover'
191
+ )}
192
+ >
123
193
  {children}
124
194
  </span>
125
195
  </label>
@@ -134,7 +204,7 @@ export function DatePickerLeftSlot({
134
204
  return (
135
205
  <div
136
206
  {...props}
137
- className={cx(
207
+ className={twJoin(
138
208
  className,
139
209
  'left-small absolute top-1/2 flex -translate-y-1/2 items-center justify-center'
140
210
  )}
@@ -150,7 +220,7 @@ export function DatePickerRightSlot({
150
220
  return (
151
221
  <div
152
222
  {...props}
153
- className={cx(
223
+ className={twJoin(
154
224
  className,
155
225
  'right-small absolute top-1/2 flex -translate-y-1/2 items-center justify-center'
156
226
  )}
@@ -158,3 +228,36 @@ export function DatePickerRightSlot({
158
228
  />
159
229
  )
160
230
  }
231
+
232
+ export function DatePickerCaption({
233
+ className,
234
+ variant = 'body',
235
+ size = 'sm',
236
+ weight = 'normal',
237
+ ...props
238
+ }: ComponentProps<typeof Typography>) {
239
+ const { hasError } = useDatePicker()
240
+
241
+ const caption = tv({
242
+ base: 'text-size-body-sm mt-small-1x font-normal',
243
+ variants: {
244
+ state: {
245
+ error: 'text-danger-label-active',
246
+ idle: 'text-neutral-dark'
247
+ }
248
+ }
249
+ })
250
+
251
+ return (
252
+ <Typography
253
+ className={caption({
254
+ state: hasError ? 'error' : 'idle',
255
+ className
256
+ })}
257
+ size={size}
258
+ variant={variant}
259
+ weight={weight}
260
+ {...props}
261
+ />
262
+ )
263
+ }
@@ -13,7 +13,7 @@ export function Icon({ className, filled = false, name, ...props }: IconProps) {
13
13
  {...props}
14
14
  className={cx(
15
15
  'material-symbols-outlined',
16
- 'text-size-body-lg',
16
+ 'text-size-body-lg select-none',
17
17
  className
18
18
  )}
19
19
  style={{
@@ -1,36 +1,53 @@
1
- import { cva, cx } from '@xjur-ui/util'
1
+ import { tv, twMerge } from '@xjur-ui/util'
2
2
  import type { ComponentProps } from 'react'
3
- import { createContext } from 'react'
3
+ import { createContext, useContext, useMemo } from 'react'
4
4
  import { InputMask, type MaskOptions } from '@react-input/mask'
5
5
  import {
6
6
  InputNumberFormat,
7
7
  type InputNumberFormatProps
8
8
  } from '@react-input/number-format'
9
9
  import { inputMask, type InputMaskType } from '../resources/input-mask'
10
+ import { Typography } from './typography'
10
11
 
11
12
  export { inputMask, type InputMaskType, type MaskOptions }
12
13
 
13
14
  export type InputProps = {
14
15
  variant?: 'outlined' | 'filled'
16
+ disabled?: boolean
17
+ hasError?: boolean
15
18
  }
16
19
 
17
20
  const FieldContext = createContext<InputProps>({
18
- variant: 'filled'
21
+ variant: 'filled',
22
+ disabled: false,
23
+ hasError: false
19
24
  })
20
25
 
26
+ function useInput() {
27
+ const context = useContext(FieldContext)
28
+ if (!context) {
29
+ throw new Error('useInput must be used within a InputRoot')
30
+ }
31
+ return context
32
+ }
33
+
21
34
  export function InputRoot({
22
35
  className,
23
36
  children,
24
37
  variant = 'filled',
38
+ disabled = false,
39
+ hasError = false,
25
40
  ...props
26
41
  }: ComponentProps<'div'> & InputProps) {
42
+ const providerValue = useMemo(
43
+ () => ({ variant, disabled, hasError }),
44
+ [variant, disabled, hasError]
45
+ )
46
+
27
47
  return (
28
- <FieldContext.Provider value={{ variant }}>
48
+ <FieldContext.Provider value={providerValue}>
29
49
  <div
30
- className={cx(
31
- 'group gap-small relative flex w-full flex-col',
32
- className
33
- )}
50
+ className={twMerge('group relative flex w-full flex-col', className)}
34
51
  {...props}
35
52
  >
36
53
  {children}
@@ -40,31 +57,45 @@ export function InputRoot({
40
57
  }
41
58
 
42
59
  export function InputContent({ className, ...props }: ComponentProps<'div'>) {
43
- const variants = cva(
44
- [
45
- 'relative h-large-4x rounded-small-1x gap-small py-small_1x-alt px-small flex items-center w-full',
46
- 'focus-within:border-primary-idle focus-within:border',
60
+ const content = tv({
61
+ base: twMerge(
62
+ 'border border-transparent relative h-large-4x rounded-small-1x gap-small py-small_1x-alt px-small flex items-center w-full',
47
63
  'group-has-[div[data-slot=input-right-action-slot]]:rounded-r-none',
48
- 'group-has-[div[data-slot=input-left-action-slot]]:rounded-l-none'
49
- ],
50
- {
51
- variants: {
52
- variant: {
53
- default: '',
54
- outlined:
55
- 'bg-layer-2-idle border-border-layer-2 border hover:group-has-[input:not(:focus-within)]:border-border-layer-3 has-[input:not(:placeholder-shown)]:border-primary-idle',
56
- filled:
57
- 'bg-layer-2-hover group-hover:border-border-layer-1 group-hover:border-1'
58
- }
64
+ 'group-has-[div[data-slot=input-left-action-slot]]:rounded-l-none',
65
+ 'focus-within:border-primary-idle focus-within:hover:border-primary-idle',
66
+ 'hover:border-border-layer-1'
67
+ ),
68
+ variants: {
69
+ variant: {
70
+ filled: 'bg-layer-2-hover',
71
+ outlined:
72
+ 'bg-layer-2-idle border-border-layer-2 hover:border-border-layer-3',
73
+ disabled: 'hover:border-transparent border-none bg-layer-2-idle'
74
+ },
75
+ state: {
76
+ idle: '',
77
+ error:
78
+ 'focus-within:border-danger-idle hover:border-danger-idle border-danger-idle focus-within:hover:border-danger-idle'
59
79
  }
80
+ },
81
+ defaultVariants: {
82
+ variant: 'filled',
83
+ state: 'idle'
60
84
  }
61
- )
85
+ })
62
86
 
63
87
  return (
64
88
  <FieldContext.Consumer>
65
- {({ variant }) => {
89
+ {({ variant, disabled, hasError }) => {
66
90
  return (
67
- <div className={cx(className, variants({ variant }))} {...props} />
91
+ <div
92
+ className={content({
93
+ variant: disabled ? 'disabled' : variant,
94
+ state: hasError ? 'error' : 'idle',
95
+ className
96
+ })}
97
+ {...props}
98
+ />
68
99
  )
69
100
  }}
70
101
  </FieldContext.Consumer>
@@ -76,22 +107,24 @@ export function InputLabel({
76
107
  className,
77
108
  children,
78
109
  ...props
79
- }: ComponentProps<'label'> & { htmlFor: string }) {
110
+ }: ComponentProps<typeof Typography> & { htmlFor: string }) {
80
111
  return (
81
112
  <FieldContext.Consumer>
82
- {({ variant }) => {
113
+ {({ variant, disabled, hasError }) => {
83
114
  return (
84
- <label
85
- className={cx(
86
- 'group text-size-body-md text-neutral-dark inline-block max-w-full truncate text-left',
115
+ <Typography
116
+ asChild={true}
117
+ className={twMerge(
118
+ 'group inline-block max-w-full truncate text-left',
87
119
  'ease animate-in left-small right-small pointer-events-none absolute top-1/2 -translate-y-1/2 transition-all duration-250',
88
120
  // Filled
89
- 'group-focus-within:text-primary-idle group-focus-within:-top-[3px]',
121
+ 'group-focus-within:!text-primary-idle group-focus-within:data-[has-error=true]:!text-danger-label-active group-focus-within:-top-[3px]',
90
122
  'group-focus-within:text-size-label-sm-1x group-focus-within:font-medium',
91
123
  'group-has-[input:not(:placeholder-shown)]:-top-[3px]',
92
124
  'group-has-[input:not(:placeholder-shown)]:text-size-label-sm-1x',
93
125
  'group-has-[input:not(:placeholder-shown)]:font-medium',
94
- 'data-[variant=outlined]:group-has-[input:not(:placeholder-shown)]:text-primary-idle',
126
+ 'group-has-[input:not(:placeholder-shown)]:!text-primary-idle',
127
+ 'group-has-[input:not(:placeholder-shown)]:!text-danger-label-active',
95
128
  // If has left slot
96
129
  'group-has-[div[data-slot=input-left-slot]]:left-large-4x',
97
130
  'group-has-[div[data-slot=input-left-slot]]:group-focus-within:left-small',
@@ -103,22 +136,26 @@ export function InputLabel({
103
136
  // Animate
104
137
  'group-focus-within:animate-in',
105
138
  'group-has-[input:not(:placeholder-shown)]:animate-out',
139
+ 'data-[disabled=true]:hidden',
106
140
  className
107
141
  )}
142
+ data-disabled={disabled}
143
+ data-has-error={hasError}
108
144
  data-variant={variant}
109
- htmlFor={htmlFor}
110
145
  {...props}
111
146
  >
112
- <span
113
- className={cx(
114
- 'rounded-small-1x max-w-full',
115
- 'group-data-[variant=outlined]:group-focus-within:bg-layer-2-idle group-data-[variant=filled]:group-focus-within:bg-layer-2-hover',
116
- 'group-data-[variant=outlined]:bg-layer-2-idle group-data-[variant=filled]:bg-layer-2-hover'
117
- )}
118
- >
119
- {children}
120
- </span>
121
- </label>
147
+ <label htmlFor={htmlFor}>
148
+ <span
149
+ className={twMerge(
150
+ 'rounded-small-1x max-w-full',
151
+ 'group-data-[variant=outlined]:group-focus-within:bg-layer-2-idle group-data-[variant=filled]:group-focus-within:bg-layer-2-hover',
152
+ 'group-data-[variant=outlined]:bg-layer-2-idle group-data-[variant=filled]:bg-layer-2-hover'
153
+ )}
154
+ >
155
+ {children}
156
+ </span>
157
+ </label>
158
+ </Typography>
122
159
  )
123
160
  }}
124
161
  </FieldContext.Consumer>
@@ -134,17 +171,19 @@ export function InputText({
134
171
  maskOptions?: MaskOptions
135
172
  maskType?: InputMaskType
136
173
  }) {
174
+ const { disabled } = useInput()
137
175
  const Component = maskOptions || maskType ? InputMask : 'input'
138
176
 
139
177
  return (
140
178
  <Component
141
179
  {...props}
180
+ disabled={disabled}
142
181
  {...(maskType
143
182
  ? { ...inputMask(maskType) }
144
183
  : maskOptions && { ...maskOptions })}
145
- className={cx(
184
+ className={twMerge(
146
185
  className,
147
- 'peer text-size-body-md text-neutral-dark flex-1 font-medium outline-none',
186
+ 'peer !text-size-body-md text-neutral-dark flex-1 outline-none',
148
187
  'group-has-[div[data-slot=input-left-slot]]:pl-large-1x ml-small-1x'
149
188
  )}
150
189
  placeholder={props.placeholder || ' '}
@@ -158,14 +197,16 @@ export function InputNumber({
158
197
  locales = 'pt-BR',
159
198
  ...props
160
199
  }: InputNumberFormatProps) {
200
+ const { disabled } = useInput()
161
201
  return (
162
202
  <InputNumberFormat
163
203
  {...props}
164
- className={cx(
204
+ className={twMerge(
165
205
  className,
166
- 'peer text-size-body-md text-neutral-dark flex-1 font-medium outline-none',
206
+ 'peer !text-size-body-md text-neutral-dark flex-1 font-medium outline-none',
167
207
  'group-has-[div[data-slot=input-left-slot]]:pl-large-1x ml-small-1x'
168
208
  )}
209
+ disabled={disabled}
169
210
  locales={locales}
170
211
  placeholder={props.placeholder || ' '}
171
212
  />
@@ -176,7 +217,7 @@ export function InputLeftSlot({ className, ...props }: ComponentProps<'div'>) {
176
217
  return (
177
218
  <div
178
219
  {...props}
179
- className={cx(
220
+ className={twMerge(
180
221
  className,
181
222
  'left-small absolute top-1/2 flex -translate-y-1/2 items-center justify-center'
182
223
  )}
@@ -189,7 +230,7 @@ export function InputRightSlot({ className, ...props }: ComponentProps<'div'>) {
189
230
  return (
190
231
  <div
191
232
  {...props}
192
- className={cx(
233
+ className={twMerge(
193
234
  className,
194
235
  'right-small absolute top-1/2 flex -translate-y-1/2 items-center justify-center'
195
236
  )}
@@ -198,14 +239,35 @@ export function InputRightSlot({ className, ...props }: ComponentProps<'div'>) {
198
239
  )
199
240
  }
200
241
 
201
- export function InputMessage({ className, ...props }: ComponentProps<'p'>) {
242
+ export function InputCaption({
243
+ className,
244
+ variant = 'body',
245
+ size = 'sm',
246
+ weight = 'normal',
247
+ ...props
248
+ }: ComponentProps<typeof Typography>) {
249
+ const { hasError } = useInput()
250
+
251
+ const caption = tv({
252
+ base: 'text-size-body-sm mt-small-1x font-normal',
253
+ variants: {
254
+ state: {
255
+ error: 'text-danger-label-active',
256
+ idle: 'text-neutral-dark'
257
+ }
258
+ }
259
+ })
260
+
202
261
  return (
203
- <p
204
- {...props}
205
- className={cx(
206
- 'text-size-body-sm text-neutral-dark font-normal',
262
+ <Typography
263
+ className={caption({
264
+ state: hasError ? 'error' : 'idle',
207
265
  className
208
- )}
266
+ })}
267
+ size={size}
268
+ variant={variant}
269
+ weight={weight}
270
+ {...props}
209
271
  />
210
272
  )
211
273
  }
@@ -214,7 +276,7 @@ export function InputContainer({ className, ...props }: ComponentProps<'div'>) {
214
276
  return (
215
277
  <div
216
278
  {...props}
217
- className={cx(className, 'flex items-center')}
279
+ className={twMerge(className, 'flex items-center')}
218
280
  data-slot="input-content"
219
281
  />
220
282
  )
@@ -227,13 +289,15 @@ export function InputAction({
227
289
  }: ComponentProps<'div'> & {
228
290
  side: 'left' | 'right'
229
291
  }) {
292
+ const { disabled } = useInput()
230
293
  return (
231
294
  <div
232
295
  {...props}
233
- className={cx(
296
+ className={twMerge(
234
297
  className,
235
- 'group h-large-4x flex items-center justify-center'
298
+ 'group h-large-4x flex items-center justify-center data-[disabled=true]:hidden'
236
299
  )}
300
+ data-disabled={disabled}
237
301
  data-side={side}
238
302
  data-slot={`input-${side}-action-slot`}
239
303
  />
@@ -247,7 +311,7 @@ export function InputActionButton({
247
311
  return (
248
312
  <div
249
313
  {...props}
250
- className={cx(
314
+ className={twMerge(
251
315
  className,
252
316
  'h-large-4x w-large-4x bg-layer-2-hover flex cursor-pointer items-center justify-center'
253
317
  )}
@@ -1,11 +1,25 @@
1
- import { memo } from 'react'
2
- import { Horizontal, Vertical, Icon } from '../resources/logo'
1
+ import { memo, lazy, Suspense } from 'react'
3
2
 
4
3
  export interface LogoProps {
5
4
  mode?: 'horizontal' | 'vertical' | 'icon'
6
5
  variant?: 'colorful' | 'orange' | 'purple' | 'white' | 'gray'
7
6
  }
8
7
 
8
+ // Lazy load logo components to reduce initial bundle size
9
+ const LazyLogos = lazy(() => import('../resources/logo'))
10
+
11
+ function LogoFallback({ mode }: { mode?: string }) {
12
+ const dimensions =
13
+ mode === 'icon' ? { width: 24, height: 24 } : { width: 95, height: 26 }
14
+ return (
15
+ <div
16
+ aria-label="Loading logo"
17
+ className="animate-pulse rounded bg-gray-200"
18
+ style={dimensions}
19
+ />
20
+ )
21
+ }
22
+
9
23
  export const Logo = memo(
10
24
  ({ mode = 'horizontal', variant = 'colorful' }: LogoProps) => {
11
25
  const colorToHex = {
@@ -15,24 +29,15 @@ export const Logo = memo(
15
29
  gray: '#6B7280'
16
30
  }
17
31
 
18
- switch (mode) {
19
- case 'icon':
20
- if (variant === 'colorful') {
21
- return <Icon.Colorful />
22
- }
23
- return <Icon.Solid color={colorToHex[variant]} />
24
- case 'vertical':
25
- if (variant === 'colorful') {
26
- return <Vertical.Colorful />
27
- }
28
- return <Vertical.Solid color={colorToHex[variant]} />
29
- case 'horizontal':
30
- default:
31
- if (variant === 'colorful') {
32
- return <Horizontal.Colorful />
33
- }
34
- return <Horizontal.Solid color={colorToHex[variant]} />
35
- }
32
+ return (
33
+ <Suspense fallback={<LogoFallback mode={mode} />}>
34
+ <LazyLogos
35
+ color={variant !== 'colorful' ? colorToHex[variant] : undefined}
36
+ mode={mode}
37
+ variant={variant}
38
+ />
39
+ </Suspense>
40
+ )
36
41
  }
37
42
  )
38
43
 
@@ -6,12 +6,13 @@ import {
6
6
  useCurrentEditor as useRichTextEditor,
7
7
  useEditorState as useRichTextEditorState,
8
8
  useEditor,
9
- type EditorOptions,
10
- Extension
9
+ type EditorOptions
11
10
  } from '@tiptap/react'
11
+ import type { Extension } from '@tiptap/core'
12
12
  import { StarterKit } from '@tiptap/starter-kit'
13
13
  import { Heading, type Level } from '@tiptap/extension-heading'
14
- import { Placeholder } from '@tiptap/extensions'
14
+ // Import specific extension instead of full extensions bundle
15
+ import { Placeholder } from '@tiptap/extension-placeholder'
15
16
  import { useOS } from '../hooks'
16
17
  import { Icon, type IconType } from './icon'
17
18
  import { Button, type ButtonProps } from './button'