@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.
Files changed (49) hide show
  1. package/dist/index.d.ts +420 -0
  2. package/dist/index.js +1710 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/locales/index.d.ts +34 -0
  5. package/dist/locales/index.js +40 -0
  6. package/dist/locales/index.js.map +1 -0
  7. package/dist/styles/index.css +8 -0
  8. package/package.json +24 -8
  9. package/src/components/alert.tsx +0 -45
  10. package/src/components/auth-header.tsx +0 -26
  11. package/src/components/button.tsx +0 -121
  12. package/src/components/command.tsx +0 -125
  13. package/src/components/dialog.tsx +0 -118
  14. package/src/components/form-fields/index.ts +0 -5
  15. package/src/components/form-fields/input-field.tsx +0 -131
  16. package/src/components/form-fields/label-field.tsx +0 -20
  17. package/src/components/form-fields/map-picker-field.tsx +0 -49
  18. package/src/components/form-fields/otp-field.tsx +0 -76
  19. package/src/components/form-fields/select-field.tsx +0 -77
  20. package/src/components/form.tsx +0 -138
  21. package/src/components/google-map-picker.tsx +0 -243
  22. package/src/components/input.tsx +0 -40
  23. package/src/components/label.tsx +0 -17
  24. package/src/components/loading.tsx +0 -19
  25. package/src/components/otp-input.tsx +0 -62
  26. package/src/components/password-checklist.tsx +0 -37
  27. package/src/components/popover.tsx +0 -31
  28. package/src/components/select-input.tsx +0 -236
  29. package/src/components/select.tsx +0 -143
  30. package/src/components/spinner.tsx +0 -61
  31. package/src/components/step-header.tsx +0 -16
  32. package/src/components/stepper.tsx +0 -69
  33. package/src/components/toast/index.ts +0 -2
  34. package/src/components/toast/toast-provider.tsx +0 -123
  35. package/src/components/toast/toast.tsx +0 -106
  36. package/src/components/tooltip.tsx +0 -56
  37. package/src/components/wrapper.tsx +0 -12
  38. package/src/index.ts +0 -27
  39. package/src/locales/es/ui.json +0 -28
  40. package/src/locales/index.ts +0 -7
  41. package/src/styles/index.css +0 -1
  42. package/src/types/password.ts +0 -12
  43. package/src/types/toast.ts +0 -19
  44. package/src/utils/cn.ts +0 -6
  45. package/src/utils/index.ts +0 -3
  46. package/src/utils/styles.ts +0 -19
  47. package/src/utils/toast-emitter.ts +0 -37
  48. package/src/vite-env.d.ts +0 -3
  49. /package/{src → dist}/styles/theme.css +0 -0
@@ -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
- )
@@ -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 }
@@ -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 }