@komensal/ui 0.1.0 → 0.2.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.
- 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/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/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/index.css +0 -0
- /package/{src → dist}/styles/theme.css +0 -0
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { ChevronDown, ChevronUp, Clock, XIcon } from 'lucide-react'
|
|
2
|
-
import React, { cloneElement, JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
3
|
-
import { FieldError, Noop } from 'react-hook-form'
|
|
4
|
-
import { useTranslation } from 'react-i18next'
|
|
5
|
-
|
|
6
|
-
import { cn, inputBase, inputDisabled, inputFocus, inputPlaceholder } from '../utils'
|
|
7
|
-
import { Button } from './button'
|
|
8
|
-
import { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from './command'
|
|
9
|
-
import { Input } from './input'
|
|
10
|
-
import { Popover, PopoverContent, PopoverTrigger } from './popover'
|
|
11
|
-
import { Spinner } from './spinner'
|
|
12
|
-
|
|
13
|
-
interface SelectInputProps<Label extends string, Value extends string> {
|
|
14
|
-
ableDeleteOption?: boolean
|
|
15
|
-
className?: string
|
|
16
|
-
disabled?: boolean
|
|
17
|
-
error?: FieldError
|
|
18
|
-
items?: null | Record<Label | Value, number | string>[]
|
|
19
|
-
labelKey?: string
|
|
20
|
-
leftIcon?: JSX.Element
|
|
21
|
-
loading?: boolean
|
|
22
|
-
onBlur?: Noop
|
|
23
|
-
onChange?: (...event: unknown[]) => void
|
|
24
|
-
onValueChangeCallBack?: (item: { label: string; value: string }) => void
|
|
25
|
-
placeholder?: string
|
|
26
|
-
placeholderClassName?: string
|
|
27
|
-
search?: boolean
|
|
28
|
-
timeField?: boolean
|
|
29
|
-
value?: string
|
|
30
|
-
valueKey?: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const SelectInput = <Label extends string, Value extends string>(props: SelectInputProps<Label, Value>) => {
|
|
34
|
-
const { t } = useTranslation('ui')
|
|
35
|
-
const {
|
|
36
|
-
ableDeleteOption,
|
|
37
|
-
className,
|
|
38
|
-
disabled = false,
|
|
39
|
-
error,
|
|
40
|
-
items,
|
|
41
|
-
labelKey = 'label',
|
|
42
|
-
leftIcon,
|
|
43
|
-
loading = false,
|
|
44
|
-
onBlur,
|
|
45
|
-
onChange,
|
|
46
|
-
onValueChangeCallBack = () => undefined,
|
|
47
|
-
placeholder,
|
|
48
|
-
placeholderClassName = '',
|
|
49
|
-
search = false,
|
|
50
|
-
timeField = false,
|
|
51
|
-
value = '',
|
|
52
|
-
valueKey = 'value'
|
|
53
|
-
} = props
|
|
54
|
-
|
|
55
|
-
const resolvedPlaceholder = placeholder ?? t('selectInput.placeholder')
|
|
56
|
-
|
|
57
|
-
const divRef = useRef<HTMLButtonElement | null>(null)
|
|
58
|
-
const [open, setOpen] = useState<boolean>(false)
|
|
59
|
-
const [width, setWidth] = useState<number>(0)
|
|
60
|
-
const [query, setQuery] = useState<string>('')
|
|
61
|
-
const [valueItem, setValueItem] = useState(value)
|
|
62
|
-
|
|
63
|
-
const normalizeString = (str: string): string => str.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()
|
|
64
|
-
|
|
65
|
-
const normalizedItems = useMemo(() => {
|
|
66
|
-
if (!items) return [] as Record<Label | Value, string>[]
|
|
67
|
-
return items.map((it) => ({
|
|
68
|
-
...it,
|
|
69
|
-
[labelKey]: String(it[labelKey as Label]),
|
|
70
|
-
[valueKey]: String(it[valueKey as Value])
|
|
71
|
-
})) as Record<Label | Value, string>[]
|
|
72
|
-
}, [items, labelKey, valueKey])
|
|
73
|
-
|
|
74
|
-
const filteredOptions = useMemo(() => {
|
|
75
|
-
if (!query) return normalizedItems
|
|
76
|
-
return normalizedItems.filter((el) =>
|
|
77
|
-
normalizeString(el[labelKey as Label] as string).includes(normalizeString(query))
|
|
78
|
-
)
|
|
79
|
-
}, [normalizedItems, labelKey, query])
|
|
80
|
-
|
|
81
|
-
const onValueChange = useCallback(
|
|
82
|
-
(val: string) => {
|
|
83
|
-
if (!normalizedItems.length) return
|
|
84
|
-
const item = normalizedItems.find((i) => i[valueKey as Value] === val)
|
|
85
|
-
if (item) {
|
|
86
|
-
onValueChangeCallBack({
|
|
87
|
-
label: item[labelKey as Label],
|
|
88
|
-
value: item[valueKey as Value]
|
|
89
|
-
})
|
|
90
|
-
setValueItem(val)
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
[normalizedItems, labelKey, valueKey, onValueChangeCallBack]
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
const buttonClassName = useMemo(
|
|
97
|
-
() =>
|
|
98
|
-
cn(
|
|
99
|
-
inputBase,
|
|
100
|
-
inputFocus,
|
|
101
|
-
'hover:bg-surface',
|
|
102
|
-
disabled && inputDisabled,
|
|
103
|
-
error && 'border-destructive',
|
|
104
|
-
className
|
|
105
|
-
),
|
|
106
|
-
[disabled, error, className]
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
const updateWidth = () => {
|
|
110
|
-
if (divRef.current) {
|
|
111
|
-
const { width } = divRef.current.getBoundingClientRect()
|
|
112
|
-
setWidth(width)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
updateWidth()
|
|
118
|
-
window.addEventListener('resize', updateWidth)
|
|
119
|
-
return () => window.removeEventListener('resize', updateWidth)
|
|
120
|
-
}, [])
|
|
121
|
-
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
setValueItem(value)
|
|
124
|
-
}, [value])
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<div className='flex flex-col gap-1'>
|
|
128
|
-
<div className='relative flex flex-col justify-center'>
|
|
129
|
-
<Popover onOpenChange={setOpen} open={open}>
|
|
130
|
-
<PopoverTrigger asChild>
|
|
131
|
-
<Button
|
|
132
|
-
aria-expanded={open}
|
|
133
|
-
className={buttonClassName}
|
|
134
|
-
disabled={disabled}
|
|
135
|
-
isLoading={loading}
|
|
136
|
-
onBlur={onBlur}
|
|
137
|
-
ref={divRef}
|
|
138
|
-
role='combobox'
|
|
139
|
-
variant='primary'
|
|
140
|
-
>
|
|
141
|
-
{loading ? (
|
|
142
|
-
<>
|
|
143
|
-
{t('selectInput.loading')} <Spinner />
|
|
144
|
-
</>
|
|
145
|
-
) : (
|
|
146
|
-
<div className='relative flex h-5 w-full items-center'>
|
|
147
|
-
<div className='flex w-full max-w-[90%] items-center truncate'>
|
|
148
|
-
{leftIcon && (
|
|
149
|
-
<span className='mr-2 shrink-0'>{cloneElement(leftIcon, { size: 20, strokeWidth: 1 })}</span>
|
|
150
|
-
)}
|
|
151
|
-
{valueItem ? (
|
|
152
|
-
filteredOptions.find((i) => i[valueKey as Value] === valueItem)?.[labelKey as Label]
|
|
153
|
-
) : (
|
|
154
|
-
<span className={cn(inputPlaceholder, placeholderClassName)}>{resolvedPlaceholder}</span>
|
|
155
|
-
)}
|
|
156
|
-
</div>
|
|
157
|
-
{timeField ? (
|
|
158
|
-
<div className='absolute top-0.5 right-0'>
|
|
159
|
-
<Clock className='ml-2 h-4 w-4 shrink-0 opacity-50' />
|
|
160
|
-
</div>
|
|
161
|
-
) : (
|
|
162
|
-
<div className='absolute top-0.5 right-0 flex gap-2'>
|
|
163
|
-
{ableDeleteOption && (
|
|
164
|
-
<XIcon
|
|
165
|
-
className='z-100 h-4 w-4 shrink-0 opacity-50'
|
|
166
|
-
onClick={(e: React.MouseEvent<SVGSVGElement>) => {
|
|
167
|
-
e.preventDefault()
|
|
168
|
-
e.stopPropagation()
|
|
169
|
-
onChange?.('')
|
|
170
|
-
onValueChange('')
|
|
171
|
-
setValueItem('')
|
|
172
|
-
setQuery('')
|
|
173
|
-
setOpen(false)
|
|
174
|
-
}}
|
|
175
|
-
/>
|
|
176
|
-
)}
|
|
177
|
-
{!open ? (
|
|
178
|
-
<ChevronDown className='ml-2 h-4 w-4 shrink-0 opacity-50' />
|
|
179
|
-
) : (
|
|
180
|
-
<ChevronUp className='ml-2 h-4 w-4 shrink-0 opacity-50' />
|
|
181
|
-
)}
|
|
182
|
-
</div>
|
|
183
|
-
)}
|
|
184
|
-
</div>
|
|
185
|
-
)}
|
|
186
|
-
</Button>
|
|
187
|
-
</PopoverTrigger>
|
|
188
|
-
<PopoverContent
|
|
189
|
-
className='bg-surface border-border z-100 rounded-sm p-1'
|
|
190
|
-
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
191
|
-
style={{ maxWidth: 'auto', minWidth: `${width}px`, width: `${width}px` }}
|
|
192
|
-
>
|
|
193
|
-
<Command className='flex w-full flex-col gap-2'>
|
|
194
|
-
{search && (
|
|
195
|
-
<Input
|
|
196
|
-
className='mb-1 rounded-sm'
|
|
197
|
-
onChange={(e) => setQuery(e.target.value)}
|
|
198
|
-
placeholder={t('selectInput.search')}
|
|
199
|
-
value={query}
|
|
200
|
-
/>
|
|
201
|
-
)}
|
|
202
|
-
<CommandEmpty className='text-center'>{t('selectInput.noResults')}</CommandEmpty>
|
|
203
|
-
<CommandList>
|
|
204
|
-
<CommandGroup className='p-0'>
|
|
205
|
-
{filteredOptions.map((item) => {
|
|
206
|
-
const labelValue = item[labelKey as Label] as string
|
|
207
|
-
const valueValue = item[valueKey as Value] as string
|
|
208
|
-
|
|
209
|
-
return (
|
|
210
|
-
<CommandItem
|
|
211
|
-
className={cn(
|
|
212
|
-
'hover:bg-hover mt-0.5 mb-0.5 flex cursor-pointer items-center gap-2 rounded-sm py-2 pl-3',
|
|
213
|
-
valueValue === value && 'bg-inactive'
|
|
214
|
-
)}
|
|
215
|
-
key={valueValue}
|
|
216
|
-
onSelect={(currentValue: string) => {
|
|
217
|
-
onChange?.(currentValue)
|
|
218
|
-
onValueChange(currentValue)
|
|
219
|
-
setOpen(false)
|
|
220
|
-
setTimeout(() => setQuery(''), 100)
|
|
221
|
-
}}
|
|
222
|
-
value={valueValue}
|
|
223
|
-
>
|
|
224
|
-
{labelValue.trim()}
|
|
225
|
-
</CommandItem>
|
|
226
|
-
)
|
|
227
|
-
})}
|
|
228
|
-
</CommandGroup>
|
|
229
|
-
</CommandList>
|
|
230
|
-
</Command>
|
|
231
|
-
</PopoverContent>
|
|
232
|
-
</Popover>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
)
|
|
236
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { CaretSortIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'
|
|
2
|
-
import * as SelectPrimitive from '@radix-ui/react-select'
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
|
|
5
|
-
import { cn } from '../utils'
|
|
6
|
-
|
|
7
|
-
const Select = SelectPrimitive.Root
|
|
8
|
-
|
|
9
|
-
const SelectGroup = SelectPrimitive.Group
|
|
10
|
-
|
|
11
|
-
const SelectValue = SelectPrimitive.Value
|
|
12
|
-
|
|
13
|
-
const SelectTrigger = React.forwardRef<
|
|
14
|
-
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
15
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
16
|
-
>(({ children, className, ...props }, ref) => (
|
|
17
|
-
<SelectPrimitive.Trigger
|
|
18
|
-
className={cn(
|
|
19
|
-
'placeholder:text-placeholder border-border ring-offset-surface focus:ring-ring flex h-9 w-full items-center justify-between rounded-xl border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-sm focus:ring-0 focus:outline-none disabled:cursor-not-allowed [&>span]:line-clamp-1',
|
|
20
|
-
className
|
|
21
|
-
)}
|
|
22
|
-
ref={ref}
|
|
23
|
-
{...props}
|
|
24
|
-
>
|
|
25
|
-
{children}
|
|
26
|
-
<SelectPrimitive.Icon asChild>
|
|
27
|
-
<CaretSortIcon className='h-4 w-4 opacity-50' />
|
|
28
|
-
</SelectPrimitive.Icon>
|
|
29
|
-
</SelectPrimitive.Trigger>
|
|
30
|
-
))
|
|
31
|
-
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
|
32
|
-
|
|
33
|
-
const SelectScrollUpButton = React.forwardRef<
|
|
34
|
-
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
35
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
36
|
-
>(({ className, ...props }, ref) => (
|
|
37
|
-
<SelectPrimitive.ScrollUpButton
|
|
38
|
-
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
|
39
|
-
ref={ref}
|
|
40
|
-
{...props}
|
|
41
|
-
>
|
|
42
|
-
<ChevronUpIcon />
|
|
43
|
-
</SelectPrimitive.ScrollUpButton>
|
|
44
|
-
))
|
|
45
|
-
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
|
46
|
-
|
|
47
|
-
const SelectScrollDownButton = React.forwardRef<
|
|
48
|
-
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
49
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
50
|
-
>(({ className, ...props }, ref) => (
|
|
51
|
-
<SelectPrimitive.ScrollDownButton
|
|
52
|
-
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
|
53
|
-
ref={ref}
|
|
54
|
-
{...props}
|
|
55
|
-
>
|
|
56
|
-
<ChevronDownIcon />
|
|
57
|
-
</SelectPrimitive.ScrollDownButton>
|
|
58
|
-
))
|
|
59
|
-
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
|
|
60
|
-
|
|
61
|
-
const SelectContent = React.forwardRef<
|
|
62
|
-
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
63
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
64
|
-
>(({ children, className, position = 'popper', ...props }, ref) => (
|
|
65
|
-
<SelectPrimitive.Portal>
|
|
66
|
-
<SelectPrimitive.Content
|
|
67
|
-
className={cn(
|
|
68
|
-
'border-border 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 relative z-50 max-h-96 min-w-32 overflow-hidden rounded-lg border shadow-md',
|
|
69
|
-
position === 'popper' &&
|
|
70
|
-
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
71
|
-
className
|
|
72
|
-
)}
|
|
73
|
-
position={position}
|
|
74
|
-
ref={ref}
|
|
75
|
-
{...props}
|
|
76
|
-
>
|
|
77
|
-
<SelectScrollUpButton />
|
|
78
|
-
<SelectPrimitive.Viewport
|
|
79
|
-
className={cn(
|
|
80
|
-
'p-1',
|
|
81
|
-
position === 'popper' && 'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)'
|
|
82
|
-
)}
|
|
83
|
-
>
|
|
84
|
-
{children}
|
|
85
|
-
</SelectPrimitive.Viewport>
|
|
86
|
-
<SelectScrollDownButton />
|
|
87
|
-
</SelectPrimitive.Content>
|
|
88
|
-
</SelectPrimitive.Portal>
|
|
89
|
-
))
|
|
90
|
-
SelectContent.displayName = SelectPrimitive.Content.displayName
|
|
91
|
-
|
|
92
|
-
const SelectLabel = React.forwardRef<
|
|
93
|
-
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
94
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
95
|
-
>(({ className, ...props }, ref) => (
|
|
96
|
-
<SelectPrimitive.Label className={cn('px-2 py-1.5 text-sm font-semibold', className)} ref={ref} {...props} />
|
|
97
|
-
))
|
|
98
|
-
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
|
99
|
-
|
|
100
|
-
const SelectItem = React.forwardRef<
|
|
101
|
-
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
102
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
103
|
-
>(({ children, className, ...props }, ref) => (
|
|
104
|
-
<SelectPrimitive.Item
|
|
105
|
-
className={cn(
|
|
106
|
-
'focus:text-foreground focus:bg-hover relative flex w-full cursor-default items-center rounded-md py-1.5 pr-8 pl-2 text-sm outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
107
|
-
className
|
|
108
|
-
)}
|
|
109
|
-
ref={ref}
|
|
110
|
-
{...props}
|
|
111
|
-
>
|
|
112
|
-
<span className='absolute right-2 flex h-3.5 w-3.5 items-center justify-center'>
|
|
113
|
-
<SelectPrimitive.ItemIndicator>
|
|
114
|
-
<CheckIcon className='h-4 w-4' />
|
|
115
|
-
</SelectPrimitive.ItemIndicator>
|
|
116
|
-
</span>
|
|
117
|
-
<SelectPrimitive.ItemText>
|
|
118
|
-
<span>{children}</span>
|
|
119
|
-
</SelectPrimitive.ItemText>
|
|
120
|
-
</SelectPrimitive.Item>
|
|
121
|
-
))
|
|
122
|
-
SelectItem.displayName = SelectPrimitive.Item.displayName
|
|
123
|
-
|
|
124
|
-
const SelectSeparator = React.forwardRef<
|
|
125
|
-
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
126
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
127
|
-
>(({ className, ...props }, ref) => (
|
|
128
|
-
<SelectPrimitive.Separator className={cn('bg-border -mx-1 my-1 h-px', className)} ref={ref} {...props} />
|
|
129
|
-
))
|
|
130
|
-
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
|
131
|
-
|
|
132
|
-
export {
|
|
133
|
-
Select,
|
|
134
|
-
SelectContent,
|
|
135
|
-
SelectGroup,
|
|
136
|
-
SelectItem,
|
|
137
|
-
SelectLabel,
|
|
138
|
-
SelectScrollDownButton,
|
|
139
|
-
SelectScrollUpButton,
|
|
140
|
-
SelectSeparator,
|
|
141
|
-
SelectTrigger,
|
|
142
|
-
SelectValue
|
|
143
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
|
-
import { useTranslation } from 'react-i18next'
|
|
3
|
-
|
|
4
|
-
const sizes = {
|
|
5
|
-
lg: '70',
|
|
6
|
-
md: '50',
|
|
7
|
-
sm: '20',
|
|
8
|
-
xl: '100',
|
|
9
|
-
xs: '5'
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const variants = {
|
|
13
|
-
light: 'var(--color-spinner-light)',
|
|
14
|
-
primary: 'var(--color-spinner-primary)',
|
|
15
|
-
secondary: 'var(--color-spinner-secondary)'
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface SpinnerProps {
|
|
19
|
-
className?: string
|
|
20
|
-
size?: keyof typeof sizes
|
|
21
|
-
variant?: keyof typeof variants
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const Spinner = ({ className = '', size = 'md', variant = 'primary' }: SpinnerProps) => {
|
|
25
|
-
const { t } = useTranslation('ui')
|
|
26
|
-
return (
|
|
27
|
-
<>
|
|
28
|
-
<svg
|
|
29
|
-
fill='currentColor'
|
|
30
|
-
height={sizes[size]}
|
|
31
|
-
stroke={variants[variant]}
|
|
32
|
-
viewBox='0 0 100 100'
|
|
33
|
-
width={sizes[size]}
|
|
34
|
-
xmlns='http://www.w3.org/2000/svg'
|
|
35
|
-
>
|
|
36
|
-
<g className={clsx('spinner_V8m1', sizes[size], variants[variant], className)}>
|
|
37
|
-
<circle cx='50' cy='50' fill='none' opacity='0.25' r='35' stroke='currentColor' strokeWidth='10' />
|
|
38
|
-
<circle
|
|
39
|
-
cx='50'
|
|
40
|
-
cy='50'
|
|
41
|
-
fill='none'
|
|
42
|
-
r='35'
|
|
43
|
-
stroke='currentColor'
|
|
44
|
-
strokeDasharray='164.93361431346415 56.97787143782138'
|
|
45
|
-
strokeWidth='10'
|
|
46
|
-
>
|
|
47
|
-
<animateTransform
|
|
48
|
-
attributeName='transform'
|
|
49
|
-
dur='1s'
|
|
50
|
-
keyTimes='0;1'
|
|
51
|
-
repeatCount='indefinite'
|
|
52
|
-
type='rotate'
|
|
53
|
-
values='0 50 50;360 50 50'
|
|
54
|
-
/>
|
|
55
|
-
</circle>
|
|
56
|
-
</g>
|
|
57
|
-
</svg>
|
|
58
|
-
<span className='sr-only'>{t('spinner.loading')}</span>
|
|
59
|
-
</>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react'
|
|
2
|
-
|
|
3
|
-
import { cn } from '../utils'
|
|
4
|
-
|
|
5
|
-
interface StepHeaderProps {
|
|
6
|
-
subtitle?: ReactNode
|
|
7
|
-
subtitleClassName?: string
|
|
8
|
-
title: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const StepHeader = ({ subtitle, subtitleClassName, title }: StepHeaderProps) => (
|
|
12
|
-
<div className='flex flex-col gap-1'>
|
|
13
|
-
<h1 className='text-title text-2xl font-bold'>{title}</h1>
|
|
14
|
-
{subtitle !== undefined && <p className={cn('text-subtitle text-sm', subtitleClassName)}>{subtitle}</p>}
|
|
15
|
-
</div>
|
|
16
|
-
)
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { CheckIcon } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
import { cn } from '../utils'
|
|
4
|
-
|
|
5
|
-
export interface StepperStep {
|
|
6
|
-
label: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface StepperProps {
|
|
10
|
-
activeIndex: number
|
|
11
|
-
className?: string
|
|
12
|
-
intermediate?: boolean
|
|
13
|
-
steps: StepperStep[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const Stepper = ({ activeIndex, className, intermediate = false, steps }: StepperProps) => {
|
|
17
|
-
return (
|
|
18
|
-
<div className={cn('flex w-full items-start', className)}>
|
|
19
|
-
{steps.map((step, index) => {
|
|
20
|
-
const isCompleted = intermediate ? index <= activeIndex : index < activeIndex
|
|
21
|
-
const isActive = !intermediate && index === activeIndex
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<div className='flex flex-1 flex-col items-center' key={step.label}>
|
|
25
|
-
<div className='flex w-full items-center'>
|
|
26
|
-
<div
|
|
27
|
-
className={cn(
|
|
28
|
-
'h-0.75 flex-1 self-center',
|
|
29
|
-
isCompleted || isActive ? 'bg-primary' : 'bg-inactive',
|
|
30
|
-
index === 0 && 'rounded-l-full'
|
|
31
|
-
)}
|
|
32
|
-
/>
|
|
33
|
-
<div
|
|
34
|
-
aria-current={isActive ? 'step' : undefined}
|
|
35
|
-
className={cn(
|
|
36
|
-
'flex h-5.5 w-5.5 shrink-0 items-center justify-center rounded-full border text-xs font-semibold transition-colors md:h-6.5 md:w-6.5',
|
|
37
|
-
isCompleted && 'bg-primary border-primary text-inverse',
|
|
38
|
-
isActive && 'bg-primary border-primary text-inverse',
|
|
39
|
-
!isCompleted && !isActive && 'bg-inactive border-inactive text-surface'
|
|
40
|
-
)}
|
|
41
|
-
>
|
|
42
|
-
{isCompleted ? <CheckIcon className='h-4 w-4' /> : index + 1}
|
|
43
|
-
</div>
|
|
44
|
-
<div
|
|
45
|
-
className={cn(
|
|
46
|
-
'relative h-0.75 flex-1 self-center',
|
|
47
|
-
isCompleted ? 'bg-primary' : 'bg-inactive',
|
|
48
|
-
index === steps.length - 1 && 'rounded-r-full'
|
|
49
|
-
)}
|
|
50
|
-
>
|
|
51
|
-
{index === 0 && (
|
|
52
|
-
<div
|
|
53
|
-
className={cn(
|
|
54
|
-
'absolute top-1/2 right-0 h-2.25 w-2.25 translate-x-1/2 -translate-y-1/2 rounded-full transition-colors',
|
|
55
|
-
isCompleted ? 'bg-primary' : 'bg-inactive'
|
|
56
|
-
)}
|
|
57
|
-
/>
|
|
58
|
-
)}
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
<span className={cn('mt-1 text-xs font-normal', isCompleted || isActive ? 'text-primary' : 'text-muted')}>
|
|
62
|
-
{step.label}
|
|
63
|
-
</span>
|
|
64
|
-
</div>
|
|
65
|
-
)
|
|
66
|
-
})}
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
import { ToastOptions, ToastPosition, ToastType } from '../../types/toast'
|
|
4
|
-
import { registerToastEmitter } from '../../utils/toast-emitter'
|
|
5
|
-
import { Toast } from './toast'
|
|
6
|
-
|
|
7
|
-
interface ToastItem extends ToastOptions {
|
|
8
|
-
id: string
|
|
9
|
-
type: ToastType
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function ToastProvider() {
|
|
13
|
-
const [toasts, setToasts] = useState<ToastItem[]>([])
|
|
14
|
-
const itemRefs = useRef(new Map<string, HTMLDivElement>())
|
|
15
|
-
const prevRects = useRef(new Map<string, DOMRect>())
|
|
16
|
-
|
|
17
|
-
const showToast = useCallback((type: ToastType, options: ToastOptions) => {
|
|
18
|
-
setToasts((prev) => [
|
|
19
|
-
...prev,
|
|
20
|
-
{
|
|
21
|
-
duration: 2500,
|
|
22
|
-
id: crypto.randomUUID(),
|
|
23
|
-
position: 'top-right',
|
|
24
|
-
type,
|
|
25
|
-
...options
|
|
26
|
-
}
|
|
27
|
-
])
|
|
28
|
-
}, [])
|
|
29
|
-
|
|
30
|
-
const removeToast = useCallback((id: string) => {
|
|
31
|
-
setToasts((prev) => prev.filter((t) => t.id !== id))
|
|
32
|
-
}, [])
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
registerToastEmitter(showToast)
|
|
36
|
-
return () => registerToastEmitter(null)
|
|
37
|
-
}, [showToast])
|
|
38
|
-
|
|
39
|
-
const grouped = useMemo(() => {
|
|
40
|
-
return toasts.reduce<Partial<Record<ToastPosition, ToastItem[]>>>((acc, toast) => {
|
|
41
|
-
const pos = toast.position ?? 'top'
|
|
42
|
-
acc[pos] = [...(acc[pos] ?? []), toast]
|
|
43
|
-
return acc
|
|
44
|
-
}, {})
|
|
45
|
-
}, [toasts])
|
|
46
|
-
|
|
47
|
-
useLayoutEffect(() => {
|
|
48
|
-
const newRects = new Map<string, DOMRect>()
|
|
49
|
-
|
|
50
|
-
itemRefs.current.forEach((el, id) => {
|
|
51
|
-
newRects.set(id, el.getBoundingClientRect())
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
newRects.forEach((newRect, id) => {
|
|
55
|
-
const prevRect = prevRects.current.get(id)
|
|
56
|
-
const el = itemRefs.current.get(id)
|
|
57
|
-
if (!el || !prevRect) return
|
|
58
|
-
|
|
59
|
-
const dy = prevRect.top - newRect.top
|
|
60
|
-
if (dy === 0) return
|
|
61
|
-
|
|
62
|
-
el.style.transform = `translateY(${dy}px)`
|
|
63
|
-
el.style.transition = 'transform 0s'
|
|
64
|
-
|
|
65
|
-
requestAnimationFrame(() => {
|
|
66
|
-
el.style.transition = 'transform 200ms ease-out'
|
|
67
|
-
el.style.transform = ''
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
prevRects.current = newRects
|
|
72
|
-
}, [toasts])
|
|
73
|
-
|
|
74
|
-
const setItemRef = useCallback((id: string) => {
|
|
75
|
-
return (el: HTMLDivElement | null) => {
|
|
76
|
-
if (!el) {
|
|
77
|
-
itemRefs.current.delete(id)
|
|
78
|
-
prevRects.current.delete(id)
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
itemRefs.current.set(id, el)
|
|
82
|
-
}
|
|
83
|
-
}, [])
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<>
|
|
87
|
-
{Object.entries(grouped).map(([position, list]) => (
|
|
88
|
-
<div className={`fixed z-4000 ${getPositionClass(position as ToastPosition)}`} key={position}>
|
|
89
|
-
<div className='relative flex flex-col gap-3'>
|
|
90
|
-
{list.map((toast) => (
|
|
91
|
-
<div key={toast.id} ref={setItemRef(toast.id)}>
|
|
92
|
-
<Toast
|
|
93
|
-
duration={toast.duration}
|
|
94
|
-
id={toast.id}
|
|
95
|
-
onClose={removeToast}
|
|
96
|
-
subtitle={toast.subtitle}
|
|
97
|
-
title={toast.title}
|
|
98
|
-
type={toast.type}
|
|
99
|
-
/>
|
|
100
|
-
</div>
|
|
101
|
-
))}
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
))}
|
|
105
|
-
</>
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function getPositionClass(position: ToastPosition) {
|
|
110
|
-
const map: Record<ToastPosition, string> = {
|
|
111
|
-
bottom: 'bottom-6 left-1/2 -translate-x-1/2',
|
|
112
|
-
'bottom-left': 'bottom-6 left-6',
|
|
113
|
-
'bottom-right': 'bottom-6 right-6',
|
|
114
|
-
center: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',
|
|
115
|
-
'middle-bottom': 'bottom-20 left-1/2 -translate-x-1/2',
|
|
116
|
-
'middle-top': 'top-20 left-1/2 -translate-x-1/2',
|
|
117
|
-
top: 'top-6 left-1/2 -translate-x-1/2',
|
|
118
|
-
'top-left': 'top-6 left-6',
|
|
119
|
-
'top-right': 'top-6 right-6'
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return map[position]
|
|
123
|
-
}
|