@komensal/ui 0.1.0 → 0.2.1
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.
- package/dist/index.d.ts +420 -0
- package/dist/index.js +1710 -0
- package/dist/index.js.map +1 -0
- package/dist/locales/index.d.ts +34 -0
- package/dist/locales/index.js +40 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/styles/index.css +8 -0
- package/package.json +24 -8
- package/src/components/alert.tsx +0 -45
- package/src/components/auth-header.tsx +0 -26
- package/src/components/button.tsx +0 -121
- package/src/components/command.tsx +0 -125
- package/src/components/dialog.tsx +0 -118
- package/src/components/form-fields/index.ts +0 -5
- package/src/components/form-fields/input-field.tsx +0 -131
- package/src/components/form-fields/label-field.tsx +0 -20
- package/src/components/form-fields/map-picker-field.tsx +0 -49
- package/src/components/form-fields/otp-field.tsx +0 -76
- package/src/components/form-fields/select-field.tsx +0 -77
- package/src/components/form.tsx +0 -138
- package/src/components/google-map-picker.tsx +0 -243
- package/src/components/input.tsx +0 -40
- package/src/components/label.tsx +0 -17
- package/src/components/loading.tsx +0 -19
- package/src/components/otp-input.tsx +0 -62
- package/src/components/password-checklist.tsx +0 -37
- package/src/components/popover.tsx +0 -31
- package/src/components/select-input.tsx +0 -236
- package/src/components/select.tsx +0 -143
- package/src/components/spinner.tsx +0 -61
- package/src/components/step-header.tsx +0 -16
- package/src/components/stepper.tsx +0 -69
- package/src/components/toast/index.ts +0 -2
- package/src/components/toast/toast-provider.tsx +0 -123
- package/src/components/toast/toast.tsx +0 -106
- package/src/components/tooltip.tsx +0 -56
- package/src/components/wrapper.tsx +0 -12
- package/src/index.ts +0 -27
- package/src/locales/es/ui.json +0 -28
- package/src/locales/index.ts +0 -7
- package/src/styles/index.css +0 -1
- package/src/types/password.ts +0 -12
- package/src/types/toast.ts +0 -19
- package/src/utils/cn.ts +0 -6
- package/src/utils/index.ts +0 -3
- package/src/utils/styles.ts +0 -19
- package/src/utils/toast-emitter.ts +0 -37
- package/src/vite-env.d.ts +0 -3
- /package/{src → dist}/styles/theme.css +0 -0
package/src/components/form.tsx
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import * as LabelPrimitive from '@radix-ui/react-label'
|
|
2
|
-
import { Slot } from '@radix-ui/react-slot'
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from 'react-hook-form'
|
|
5
|
-
|
|
6
|
-
import { cn } from '../utils'
|
|
7
|
-
import { Label } from './label'
|
|
8
|
-
|
|
9
|
-
const Form = FormProvider
|
|
10
|
-
|
|
11
|
-
interface FormFieldContextValue<
|
|
12
|
-
TFieldValues extends FieldValues = FieldValues,
|
|
13
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
|
14
|
-
> {
|
|
15
|
-
name: TName
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const FormFieldContext = React.createContext<FormFieldContextValue | null>(null)
|
|
19
|
-
|
|
20
|
-
const FormField = <
|
|
21
|
-
TFieldValues extends FieldValues = FieldValues,
|
|
22
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
|
23
|
-
>({
|
|
24
|
-
...props
|
|
25
|
-
}: ControllerProps<TFieldValues, TName>) => {
|
|
26
|
-
return (
|
|
27
|
-
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
28
|
-
<Controller {...props} />
|
|
29
|
-
</FormFieldContext.Provider>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const useFormField = () => {
|
|
34
|
-
const fieldContext = React.useContext(FormFieldContext)
|
|
35
|
-
const itemContext = React.useContext(FormItemContext)
|
|
36
|
-
const { formState, getFieldState } = useFormContext()
|
|
37
|
-
|
|
38
|
-
if (!fieldContext) {
|
|
39
|
-
throw new Error('useFormField must be used within <FormField>')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!itemContext) {
|
|
43
|
-
throw new Error('useFormField must be used within <FormItem>')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const fieldState = getFieldState(fieldContext.name, formState)
|
|
47
|
-
|
|
48
|
-
const { id } = itemContext
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
formDescriptionId: `${id}-form-item-description`,
|
|
52
|
-
formItemId: `${id}-form-item`,
|
|
53
|
-
formMessageId: `${id}-form-item-message`,
|
|
54
|
-
id,
|
|
55
|
-
name: fieldContext.name,
|
|
56
|
-
...fieldState
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface FormItemContextValue {
|
|
61
|
-
id: string
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const FormItemContext = React.createContext<FormItemContextValue | null>(null)
|
|
65
|
-
|
|
66
|
-
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
67
|
-
({ className, ...props }, ref) => {
|
|
68
|
-
const id = React.useId()
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<FormItemContext.Provider value={{ id }}>
|
|
72
|
-
<div className={cn('space-y-1', className)} ref={ref} {...props} />
|
|
73
|
-
</FormItemContext.Provider>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
)
|
|
77
|
-
FormItem.displayName = 'FormItem'
|
|
78
|
-
|
|
79
|
-
const FormLabel = React.forwardRef<
|
|
80
|
-
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
81
|
-
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
|
82
|
-
>(({ className, ...props }, ref) => {
|
|
83
|
-
const { error, formItemId } = useFormField()
|
|
84
|
-
|
|
85
|
-
return <Label className={cn(error && 'text-destructive', className)} htmlFor={formItemId} ref={ref} {...props} />
|
|
86
|
-
})
|
|
87
|
-
FormLabel.displayName = 'FormLabel'
|
|
88
|
-
|
|
89
|
-
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
|
|
90
|
-
({ ...props }, ref) => {
|
|
91
|
-
const { error, formDescriptionId, formItemId, formMessageId } = useFormField()
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<Slot
|
|
95
|
-
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
|
96
|
-
aria-invalid={!!error}
|
|
97
|
-
id={formItemId}
|
|
98
|
-
ref={ref}
|
|
99
|
-
{...props}
|
|
100
|
-
/>
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
)
|
|
104
|
-
FormControl.displayName = 'FormControl'
|
|
105
|
-
|
|
106
|
-
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
107
|
-
({ className, ...props }, ref) => {
|
|
108
|
-
const { formDescriptionId } = useFormField()
|
|
109
|
-
|
|
110
|
-
return <p className={cn('text-muted text-[0.8rem]', className)} id={formDescriptionId} ref={ref} {...props} />
|
|
111
|
-
}
|
|
112
|
-
)
|
|
113
|
-
FormDescription.displayName = 'FormDescription'
|
|
114
|
-
|
|
115
|
-
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
116
|
-
({ children, className, ...props }, ref) => {
|
|
117
|
-
const { error, formMessageId } = useFormField()
|
|
118
|
-
const body = error?.message ? String(error.message) : children
|
|
119
|
-
|
|
120
|
-
if (!body) {
|
|
121
|
-
return null
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<p
|
|
126
|
-
className={cn('text-destructive text-[0.8rem] font-medium', className)}
|
|
127
|
-
id={formMessageId}
|
|
128
|
-
ref={ref}
|
|
129
|
-
{...props}
|
|
130
|
-
>
|
|
131
|
-
{body}
|
|
132
|
-
</p>
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
)
|
|
136
|
-
FormMessage.displayName = 'FormMessage'
|
|
137
|
-
|
|
138
|
-
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField }
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import type { MapMouseEvent } from '@vis.gl/react-google-maps'
|
|
2
|
-
|
|
3
|
-
import { AdvancedMarker, APIProvider, Map, useMap, useMapsLibrary } from '@vis.gl/react-google-maps'
|
|
4
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
5
|
-
|
|
6
|
-
import { cn, inputBase, inputFocus } from '../utils'
|
|
7
|
-
|
|
8
|
-
const DEFAULT_CENTER = { lat: 4.711, lng: -74.0721 }
|
|
9
|
-
const PICKER_MAP_ID = 'komensal-restaurant-map'
|
|
10
|
-
const SELECTED_ZOOM = 15
|
|
11
|
-
const DEBOUNCE_MS = 300
|
|
12
|
-
|
|
13
|
-
export interface GoogleMapPickerProps {
|
|
14
|
-
centerOn?: google.maps.LatLngLiteral
|
|
15
|
-
disabled?: boolean
|
|
16
|
-
hasError?: boolean
|
|
17
|
-
locationQuery?: string
|
|
18
|
-
locationZoom?: number
|
|
19
|
-
onChange: (address: string) => void
|
|
20
|
-
placeholder?: string
|
|
21
|
-
value: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const CITY_ZOOM = 12
|
|
25
|
-
|
|
26
|
-
const MapPickerContent = ({
|
|
27
|
-
centerOn,
|
|
28
|
-
disabled,
|
|
29
|
-
hasError,
|
|
30
|
-
locationQuery,
|
|
31
|
-
locationZoom,
|
|
32
|
-
onChange,
|
|
33
|
-
placeholder,
|
|
34
|
-
value
|
|
35
|
-
}: GoogleMapPickerProps) => {
|
|
36
|
-
const placesLib = useMapsLibrary('places')
|
|
37
|
-
const geocodingLib = useMapsLibrary('geocoding')
|
|
38
|
-
const map = useMap(PICKER_MAP_ID)
|
|
39
|
-
const onChangeRef = useRef(onChange)
|
|
40
|
-
const geocoder = useMemo<google.maps.Geocoder | null>(
|
|
41
|
-
() => (geocodingLib ? new geocodingLib.Geocoder() : null),
|
|
42
|
-
[geocodingLib]
|
|
43
|
-
)
|
|
44
|
-
const debounceRef = useRef<null | ReturnType<typeof setTimeout>>(null)
|
|
45
|
-
|
|
46
|
-
const [inputValue, setInputValue] = useState(value)
|
|
47
|
-
const [suggestions, setSuggestions] = useState<google.maps.places.AutocompleteSuggestion[]>([])
|
|
48
|
-
const [showDropdown, setShowDropdown] = useState(false)
|
|
49
|
-
const [sessionToken, setSessionToken] = useState<google.maps.places.AutocompleteSessionToken | undefined>()
|
|
50
|
-
const [position, setPosition] = useState<google.maps.LatLngLiteral | null>(null)
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
onChangeRef.current = onChange
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
setInputValue(value)
|
|
58
|
-
}, [value])
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (map && centerOn) {
|
|
62
|
-
map.panTo(centerOn)
|
|
63
|
-
map.setZoom(CITY_ZOOM)
|
|
64
|
-
}
|
|
65
|
-
}, [map, centerOn])
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (map && position) {
|
|
69
|
-
map.panTo(position)
|
|
70
|
-
map.setZoom(SELECTED_ZOOM)
|
|
71
|
-
}
|
|
72
|
-
}, [map, position])
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (!placesLib) return
|
|
76
|
-
setSessionToken(new placesLib.AutocompleteSessionToken())
|
|
77
|
-
}, [placesLib])
|
|
78
|
-
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
if (!geocoder || !locationQuery || !map) return
|
|
81
|
-
const g = geocoder
|
|
82
|
-
const ctrl = { cancelled: false }
|
|
83
|
-
void (async () => {
|
|
84
|
-
try {
|
|
85
|
-
const { results } = await g.geocode({ address: locationQuery })
|
|
86
|
-
if (ctrl.cancelled || results.length === 0) return
|
|
87
|
-
const loc = results[0].geometry.location
|
|
88
|
-
map.panTo({ lat: loc.lat(), lng: loc.lng() })
|
|
89
|
-
map.setZoom(locationZoom ?? CITY_ZOOM)
|
|
90
|
-
} catch {
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
})()
|
|
94
|
-
return () => {
|
|
95
|
-
ctrl.cancelled = true
|
|
96
|
-
}
|
|
97
|
-
}, [geocoder, locationQuery, locationZoom, map])
|
|
98
|
-
|
|
99
|
-
const fetchSuggestions = useCallback(
|
|
100
|
-
async (input: string) => {
|
|
101
|
-
if (!placesLib || input.length < 2) {
|
|
102
|
-
setSuggestions([])
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
try {
|
|
106
|
-
const result = await placesLib.AutocompleteSuggestion.fetchAutocompleteSuggestions({
|
|
107
|
-
input,
|
|
108
|
-
sessionToken
|
|
109
|
-
})
|
|
110
|
-
setSuggestions(result.suggestions)
|
|
111
|
-
setShowDropdown(true)
|
|
112
|
-
} catch {
|
|
113
|
-
setSuggestions([])
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
[placesLib, sessionToken]
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
const reverseGeocode = useCallback(
|
|
120
|
-
async (latLng: google.maps.LatLngLiteral) => {
|
|
121
|
-
if (!geocoder) return
|
|
122
|
-
try {
|
|
123
|
-
const { results } = await geocoder.geocode({ location: latLng })
|
|
124
|
-
if (results.length > 0) {
|
|
125
|
-
const address = results[0].formatted_address
|
|
126
|
-
setInputValue(address)
|
|
127
|
-
onChangeRef.current(address)
|
|
128
|
-
}
|
|
129
|
-
} catch {
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
[geocoder]
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
137
|
-
const val = e.target.value
|
|
138
|
-
setInputValue(val)
|
|
139
|
-
onChangeRef.current(val)
|
|
140
|
-
|
|
141
|
-
if (debounceRef.current) clearTimeout(debounceRef.current)
|
|
142
|
-
debounceRef.current = setTimeout(() => void fetchSuggestions(val), DEBOUNCE_MS)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const handleSelect = async (suggestion: google.maps.places.AutocompleteSuggestion) => {
|
|
146
|
-
const prediction = suggestion.placePrediction
|
|
147
|
-
if (!prediction) return
|
|
148
|
-
|
|
149
|
-
const place = prediction.toPlace()
|
|
150
|
-
await place.fetchFields({ fields: ['formattedAddress', 'location'] })
|
|
151
|
-
|
|
152
|
-
const address = place.formattedAddress ?? prediction.text.text
|
|
153
|
-
setInputValue(address)
|
|
154
|
-
onChangeRef.current(address)
|
|
155
|
-
setSuggestions([])
|
|
156
|
-
setShowDropdown(false)
|
|
157
|
-
|
|
158
|
-
if (place.location) {
|
|
159
|
-
setPosition({ lat: place.location.lat(), lng: place.location.lng() })
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (placesLib) setSessionToken(new placesLib.AutocompleteSessionToken())
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const handleBlur = () => {
|
|
166
|
-
setTimeout(() => setShowDropdown(false), 150)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const handleMapClick = useCallback(
|
|
170
|
-
(e: MapMouseEvent) => {
|
|
171
|
-
if (disabled || !e.detail.latLng) return
|
|
172
|
-
const latLng = e.detail.latLng
|
|
173
|
-
setPosition(latLng)
|
|
174
|
-
void reverseGeocode(latLng)
|
|
175
|
-
},
|
|
176
|
-
[disabled, reverseGeocode]
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
const handleMarkerDragEnd = useCallback(
|
|
180
|
-
(e: google.maps.MapMouseEvent) => {
|
|
181
|
-
const latLng = e.latLng
|
|
182
|
-
if (!latLng) return
|
|
183
|
-
const pos = { lat: latLng.lat(), lng: latLng.lng() }
|
|
184
|
-
setPosition(pos)
|
|
185
|
-
void reverseGeocode(pos)
|
|
186
|
-
},
|
|
187
|
-
[reverseGeocode]
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
return (
|
|
191
|
-
<div className='flex flex-col gap-3'>
|
|
192
|
-
<div className='relative'>
|
|
193
|
-
<input
|
|
194
|
-
className={cn(inputBase, inputFocus, hasError && 'border-destructive')}
|
|
195
|
-
disabled={disabled}
|
|
196
|
-
onBlur={handleBlur}
|
|
197
|
-
onChange={handleInputChange}
|
|
198
|
-
onFocus={() => suggestions.length > 0 && setShowDropdown(true)}
|
|
199
|
-
placeholder={placeholder}
|
|
200
|
-
type='text'
|
|
201
|
-
value={inputValue}
|
|
202
|
-
/>
|
|
203
|
-
|
|
204
|
-
{showDropdown && suggestions.length > 0 && (
|
|
205
|
-
<ul className='border-border bg-surface absolute z-50 mt-1 w-full overflow-hidden rounded-[10px] border shadow-md'>
|
|
206
|
-
{suggestions.map((suggestion) => (
|
|
207
|
-
<li key={suggestion.placePrediction?.placeId ?? suggestion.placePrediction?.text.text}>
|
|
208
|
-
<button
|
|
209
|
-
className='text-foreground hover:bg-hover w-full px-4 py-3 text-left text-sm transition-colors'
|
|
210
|
-
onClick={() => void handleSelect(suggestion)}
|
|
211
|
-
type='button'
|
|
212
|
-
>
|
|
213
|
-
{suggestion.placePrediction?.text.text ?? ''}
|
|
214
|
-
</button>
|
|
215
|
-
</li>
|
|
216
|
-
))}
|
|
217
|
-
</ul>
|
|
218
|
-
)}
|
|
219
|
-
</div>
|
|
220
|
-
|
|
221
|
-
<div className='border-border h-59 overflow-hidden rounded-[10px] border md:h-87.5'>
|
|
222
|
-
<Map
|
|
223
|
-
defaultCenter={DEFAULT_CENTER}
|
|
224
|
-
defaultZoom={5}
|
|
225
|
-
disableDefaultUI
|
|
226
|
-
gestureHandling='cooperative'
|
|
227
|
-
id={PICKER_MAP_ID}
|
|
228
|
-
mapId={(import.meta.env.VITE_GOOGLE_MAPS_MAP_ID as string | undefined) ?? 'DEMO_MAP_ID'}
|
|
229
|
-
onClick={handleMapClick}
|
|
230
|
-
style={{ height: '100%', width: '100%' }}
|
|
231
|
-
>
|
|
232
|
-
{position && <AdvancedMarker draggable={!disabled} onDragEnd={handleMarkerDragEnd} position={position} />}
|
|
233
|
-
</Map>
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export const GoogleMapPicker = (props: GoogleMapPickerProps) => (
|
|
240
|
-
<APIProvider apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ''} language='es' region='CO'>
|
|
241
|
-
<MapPickerContent {...props} />
|
|
242
|
-
</APIProvider>
|
|
243
|
-
)
|
package/src/components/input.tsx
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
|
|
3
|
-
import { cn, inputBase, inputFocus } from '../utils'
|
|
4
|
-
|
|
5
|
-
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
|
|
6
|
-
|
|
7
|
-
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
|
|
8
|
-
return (
|
|
9
|
-
<input
|
|
10
|
-
className={cn(
|
|
11
|
-
inputBase,
|
|
12
|
-
inputFocus,
|
|
13
|
-
'file:border-0 file:bg-transparent file:text-base file:font-medium',
|
|
14
|
-
className
|
|
15
|
-
)}
|
|
16
|
-
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
17
|
-
const key = e.key
|
|
18
|
-
const allowedKeys = ['Backspace', 'Tab', 'Enter', 'Escape', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown']
|
|
19
|
-
if (allowedKeys.includes(key)) {
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
if (!(key >= '0' && key <= '9') && type === 'number') {
|
|
23
|
-
e.preventDefault()
|
|
24
|
-
}
|
|
25
|
-
if (type === 'onlyText') {
|
|
26
|
-
const regex = /^[a-zA-ZáéíóúÁÉÍÓÚñÑ ]*$/
|
|
27
|
-
if (!regex.test(key)) {
|
|
28
|
-
e.preventDefault()
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}}
|
|
32
|
-
ref={ref}
|
|
33
|
-
type={type}
|
|
34
|
-
{...props}
|
|
35
|
-
/>
|
|
36
|
-
)
|
|
37
|
-
})
|
|
38
|
-
Input.displayName = 'Input'
|
|
39
|
-
|
|
40
|
-
export { Input }
|
package/src/components/label.tsx
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as LabelPrimitive from '@radix-ui/react-label'
|
|
2
|
-
import { cva, type VariantProps } from 'class-variance-authority'
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
|
|
5
|
-
import { cn } from '../utils'
|
|
6
|
-
|
|
7
|
-
const labelVariants = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70')
|
|
8
|
-
|
|
9
|
-
const Label = React.forwardRef<
|
|
10
|
-
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
11
|
-
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
|
|
12
|
-
>(({ className, ...props }, ref) => (
|
|
13
|
-
<LabelPrimitive.Root className={cn(labelVariants(), className)} ref={ref} {...props} />
|
|
14
|
-
))
|
|
15
|
-
Label.displayName = LabelPrimitive.Root.displayName
|
|
16
|
-
|
|
17
|
-
export { Label }
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { cn } from '../utils'
|
|
2
|
-
import { Spinner } from './spinner'
|
|
3
|
-
|
|
4
|
-
interface LoadingProps {
|
|
5
|
-
className?: string
|
|
6
|
-
isSpinner?: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function Loading({ className = '', isSpinner = true }: Readonly<LoadingProps>) {
|
|
10
|
-
return (
|
|
11
|
-
<div className={cn('relative flex min-h-screen items-center justify-center gap-6', className)}>
|
|
12
|
-
{isSpinner && (
|
|
13
|
-
<div className='absolute right-0 bottom-0 m-10'>
|
|
14
|
-
<Spinner />
|
|
15
|
-
</div>
|
|
16
|
-
)}
|
|
17
|
-
</div>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { OTPInput, OTPInputContext } from 'input-otp'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
|
|
4
|
-
import { cn } from '../utils'
|
|
5
|
-
|
|
6
|
-
function InputOTP({
|
|
7
|
-
className,
|
|
8
|
-
containerClassName,
|
|
9
|
-
...props
|
|
10
|
-
}: React.ComponentProps<typeof OTPInput> & {
|
|
11
|
-
containerClassName?: string
|
|
12
|
-
}) {
|
|
13
|
-
return (
|
|
14
|
-
<OTPInput
|
|
15
|
-
className={cn('disabled:cursor-not-allowed', className)}
|
|
16
|
-
containerClassName={cn('flex items-center gap-2 has-disabled:opacity-50', containerClassName)}
|
|
17
|
-
data-slot='input-otp'
|
|
18
|
-
{...props}
|
|
19
|
-
/>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function InputOTPGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
|
24
|
-
return <div className={cn('flex items-center gap-2', className)} data-slot='input-otp-group' {...props} />
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function InputOTPSlot({
|
|
28
|
-
className,
|
|
29
|
-
index,
|
|
30
|
-
...props
|
|
31
|
-
}: React.ComponentProps<'div'> & {
|
|
32
|
-
index: number
|
|
33
|
-
}) {
|
|
34
|
-
const inputOTPContext = React.useContext(OTPInputContext) as {
|
|
35
|
-
slots: { char: string | null; hasFakeCaret: boolean; isActive: boolean }[]
|
|
36
|
-
}
|
|
37
|
-
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] ?? {}
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div
|
|
41
|
-
className={cn(
|
|
42
|
-
'border-border text-foreground relative flex h-14 w-12 items-center justify-center rounded-[10px] border text-xl font-medium transition-all outline-none',
|
|
43
|
-
char ? 'bg-brand-subtle' : 'bg-transparent',
|
|
44
|
-
'data-[active=true]:border-primary data-[active=true]:ring-ring data-[active=true]:z-10 data-[active=true]:ring-2',
|
|
45
|
-
'aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/40',
|
|
46
|
-
className
|
|
47
|
-
)}
|
|
48
|
-
data-active={isActive}
|
|
49
|
-
data-slot='input-otp-slot'
|
|
50
|
-
{...props}
|
|
51
|
-
>
|
|
52
|
-
{char}
|
|
53
|
-
{hasFakeCaret && (
|
|
54
|
-
<div className='pointer-events-none absolute inset-0 flex items-center justify-center'>
|
|
55
|
-
<div className='animate-caret-blink bg-foreground h-6 w-px duration-1000' />
|
|
56
|
-
</div>
|
|
57
|
-
)}
|
|
58
|
-
</div>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export { InputOTP, InputOTPGroup, InputOTPSlot }
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { IoMdCheckmarkCircle, IoMdCloseCircle } from 'react-icons/io'
|
|
3
|
-
|
|
4
|
-
import { PasswordRule } from '../types/password'
|
|
5
|
-
import { cn } from '../utils'
|
|
6
|
-
|
|
7
|
-
interface PasswordChecklistProps {
|
|
8
|
-
className?: string
|
|
9
|
-
rules: PasswordRule[]
|
|
10
|
-
value: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const PasswordChecklist = ({ className, rules, value }: PasswordChecklistProps) => {
|
|
14
|
-
if (rules.length === 0) return null
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<ul className={cn('flex flex-col gap-1', className)}>
|
|
18
|
-
{rules.map((rule) => {
|
|
19
|
-
const isMet = rule.test(value)
|
|
20
|
-
const Icon = (isMet ? IoMdCheckmarkCircle : IoMdCloseCircle) as React.FC<React.SVGProps<SVGSVGElement>>
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<li
|
|
24
|
-
className={cn(
|
|
25
|
-
'flex items-center gap-2 text-xs transition-colors',
|
|
26
|
-
isMet ? 'text-success' : 'text-inactive-text'
|
|
27
|
-
)}
|
|
28
|
-
key={rule.key}
|
|
29
|
-
>
|
|
30
|
-
<Icon aria-hidden='true' className='h-4 w-4 shrink-0' />
|
|
31
|
-
<span>{rule.label}</span>
|
|
32
|
-
</li>
|
|
33
|
-
)
|
|
34
|
-
})}
|
|
35
|
-
</ul>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
|
2
|
-
import * as React from 'react'
|
|
3
|
-
|
|
4
|
-
import { cn } from '../utils'
|
|
5
|
-
|
|
6
|
-
const Popover = PopoverPrimitive.Root
|
|
7
|
-
|
|
8
|
-
const PopoverTrigger = PopoverPrimitive.Trigger
|
|
9
|
-
|
|
10
|
-
const PopoverAnchor = PopoverPrimitive.Anchor
|
|
11
|
-
|
|
12
|
-
const PopoverContent = React.forwardRef<
|
|
13
|
-
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
14
|
-
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
15
|
-
>(({ align = 'center', className, sideOffset = 4, ...props }, ref) => (
|
|
16
|
-
<PopoverPrimitive.Portal>
|
|
17
|
-
<PopoverPrimitive.Content
|
|
18
|
-
align={align}
|
|
19
|
-
className={cn(
|
|
20
|
-
'text-foreground bg-surface data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
|
|
21
|
-
className
|
|
22
|
-
)}
|
|
23
|
-
ref={ref}
|
|
24
|
-
sideOffset={sideOffset}
|
|
25
|
-
{...props}
|
|
26
|
-
/>
|
|
27
|
-
</PopoverPrimitive.Portal>
|
|
28
|
-
))
|
|
29
|
-
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
|
30
|
-
|
|
31
|
-
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }
|