@open-mercato/ui 0.6.1-develop.3276.1.1fcd145078 β 0.6.1-develop.3287.1.450f4ffb56
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +23 -14
- package/dist/backend/AppShell.js +8 -32
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/CrudForm.js +18 -0
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/DataTable.js +149 -53
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/FilterBar.js +11 -14
- package/dist/backend/FilterBar.js.map +2 -2
- package/dist/backend/columns/ColumnChooserPanel.js +4 -6
- package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
- package/dist/backend/filters/ActiveFilterChips.js +142 -0
- package/dist/backend/filters/ActiveFilterChips.js.map +7 -0
- package/dist/backend/filters/AdvancedFilterBuilder.js +689 -163
- package/dist/backend/filters/AdvancedFilterBuilder.js.map +2 -2
- package/dist/backend/filters/AdvancedFilterPanel.js +354 -0
- package/dist/backend/filters/AdvancedFilterPanel.js.map +7 -0
- package/dist/backend/filters/FilterEmptyState.js +41 -0
- package/dist/backend/filters/FilterEmptyState.js.map +7 -0
- package/dist/backend/filters/FilterFieldPicker.js +148 -0
- package/dist/backend/filters/FilterFieldPicker.js.map +7 -0
- package/dist/backend/filters/FilteredEmptyResults.js +26 -0
- package/dist/backend/filters/FilteredEmptyResults.js.map +7 -0
- package/dist/backend/filters/QuickFilters.js +41 -0
- package/dist/backend/filters/QuickFilters.js.map +7 -0
- package/dist/backend/filters/filterValidation.js +47 -0
- package/dist/backend/filters/filterValidation.js.map +7 -0
- package/dist/backend/filters/treeReducer.js +159 -0
- package/dist/backend/filters/treeReducer.js.map +7 -0
- package/dist/backend/hooks/useAdvancedFilter.js +82 -1
- package/dist/backend/hooks/useAdvancedFilter.js.map +2 -2
- package/dist/backend/inputs/ComboboxInput.js +11 -7
- package/dist/backend/inputs/ComboboxInput.js.map +2 -2
- package/dist/backend/inputs/EventPatternInput.js +5 -2
- package/dist/backend/inputs/EventPatternInput.js.map +2 -2
- package/dist/backend/inputs/EventSelect.js +10 -4
- package/dist/backend/inputs/EventSelect.js.map +2 -2
- package/dist/backend/inputs/LookupSelect.js +51 -47
- package/dist/backend/inputs/LookupSelect.js.map +2 -2
- package/dist/backend/inputs/PhoneNumberField.js +196 -45
- package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
- package/dist/backend/inputs/SwitchableMarkdownInput.js.map +2 -2
- package/dist/backend/messages/message-compose-form-groups.js +2 -2
- package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
- package/dist/backend/utils/api.js +18 -1
- package/dist/backend/utils/api.js.map +2 -2
- package/dist/backend/utils/serverErrors.js +6 -2
- package/dist/backend/utils/serverErrors.js.map +2 -2
- package/dist/backend/utils/useAutoDiscoveredFields.js +1 -0
- package/dist/backend/utils/useAutoDiscoveredFields.js.map +2 -2
- package/dist/backend/utils/useCurrentUserId.js +31 -0
- package/dist/backend/utils/useCurrentUserId.js.map +7 -0
- package/dist/portal/utils/nav.js +17 -0
- package/dist/portal/utils/nav.js.map +2 -2
- package/dist/primitives/amount-input.js +128 -0
- package/dist/primitives/amount-input.js.map +7 -0
- package/dist/primitives/button-input.js +47 -0
- package/dist/primitives/button-input.js.map +7 -0
- package/dist/primitives/card-input.js +133 -0
- package/dist/primitives/card-input.js.map +7 -0
- package/dist/primitives/dialog.js +7 -5
- package/dist/primitives/dialog.js.map +2 -2
- package/dist/primitives/email-input.js +29 -0
- package/dist/primitives/email-input.js.map +7 -0
- package/dist/primitives/password-input.js +64 -0
- package/dist/primitives/password-input.js.map +7 -0
- package/dist/primitives/search-input.js +57 -0
- package/dist/primitives/search-input.js.map +7 -0
- package/dist/primitives/select.js +43 -5
- package/dist/primitives/select.js.map +2 -2
- package/dist/primitives/tag.js +4 -2
- package/dist/primitives/tag.js.map +2 -2
- package/dist/primitives/website-input.js +56 -0
- package/dist/primitives/website-input.js.map +7 -0
- package/package.json +3 -3
- package/src/backend/AppShell.tsx +7 -30
- package/src/backend/CrudForm.tsx +17 -0
- package/src/backend/DataTable.tsx +307 -63
- package/src/backend/FilterBar.tsx +5 -6
- package/src/backend/__tests__/DataTable.extensions.test.tsx +170 -0
- package/src/backend/__tests__/serverErrors.test.ts +79 -1
- package/src/backend/columns/ColumnChooserPanel.tsx +4 -5
- package/src/backend/filters/ActiveFilterChips.tsx +179 -0
- package/src/backend/filters/AdvancedFilterBuilder.tsx +840 -237
- package/src/backend/filters/AdvancedFilterPanel.tsx +456 -0
- package/src/backend/filters/FilterEmptyState.tsx +51 -0
- package/src/backend/filters/FilterFieldPicker.tsx +139 -0
- package/src/backend/filters/FilteredEmptyResults.tsx +40 -0
- package/src/backend/filters/QuickFilters.tsx +62 -0
- package/src/backend/filters/__tests__/ActiveFilterChips.test.tsx +75 -0
- package/src/backend/filters/__tests__/AdvancedFilterBuilder.test.tsx +210 -0
- package/src/backend/filters/__tests__/AdvancedFilterPanel.test.tsx +212 -0
- package/src/backend/filters/__tests__/FilterEmptyState.test.tsx +31 -0
- package/src/backend/filters/__tests__/FilterFieldPicker.test.tsx +61 -0
- package/src/backend/filters/__tests__/FilteredEmptyResults.test.tsx +35 -0
- package/src/backend/filters/__tests__/QuickFilters.test.tsx +34 -0
- package/src/backend/filters/__tests__/filterValidation.test.ts +69 -0
- package/src/backend/filters/__tests__/treeReducer.test.ts +189 -0
- package/src/backend/filters/filterValidation.ts +61 -0
- package/src/backend/filters/treeReducer.ts +201 -0
- package/src/backend/hooks/__tests__/useAdvancedFilter.test.tsx +213 -0
- package/src/backend/hooks/useAdvancedFilter.ts +130 -0
- package/src/backend/inputs/ComboboxInput.tsx +32 -26
- package/src/backend/inputs/EventPatternInput.tsx +11 -2
- package/src/backend/inputs/EventSelect.tsx +13 -4
- package/src/backend/inputs/LookupSelect.tsx +75 -53
- package/src/backend/inputs/PhoneNumberField.tsx +237 -47
- package/src/backend/inputs/SwitchableMarkdownInput.tsx +30 -0
- package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +4 -0
- package/src/backend/inputs/__tests__/PhoneNumberField.test.tsx +4 -0
- package/src/backend/messages/message-compose-form-groups.tsx +2 -2
- package/src/backend/utils/api.ts +27 -2
- package/src/backend/utils/serverErrors.ts +15 -8
- package/src/backend/utils/useAutoDiscoveredFields.ts +2 -0
- package/src/backend/utils/useCurrentUserId.ts +40 -0
- package/src/portal/utils/__tests__/nav.test.ts +80 -0
- package/src/portal/utils/nav.ts +23 -1
- package/src/primitives/__tests__/password-input.test.tsx +93 -0
- package/src/primitives/__tests__/search-input.test.tsx +95 -0
- package/src/primitives/__tests__/zindex-overlay.test.tsx +26 -0
- package/src/primitives/amount-input.tsx +184 -0
- package/src/primitives/button-input.tsx +68 -0
- package/src/primitives/card-input.tsx +193 -0
- package/src/primitives/dialog.tsx +18 -7
- package/src/primitives/email-input.tsx +36 -0
- package/src/primitives/password-input.tsx +100 -0
- package/src/primitives/search-input.tsx +87 -0
- package/src/primitives/select.tsx +98 -11
- package/src/primitives/tag.tsx +3 -1
- package/src/primitives/website-input.tsx +72 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Search, X } from 'lucide-react'
|
|
5
|
+
import type { VariantProps } from 'class-variance-authority'
|
|
6
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
8
|
+
import { inputWrapperVariants, inputElementVariants } from './input'
|
|
9
|
+
|
|
10
|
+
export type SearchInputProps = Omit<React.ComponentPropsWithoutRef<'input'>, 'size' | 'type' | 'onChange'> &
|
|
11
|
+
VariantProps<typeof inputWrapperVariants> & {
|
|
12
|
+
/** Controlled value. */
|
|
13
|
+
value: string
|
|
14
|
+
/** Called on every keystroke with the new value. */
|
|
15
|
+
onChange: (next: string) => void
|
|
16
|
+
/**
|
|
17
|
+
* Called when the user presses the trailing Γ button.
|
|
18
|
+
* Defaults to `onChange('')` β pass an explicit handler to also reset adjacent state
|
|
19
|
+
* (e.g. cancel an in-flight request, reset paging).
|
|
20
|
+
*/
|
|
21
|
+
onClear?: () => void
|
|
22
|
+
/** Show the trailing Γ button when the value is non-empty. Defaults to `true`. */
|
|
23
|
+
clearable?: boolean
|
|
24
|
+
/** Optional className on the wrapper. */
|
|
25
|
+
className?: string
|
|
26
|
+
/** Optional className on the inner `<input>`. */
|
|
27
|
+
inputClassName?: string
|
|
28
|
+
/** Translated aria-label for the clear button. Defaults to `t('ui.inputs.searchInput.clear', 'Clear search')`. */
|
|
29
|
+
clearLabel?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Search input matching Figma `Text Input [1.1]` (node `266:5251`) **Search** variant β a leading
|
|
34
|
+
* `Search` icon, the text input, and an optional trailing `X` button to clear the field. Built on
|
|
35
|
+
* the shared `inputWrapperVariants` / `inputElementVariants` CVA so the visual contract stays in
|
|
36
|
+
* sync with the foundation `Input` primitive.
|
|
37
|
+
*
|
|
38
|
+
* The clear button is rendered as a proper `<button>` (not inside an `aria-hidden` decorative
|
|
39
|
+
* span), so it remains keyboard-focusable and screen-reader-accessible.
|
|
40
|
+
*/
|
|
41
|
+
export const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
|
|
42
|
+
({ className, inputClassName, size, value, onChange, onClear, clearable = true, clearLabel, placeholder, disabled, ...props }, ref) => {
|
|
43
|
+
const t = useT()
|
|
44
|
+
const resolvedPlaceholder = placeholder ?? t('ui.inputs.searchInput.placeholder', 'Searchβ¦')
|
|
45
|
+
const resolvedClearLabel = clearLabel ?? t('ui.inputs.searchInput.clear', 'Clear search')
|
|
46
|
+
const showClear = clearable && value.length > 0 && !disabled
|
|
47
|
+
|
|
48
|
+
const handleClear = React.useCallback(() => {
|
|
49
|
+
if (onClear) onClear()
|
|
50
|
+
else onChange('')
|
|
51
|
+
}, [onChange, onClear])
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={cn(inputWrapperVariants({ size }), className)} data-slot="search-input-wrapper">
|
|
55
|
+
<Search className="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
56
|
+
<input
|
|
57
|
+
ref={ref}
|
|
58
|
+
type="search"
|
|
59
|
+
inputMode="search"
|
|
60
|
+
autoComplete="off"
|
|
61
|
+
placeholder={resolvedPlaceholder}
|
|
62
|
+
value={value}
|
|
63
|
+
onChange={(event) => onChange(event.target.value)}
|
|
64
|
+
disabled={disabled}
|
|
65
|
+
className={cn(
|
|
66
|
+
inputElementVariants({ size }),
|
|
67
|
+
// Disable the native search clear button β we render our own.
|
|
68
|
+
'[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none',
|
|
69
|
+
inputClassName,
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
{showClear ? (
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={handleClear}
|
|
77
|
+
aria-label={resolvedClearLabel}
|
|
78
|
+
className="flex shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring/40"
|
|
79
|
+
>
|
|
80
|
+
<X className="size-4" aria-hidden="true" />
|
|
81
|
+
</button>
|
|
82
|
+
) : null}
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
SearchInput.displayName = 'SearchInput'
|
|
@@ -44,6 +44,46 @@ const SelectTrigger = React.forwardRef<
|
|
|
44
44
|
))
|
|
45
45
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Leading visual slot inside a `SelectTrigger` β render flags, avatars,
|
|
49
|
+
* provider logos, brand marks, or company icons before the `<SelectValue>`.
|
|
50
|
+
*
|
|
51
|
+
* Mirrors the Type variants in the Figma DS (Basic / Country / Avatar /
|
|
52
|
+
* Provider / Brand / Company β Figma `Select [1.1]`, node `270:1085`).
|
|
53
|
+
*
|
|
54
|
+
* Render inside `SelectTrigger`, BEFORE the `<SelectValue>`:
|
|
55
|
+
*
|
|
56
|
+
* ```tsx
|
|
57
|
+
* <SelectTrigger>
|
|
58
|
+
* <SelectTriggerLeading><Avatar size="sm" label={user.name} /></SelectTriggerLeading>
|
|
59
|
+
* <SelectValue placeholder="Select user" />
|
|
60
|
+
* </SelectTrigger>
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* Sizing follows the trigger: icons render `size-4`, images render `size-5`
|
|
64
|
+
* by default β pass your own classes on the inner element when you need a
|
|
65
|
+
* different visual size (e.g. country flags `h-3 w-4`).
|
|
66
|
+
*/
|
|
67
|
+
const SelectTriggerLeading = React.forwardRef<
|
|
68
|
+
HTMLSpanElement,
|
|
69
|
+
React.HTMLAttributes<HTMLSpanElement>
|
|
70
|
+
>(({ className, children, ...props }, ref) => (
|
|
71
|
+
<span
|
|
72
|
+
ref={ref}
|
|
73
|
+
data-slot="select-trigger-leading"
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
className={cn(
|
|
76
|
+
'flex shrink-0 items-center justify-center',
|
|
77
|
+
'[&>svg]:size-4 [&>svg]:shrink-0 [&>img]:size-5 [&>img]:shrink-0',
|
|
78
|
+
className
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
>
|
|
82
|
+
{children}
|
|
83
|
+
</span>
|
|
84
|
+
))
|
|
85
|
+
SelectTriggerLeading.displayName = 'SelectTriggerLeading'
|
|
86
|
+
|
|
47
87
|
const SelectScrollUpButton = React.forwardRef<
|
|
48
88
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
49
89
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
@@ -92,7 +132,7 @@ const SelectContent = React.forwardRef<
|
|
|
92
132
|
<SelectScrollUpButton />
|
|
93
133
|
<SelectPrimitive.Viewport
|
|
94
134
|
className={cn(
|
|
95
|
-
'p-
|
|
135
|
+
'p-2 max-h-[var(--radix-select-content-available-height)] overflow-y-auto',
|
|
96
136
|
position === 'popper' && 'w-full min-w-[var(--radix-select-trigger-width)]'
|
|
97
137
|
)}
|
|
98
138
|
>
|
|
@@ -110,7 +150,11 @@ const SelectLabel = React.forwardRef<
|
|
|
110
150
|
>(({ className, ...props }, ref) => (
|
|
111
151
|
<SelectPrimitive.Label
|
|
112
152
|
ref={ref}
|
|
113
|
-
className={cn(
|
|
153
|
+
className={cn(
|
|
154
|
+
'px-2 pt-2 pb-1 text-overline font-semibold uppercase tracking-wider text-muted-foreground/80',
|
|
155
|
+
'select-none',
|
|
156
|
+
className
|
|
157
|
+
)}
|
|
114
158
|
{...props}
|
|
115
159
|
/>
|
|
116
160
|
))
|
|
@@ -123,7 +167,7 @@ const SelectItem = React.forwardRef<
|
|
|
123
167
|
<SelectPrimitive.Item
|
|
124
168
|
ref={ref}
|
|
125
169
|
className={cn(
|
|
126
|
-
'relative flex w-full cursor-default select-none items-center gap-2 rounded-
|
|
170
|
+
'relative flex w-full cursor-default select-none items-center gap-2 rounded-md p-2 text-sm outline-none transition-colors',
|
|
127
171
|
'focus:bg-muted focus:text-foreground',
|
|
128
172
|
'data-[state=checked]:bg-muted/70 data-[state=checked]:text-foreground',
|
|
129
173
|
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
@@ -132,18 +176,59 @@ const SelectItem = React.forwardRef<
|
|
|
132
176
|
)}
|
|
133
177
|
{...props}
|
|
134
178
|
>
|
|
135
|
-
<
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
</
|
|
179
|
+
<SelectPrimitive.ItemText asChild>
|
|
180
|
+
<span className="flex min-w-0 flex-1 items-center gap-2 truncate">
|
|
181
|
+
{children}
|
|
182
|
+
</span>
|
|
183
|
+
</SelectPrimitive.ItemText>
|
|
184
|
+
<SelectPrimitive.ItemIndicator className="ml-auto flex shrink-0 items-center justify-center">
|
|
185
|
+
<Check className="size-4 text-foreground" aria-hidden="true" />
|
|
186
|
+
</SelectPrimitive.ItemIndicator>
|
|
143
187
|
</SelectPrimitive.Item>
|
|
144
188
|
))
|
|
145
189
|
SelectItem.displayName = SelectPrimitive.Item.displayName
|
|
146
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Leading visual slot inside a `SelectItem` β mirror of `SelectTriggerLeading`
|
|
193
|
+
* for dropdown rows. Compose with the DS `Avatar`, country flag images, or
|
|
194
|
+
* `lucide-react` icons.
|
|
195
|
+
*
|
|
196
|
+
* Render inside the `<SelectItem>` children, before the row label:
|
|
197
|
+
*
|
|
198
|
+
* ```tsx
|
|
199
|
+
* <SelectItem value="us">
|
|
200
|
+
* <SelectItemLeading><span className="h-3 w-4 rounded-sm overflow-hidden">πΊπΈ</span></SelectItemLeading>
|
|
201
|
+
* United States
|
|
202
|
+
* </SelectItem>
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* NOTE: `SelectPrimitive.ItemText` only reads the text node siblings for the
|
|
206
|
+
* trigger preview β wrap the leading slot OUTSIDE `ItemText` (which this
|
|
207
|
+
* primitive does by default, since `SelectItem`'s `children` are placed inside
|
|
208
|
+
* `<SelectPrimitive.ItemText>` only via the inner `<span className="flex-1
|
|
209
|
+
* truncate">`). Place the `SelectItemLeading` alongside the row text and the
|
|
210
|
+
* label will read correctly in the trigger preview.
|
|
211
|
+
*/
|
|
212
|
+
const SelectItemLeading = React.forwardRef<
|
|
213
|
+
HTMLSpanElement,
|
|
214
|
+
React.HTMLAttributes<HTMLSpanElement>
|
|
215
|
+
>(({ className, children, ...props }, ref) => (
|
|
216
|
+
<span
|
|
217
|
+
ref={ref}
|
|
218
|
+
data-slot="select-item-leading"
|
|
219
|
+
aria-hidden="true"
|
|
220
|
+
className={cn(
|
|
221
|
+
'flex shrink-0 items-center justify-center',
|
|
222
|
+
'[&>svg]:size-4 [&>svg]:shrink-0 [&>img]:size-5 [&>img]:shrink-0',
|
|
223
|
+
className
|
|
224
|
+
)}
|
|
225
|
+
{...props}
|
|
226
|
+
>
|
|
227
|
+
{children}
|
|
228
|
+
</span>
|
|
229
|
+
))
|
|
230
|
+
SelectItemLeading.displayName = 'SelectItemLeading'
|
|
231
|
+
|
|
147
232
|
const SelectSeparator = React.forwardRef<
|
|
148
233
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
149
234
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
@@ -191,9 +276,11 @@ export {
|
|
|
191
276
|
SelectGroup,
|
|
192
277
|
SelectValue,
|
|
193
278
|
SelectTrigger,
|
|
279
|
+
SelectTriggerLeading,
|
|
194
280
|
SelectContent,
|
|
195
281
|
SelectLabel,
|
|
196
282
|
SelectItem,
|
|
283
|
+
SelectItemLeading,
|
|
197
284
|
SelectSeparator,
|
|
198
285
|
SelectScrollUpButton,
|
|
199
286
|
SelectScrollDownButton,
|
package/src/primitives/tag.tsx
CHANGED
|
@@ -15,6 +15,7 @@ const tagVariants = cva(
|
|
|
15
15
|
info: 'border-status-info-border bg-status-info-bg text-status-info-text',
|
|
16
16
|
neutral: 'border-status-neutral-border bg-status-neutral-bg text-status-neutral-text',
|
|
17
17
|
brand: 'border-brand-violet/30 bg-brand-violet/10 text-brand-violet',
|
|
18
|
+
pink: 'border-status-pink-border bg-status-pink-bg text-status-pink-text',
|
|
18
19
|
},
|
|
19
20
|
shape: {
|
|
20
21
|
pill: 'rounded-full px-2.5 py-0.5',
|
|
@@ -33,9 +34,10 @@ const dotColorMap: Record<TagVariant, string> = {
|
|
|
33
34
|
info: 'bg-status-info-icon',
|
|
34
35
|
neutral: 'bg-status-neutral-icon',
|
|
35
36
|
brand: 'bg-brand-violet',
|
|
37
|
+
pink: 'bg-status-pink-icon',
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
export type TagVariant = 'default' | 'success' | 'warning' | 'error' | 'info' | 'neutral' | 'brand'
|
|
40
|
+
export type TagVariant = 'default' | 'success' | 'warning' | 'error' | 'info' | 'neutral' | 'brand' | 'pink'
|
|
39
41
|
|
|
40
42
|
export type TagShape = 'pill' | 'square'
|
|
41
43
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import type { VariantProps } from 'class-variance-authority'
|
|
5
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
7
|
+
import { inputWrapperVariants, inputElementVariants } from './input'
|
|
8
|
+
|
|
9
|
+
export type WebsiteInputProps = Omit<React.ComponentPropsWithoutRef<'input'>, 'size' | 'type'> &
|
|
10
|
+
VariantProps<typeof inputWrapperVariants> & {
|
|
11
|
+
/** Protocol shown in the left prefix box. Defaults to `'https://'` per Figma. */
|
|
12
|
+
prefix?: string
|
|
13
|
+
/** Hide the prefix box entirely (consumer wants a bare URL input). */
|
|
14
|
+
showPrefix?: boolean
|
|
15
|
+
/** Optional className on the wrapper. */
|
|
16
|
+
className?: string
|
|
17
|
+
/** Optional className on the inner `<input>`. */
|
|
18
|
+
inputClassName?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Website input matching Figma `Text Input [1.1]` (node `266:5251`) **Website** variant β a left
|
|
23
|
+
* prefix box with the protocol text (default `'https://'`), a vertical divider, then the URL text
|
|
24
|
+
* input. Built on the shared `inputWrapperVariants` / `inputElementVariants` CVA so the visual
|
|
25
|
+
* contract matches the foundation `Input` primitive.
|
|
26
|
+
*
|
|
27
|
+
* The prefix is **display-only** β the inner `<input>` value contains ONLY the host/path portion
|
|
28
|
+
* (e.g. `'www.example.com/path'`). Compose the full URL at the consumer boundary if needed.
|
|
29
|
+
*/
|
|
30
|
+
export const WebsiteInput = React.forwardRef<HTMLInputElement, WebsiteInputProps>(
|
|
31
|
+
({ className, inputClassName, size, prefix = 'https://', showPrefix = true, placeholder, disabled, ...props }, ref) => {
|
|
32
|
+
const t = useT()
|
|
33
|
+
const resolvedPlaceholder = placeholder ?? t('ui.inputs.websiteInput.placeholder', 'www.example.com')
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={cn(
|
|
37
|
+
inputWrapperVariants({ size }),
|
|
38
|
+
// Override default px so prefix box can sit flush left.
|
|
39
|
+
'px-0 overflow-hidden',
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
data-slot="website-input-wrapper"
|
|
43
|
+
>
|
|
44
|
+
{showPrefix ? (
|
|
45
|
+
<>
|
|
46
|
+
<span
|
|
47
|
+
className="flex shrink-0 items-center self-stretch border-r border-input px-3 text-sm text-muted-foreground select-none"
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
>
|
|
50
|
+
{prefix}
|
|
51
|
+
</span>
|
|
52
|
+
</>
|
|
53
|
+
) : null}
|
|
54
|
+
<input
|
|
55
|
+
ref={ref}
|
|
56
|
+
type="url"
|
|
57
|
+
inputMode="url"
|
|
58
|
+
autoComplete="url"
|
|
59
|
+
placeholder={resolvedPlaceholder}
|
|
60
|
+
disabled={disabled}
|
|
61
|
+
className={cn(
|
|
62
|
+
inputElementVariants({ size }),
|
|
63
|
+
'px-3',
|
|
64
|
+
inputClassName,
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
WebsiteInput.displayName = 'WebsiteInput'
|