@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
package/dist/portal/utils/nav.js
CHANGED
|
@@ -13,6 +13,16 @@ function pickGroup(group) {
|
|
|
13
13
|
if (group === "main" || group === "account") return group;
|
|
14
14
|
return "main";
|
|
15
15
|
}
|
|
16
|
+
function pickPreferredRoute(existing, candidate) {
|
|
17
|
+
const existingFeatures = existing.requireCustomerFeatures ?? [];
|
|
18
|
+
const candidateFeatures = candidate.requireCustomerFeatures ?? [];
|
|
19
|
+
if (existingFeatures.length === 0 && candidateFeatures.length > 0) return candidate;
|
|
20
|
+
if (candidateFeatures.length === 0 && existingFeatures.length > 0) return existing;
|
|
21
|
+
if (existingFeatures.length !== candidateFeatures.length) {
|
|
22
|
+
return candidateFeatures.length > existingFeatures.length ? candidate : existing;
|
|
23
|
+
}
|
|
24
|
+
return existing;
|
|
25
|
+
}
|
|
16
26
|
function buildPortalNav({
|
|
17
27
|
routes,
|
|
18
28
|
orgSlug,
|
|
@@ -21,12 +31,19 @@ function buildPortalNav({
|
|
|
21
31
|
}) {
|
|
22
32
|
const mainItems = [];
|
|
23
33
|
const accountItems = [];
|
|
34
|
+
const dedupedByPattern = /* @__PURE__ */ new Map();
|
|
24
35
|
for (const route of routes) {
|
|
25
36
|
const pattern = route.pattern ?? route.path;
|
|
26
37
|
if (!isPortalPattern(pattern)) continue;
|
|
27
38
|
if (route.navHidden) continue;
|
|
28
39
|
const nav = route.nav;
|
|
29
40
|
if (!nav || typeof nav.label !== "string" || nav.label.length === 0) continue;
|
|
41
|
+
const existing = dedupedByPattern.get(pattern);
|
|
42
|
+
dedupedByPattern.set(pattern, existing ? pickPreferredRoute(existing, route) : route);
|
|
43
|
+
}
|
|
44
|
+
for (const route of dedupedByPattern.values()) {
|
|
45
|
+
const pattern = route.pattern ?? route.path;
|
|
46
|
+
const nav = route.nav;
|
|
30
47
|
const requireFeatures = route.requireCustomerFeatures ?? [];
|
|
31
48
|
if (!isPortalAdmin && requireFeatures.length) {
|
|
32
49
|
if (!hasAllFeatures(grantedFeatures, requireFeatures)) continue;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/portal/utils/nav.ts"],
|
|
4
|
-
"sourcesContent": ["import type { FrontendRouteManifestEntry } from '@open-mercato/shared/modules/registry'\nimport { hasAllFeatures } from '@open-mercato/shared/security/features'\n\nexport type PortalNavGroupId = 'main' | 'account'\n\nexport type PortalNavItem = {\n id: string\n label: string\n labelKey?: string\n href: string\n icon?: string\n order: number\n}\n\nexport type PortalNavGroup = {\n id: PortalNavGroupId\n items: PortalNavItem[]\n}\n\nexport type BuildPortalNavOptions = {\n /** Route manifest to inspect (typically `getFrontendRouteManifests()`). */\n routes: readonly FrontendRouteManifestEntry[]\n /** Current customer org slug \u2014 substituted into `[orgSlug]` patterns. */\n orgSlug: string\n /** Feature strings granted to the current customer (may include wildcards). */\n grantedFeatures: readonly string[]\n /** If true, bypass feature checks (portal admin). Defaults to false. */\n isPortalAdmin?: boolean\n}\n\nfunction isPortalPattern(pattern: string | undefined): pattern is string {\n if (!pattern) return false\n return pattern.startsWith('/[orgSlug]/portal/') || pattern === '/[orgSlug]/portal'\n}\n\nfunction hasNoUnresolvedParams(href: string): boolean {\n return !href.includes('[')\n}\n\nfunction resolveHref(pattern: string, orgSlug: string): string {\n return pattern.replace('[orgSlug]', orgSlug)\n}\n\nfunction pickGroup(group: unknown): PortalNavGroupId {\n if (group === 'main' || group === 'account') return group\n return 'main'\n}\n\n/**\n * Build the portal sidebar from the frontend route manifest.\n *\n * Mirrors `buildAdminNav()` for the portal surface: selects routes under\n * `/[orgSlug]/portal/*` that declare a `nav` block, applies\n * `requireCustomerFeatures` against the caller's grants (wildcards honored),\n * and returns ordered sidebar groups.\n *\n * Absence of `nav` on a metadata file means the page is routable but not\n * auto-listed \u2014 useful for detail/create pages.\n */\nexport function buildPortalNav({\n routes,\n orgSlug,\n grantedFeatures,\n isPortalAdmin = false,\n}: BuildPortalNavOptions): PortalNavGroup[] {\n const mainItems: PortalNavItem[] = []\n const accountItems: PortalNavItem[] = []\n\n for (const route of routes) {\n const pattern = route.pattern ?? route.path\n if (!isPortalPattern(pattern)) continue\n if (route.navHidden) continue\n const nav = route.nav\n if (!nav || typeof nav.label !== 'string' || nav.label.length === 0) continue\n\n const requireFeatures = route.requireCustomerFeatures ?? []\n if (!isPortalAdmin && requireFeatures.length) {\n if (!hasAllFeatures(grantedFeatures as string[], requireFeatures as string[])) continue\n }\n\n const href = resolveHref(pattern
|
|
5
|
-
"mappings": "AACA,SAAS,sBAAsB;AA6B/B,SAAS,gBAAgB,SAAgD;AACvE,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,WAAW,oBAAoB,KAAK,YAAY;AACjE;AAEA,SAAS,sBAAsB,MAAuB;AACpD,SAAO,CAAC,KAAK,SAAS,GAAG;AAC3B;AAEA,SAAS,YAAY,SAAiB,SAAyB;AAC7D,SAAO,QAAQ,QAAQ,aAAa,OAAO;AAC7C;AAEA,SAAS,UAAU,OAAkC;AACnD,MAAI,UAAU,UAAU,UAAU,UAAW,QAAO;AACpD,SAAO;AACT;AAaO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAClB,GAA4C;AAC1C,QAAM,YAA6B,CAAC;AACpC,QAAM,eAAgC,CAAC;AAEvC,aAAW,SAAS,QAAQ;AAC1B,UAAM,UAAU,MAAM,WAAW,MAAM;AACvC,QAAI,CAAC,gBAAgB,OAAO,EAAG;AAC/B,QAAI,MAAM,UAAW;AACrB,UAAM,MAAM,MAAM;AAClB,QAAI,CAAC,OAAO,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,WAAW,EAAG;
|
|
4
|
+
"sourcesContent": ["import type { FrontendRouteManifestEntry } from '@open-mercato/shared/modules/registry'\nimport { hasAllFeatures } from '@open-mercato/shared/security/features'\n\nexport type PortalNavGroupId = 'main' | 'account'\n\nexport type PortalNavItem = {\n id: string\n label: string\n labelKey?: string\n href: string\n icon?: string\n order: number\n}\n\nexport type PortalNavGroup = {\n id: PortalNavGroupId\n items: PortalNavItem[]\n}\n\nexport type BuildPortalNavOptions = {\n /** Route manifest to inspect (typically `getFrontendRouteManifests()`). */\n routes: readonly FrontendRouteManifestEntry[]\n /** Current customer org slug \u2014 substituted into `[orgSlug]` patterns. */\n orgSlug: string\n /** Feature strings granted to the current customer (may include wildcards). */\n grantedFeatures: readonly string[]\n /** If true, bypass feature checks (portal admin). Defaults to false. */\n isPortalAdmin?: boolean\n}\n\nfunction isPortalPattern(pattern: string | undefined): pattern is string {\n if (!pattern) return false\n return pattern.startsWith('/[orgSlug]/portal/') || pattern === '/[orgSlug]/portal'\n}\n\nfunction hasNoUnresolvedParams(href: string): boolean {\n return !href.includes('[')\n}\n\nfunction resolveHref(pattern: string, orgSlug: string): string {\n return pattern.replace('[orgSlug]', orgSlug)\n}\n\nfunction pickGroup(group: unknown): PortalNavGroupId {\n if (group === 'main' || group === 'account') return group\n return 'main'\n}\n\nfunction pickPreferredRoute(\n existing: FrontendRouteManifestEntry,\n candidate: FrontendRouteManifestEntry,\n): FrontendRouteManifestEntry {\n const existingFeatures = existing.requireCustomerFeatures ?? []\n const candidateFeatures = candidate.requireCustomerFeatures ?? []\n if (existingFeatures.length === 0 && candidateFeatures.length > 0) return candidate\n if (candidateFeatures.length === 0 && existingFeatures.length > 0) return existing\n if (existingFeatures.length !== candidateFeatures.length) {\n return candidateFeatures.length > existingFeatures.length ? candidate : existing\n }\n return existing\n}\n\n/**\n * Build the portal sidebar from the frontend route manifest.\n *\n * Mirrors `buildAdminNav()` for the portal surface: selects routes under\n * `/[orgSlug]/portal/*` that declare a `nav` block, applies\n * `requireCustomerFeatures` against the caller's grants (wildcards honored),\n * and returns ordered sidebar groups.\n *\n * Absence of `nav` on a metadata file means the page is routable but not\n * auto-listed \u2014 useful for detail/create pages.\n */\nexport function buildPortalNav({\n routes,\n orgSlug,\n grantedFeatures,\n isPortalAdmin = false,\n}: BuildPortalNavOptions): PortalNavGroup[] {\n const mainItems: PortalNavItem[] = []\n const accountItems: PortalNavItem[] = []\n\n const dedupedByPattern = new Map<string, FrontendRouteManifestEntry>()\n for (const route of routes) {\n const pattern = route.pattern ?? route.path\n if (!isPortalPattern(pattern)) continue\n if (route.navHidden) continue\n const nav = route.nav\n if (!nav || typeof nav.label !== 'string' || nav.label.length === 0) continue\n const existing = dedupedByPattern.get(pattern)\n dedupedByPattern.set(pattern, existing ? pickPreferredRoute(existing, route) : route)\n }\n\n for (const route of dedupedByPattern.values()) {\n const pattern = (route.pattern ?? route.path) as string\n const nav = route.nav!\n\n const requireFeatures = route.requireCustomerFeatures ?? []\n if (!isPortalAdmin && requireFeatures.length) {\n if (!hasAllFeatures(grantedFeatures as string[], requireFeatures as string[])) continue\n }\n\n const href = resolveHref(pattern, orgSlug)\n if (!hasNoUnresolvedParams(href)) continue\n\n const group = pickGroup(nav.group)\n const item: PortalNavItem = {\n id: `portal-nav:${pattern}`,\n label: nav.label,\n labelKey: nav.labelKey,\n href,\n icon: nav.icon,\n order: typeof nav.order === 'number' ? nav.order : 100,\n }\n if (group === 'account') accountItems.push(item)\n else mainItems.push(item)\n }\n\n const sortItems = (items: PortalNavItem[]) =>\n items.sort((a, b) => {\n if (a.order !== b.order) return a.order - b.order\n return a.label.localeCompare(b.label)\n })\n\n sortItems(mainItems)\n sortItems(accountItems)\n\n const groups: PortalNavGroup[] = []\n if (mainItems.length) groups.push({ id: 'main', items: mainItems })\n if (accountItems.length) groups.push({ id: 'account', items: accountItems })\n return groups\n}\n\n/**\n * Merge sidebar groups from the portal nav endpoint with items contributed via\n * `usePortalInjectedMenuItems`. Auto-discovered entries take precedence \u2014\n * injected items with matching `id` or `href` are dropped as duplicates.\n */\nexport function mergePortalSidebarGroupsWithInjected<TInjected extends { id: string; href?: string }>(\n discovered: readonly PortalNavGroup[],\n injected: {\n main: readonly TInjected[]\n account: readonly TInjected[]\n },\n): {\n main: Array<PortalNavItem | TInjected>\n account: Array<PortalNavItem | TInjected>\n} {\n const mergeGroup = <T extends PortalNavItem | TInjected>(\n base: readonly PortalNavItem[],\n extra: readonly TInjected[],\n ): Array<PortalNavItem | TInjected> => {\n const knownIds = new Set(base.map((item) => item.id))\n const knownHrefs = new Set(base.map((item) => item.href).filter((href): href is string => Boolean(href)))\n const merged: Array<PortalNavItem | TInjected> = [...base]\n for (const item of extra) {\n if (knownIds.has(item.id)) continue\n if (item.href && knownHrefs.has(item.href)) continue\n merged.push(item)\n knownIds.add(item.id)\n if (item.href) knownHrefs.add(item.href)\n }\n return merged\n }\n\n const mainBase = discovered.find((g) => g.id === 'main')?.items ?? []\n const accountBase = discovered.find((g) => g.id === 'account')?.items ?? []\n return {\n main: mergeGroup(mainBase, injected.main),\n account: mergeGroup(accountBase, injected.account),\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
6
|
+
import { inputWrapperVariants, inputElementVariants } from "./input.js";
|
|
7
|
+
import {
|
|
8
|
+
Select,
|
|
9
|
+
SelectContent,
|
|
10
|
+
SelectItem,
|
|
11
|
+
SelectItemLeading,
|
|
12
|
+
SelectTrigger
|
|
13
|
+
} from "./select.js";
|
|
14
|
+
const AMOUNT_CURRENCIES = [
|
|
15
|
+
{ code: "EUR", symbol: "\u20AC", label: "Euro", flag: "\u{1F1EA}\u{1F1FA}" },
|
|
16
|
+
{ code: "USD", symbol: "$", label: "US Dollar", flag: "\u{1F1FA}\u{1F1F8}" },
|
|
17
|
+
{ code: "GBP", symbol: "\xA3", label: "Pound Sterling", flag: "\u{1F1EC}\u{1F1E7}" },
|
|
18
|
+
{ code: "PLN", symbol: "z\u0142", label: "Polish Z\u0142oty", flag: "\u{1F1F5}\u{1F1F1}" },
|
|
19
|
+
{ code: "CHF", symbol: "CHF", label: "Swiss Franc", flag: "\u{1F1E8}\u{1F1ED}" },
|
|
20
|
+
{ code: "SEK", symbol: "kr", label: "Swedish Krona", flag: "\u{1F1F8}\u{1F1EA}" },
|
|
21
|
+
{ code: "CZK", symbol: "K\u010D", label: "Czech Koruna", flag: "\u{1F1E8}\u{1F1FF}" },
|
|
22
|
+
{ code: "JPY", symbol: "\xA5", label: "Japanese Yen", flag: "\u{1F1EF}\u{1F1F5}" },
|
|
23
|
+
{ code: "AUD", symbol: "A$", label: "Australian Dollar", flag: "\u{1F1E6}\u{1F1FA}" },
|
|
24
|
+
{ code: "CAD", symbol: "C$", label: "Canadian Dollar", flag: "\u{1F1E8}\u{1F1E6}" }
|
|
25
|
+
];
|
|
26
|
+
const DEFAULT_CURRENCY = AMOUNT_CURRENCIES[0];
|
|
27
|
+
function findCurrency(code, list) {
|
|
28
|
+
return list.find((c) => c.code === code) ?? list[0] ?? DEFAULT_CURRENCY;
|
|
29
|
+
}
|
|
30
|
+
const AmountInput = React.forwardRef(
|
|
31
|
+
({
|
|
32
|
+
className,
|
|
33
|
+
inputClassName,
|
|
34
|
+
size,
|
|
35
|
+
value,
|
|
36
|
+
onChange,
|
|
37
|
+
currencies: currenciesProp,
|
|
38
|
+
showCurrency = true,
|
|
39
|
+
placeholder,
|
|
40
|
+
disabled,
|
|
41
|
+
...props
|
|
42
|
+
}, ref) => {
|
|
43
|
+
const t = useT();
|
|
44
|
+
const resolvedPlaceholder = placeholder ?? t("ui.inputs.amountInput.placeholder", "0.00");
|
|
45
|
+
const currencies = currenciesProp ?? AMOUNT_CURRENCIES;
|
|
46
|
+
const current = findCurrency(value.currency, currencies);
|
|
47
|
+
const handleAmountChange = React.useCallback(
|
|
48
|
+
(event) => {
|
|
49
|
+
onChange({ amount: event.target.value, currency: current.code });
|
|
50
|
+
},
|
|
51
|
+
[current.code, onChange]
|
|
52
|
+
);
|
|
53
|
+
const handleCurrencyChange = React.useCallback(
|
|
54
|
+
(nextCode) => {
|
|
55
|
+
onChange({ amount: value.amount, currency: nextCode });
|
|
56
|
+
},
|
|
57
|
+
[onChange, value.amount]
|
|
58
|
+
);
|
|
59
|
+
return /* @__PURE__ */ jsxs(
|
|
60
|
+
"div",
|
|
61
|
+
{
|
|
62
|
+
className: cn(inputWrapperVariants({ size }), "px-0 overflow-hidden", className),
|
|
63
|
+
"data-slot": "amount-input-wrapper",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-w-0 items-center gap-2 pl-3", children: [
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
className: "text-sm text-muted-foreground select-none shrink-0 tabular-nums",
|
|
70
|
+
"aria-hidden": "true",
|
|
71
|
+
children: current.symbol
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
"input",
|
|
76
|
+
{
|
|
77
|
+
ref,
|
|
78
|
+
type: "text",
|
|
79
|
+
inputMode: "decimal",
|
|
80
|
+
value: value.amount,
|
|
81
|
+
onChange: handleAmountChange,
|
|
82
|
+
placeholder: resolvedPlaceholder,
|
|
83
|
+
disabled,
|
|
84
|
+
className: cn(
|
|
85
|
+
inputElementVariants({ size }),
|
|
86
|
+
"pr-3 tabular-nums",
|
|
87
|
+
inputClassName
|
|
88
|
+
),
|
|
89
|
+
...props
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
] }),
|
|
93
|
+
showCurrency ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
94
|
+
/* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "w-px self-stretch bg-input" }),
|
|
95
|
+
/* @__PURE__ */ jsxs(Select, { value: current.code, onValueChange: handleCurrencyChange, disabled, children: [
|
|
96
|
+
/* @__PURE__ */ jsxs(
|
|
97
|
+
SelectTrigger,
|
|
98
|
+
{
|
|
99
|
+
"aria-label": t("ui.inputs.amountInput.currencyLabel", "Currency"),
|
|
100
|
+
className: cn(
|
|
101
|
+
"h-auto w-auto shrink-0 gap-1.5 rounded-none border-0 bg-transparent px-2.5 py-2 shadow-none",
|
|
102
|
+
"hover:bg-muted/40 focus:bg-muted/40 focus-visible:shadow-none focus-visible:border-0",
|
|
103
|
+
"disabled:bg-transparent disabled:hover:bg-transparent"
|
|
104
|
+
),
|
|
105
|
+
children: [
|
|
106
|
+
current.flag ? /* @__PURE__ */ jsx("span", { className: "text-base leading-none", "aria-hidden": "true", children: current.flag }) : null,
|
|
107
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-foreground tabular-nums", children: current.code })
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
/* @__PURE__ */ jsx(SelectContent, { align: "end", children: currencies.map((c) => /* @__PURE__ */ jsxs(SelectItem, { value: c.code, children: [
|
|
112
|
+
c.flag ? /* @__PURE__ */ jsx(SelectItemLeading, { children: /* @__PURE__ */ jsx("span", { className: "text-base leading-none", children: c.flag }) }) : null,
|
|
113
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: c.label }),
|
|
114
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground tabular-nums", children: c.code })
|
|
115
|
+
] }, c.code)) })
|
|
116
|
+
] })
|
|
117
|
+
] }) : null
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
AmountInput.displayName = "AmountInput";
|
|
124
|
+
export {
|
|
125
|
+
AMOUNT_CURRENCIES,
|
|
126
|
+
AmountInput
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=amount-input.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/amount-input.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { VariantProps } from 'class-variance-authority'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { inputWrapperVariants, inputElementVariants } from './input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectItemLeading,\n SelectTrigger,\n} from './select'\n\nexport type AmountCurrency = {\n /** ISO 4217 code (e.g. `'EUR'`, `'USD'`, `'PLN'`). */\n code: string\n /** Symbol shown as the leading prefix inside the amount input (e.g. `'\u20AC'`). */\n symbol: string\n /** Human-readable label for the picker dropdown. */\n label: string\n /** Optional emoji flag (or any leading visual) for the picker row. */\n flag?: string\n}\n\n/**\n * Static currency list \u2014 extend as needed. Order is alphabetical by code so the picker reads\n * predictably; the first entry is the default fallback when `value.currency` is empty or\n * unrecognised.\n */\nexport const AMOUNT_CURRENCIES: AmountCurrency[] = [\n { code: 'EUR', symbol: '\u20AC', label: 'Euro', flag: '\uD83C\uDDEA\uD83C\uDDFA' },\n { code: 'USD', symbol: '$', label: 'US Dollar', flag: '\uD83C\uDDFA\uD83C\uDDF8' },\n { code: 'GBP', symbol: '\u00A3', label: 'Pound Sterling', flag: '\uD83C\uDDEC\uD83C\uDDE7' },\n { code: 'PLN', symbol: 'z\u0142', label: 'Polish Z\u0142oty', flag: '\uD83C\uDDF5\uD83C\uDDF1' },\n { code: 'CHF', symbol: 'CHF', label: 'Swiss Franc', flag: '\uD83C\uDDE8\uD83C\uDDED' },\n { code: 'SEK', symbol: 'kr', label: 'Swedish Krona', flag: '\uD83C\uDDF8\uD83C\uDDEA' },\n { code: 'CZK', symbol: 'K\u010D', label: 'Czech Koruna', flag: '\uD83C\uDDE8\uD83C\uDDFF' },\n { code: 'JPY', symbol: '\u00A5', label: 'Japanese Yen', flag: '\uD83C\uDDEF\uD83C\uDDF5' },\n { code: 'AUD', symbol: 'A$', label: 'Australian Dollar', flag: '\uD83C\uDDE6\uD83C\uDDFA' },\n { code: 'CAD', symbol: 'C$', label: 'Canadian Dollar', flag: '\uD83C\uDDE8\uD83C\uDDE6' },\n]\n\nconst DEFAULT_CURRENCY = AMOUNT_CURRENCIES[0]\n\nexport type AmountValue = {\n /** Raw numeric amount as a string (preserves leading zeros, partial decimals like `'12.'`). */\n amount: string\n /** ISO 4217 currency code from the picker. */\n currency: string\n}\n\nexport type AmountInputProps = Omit<\n React.ComponentPropsWithoutRef<'input'>,\n 'size' | 'type' | 'value' | 'onChange'\n> &\n VariantProps<typeof inputWrapperVariants> & {\n value: AmountValue\n onChange: (next: AmountValue) => void\n /** Override the static currency list (e.g. limit to specific markets). */\n currencies?: AmountCurrency[]\n /** Hide the currency picker entirely \u2014 single-currency surfaces (e.g. settings page). */\n showCurrency?: boolean\n /** Optional className on the wrapper. */\n className?: string\n /** Optional className on the inner `<input>`. */\n inputClassName?: string\n }\n\nfunction findCurrency(code: string, list: AmountCurrency[]): AmountCurrency {\n return list.find((c) => c.code === code) ?? list[0] ?? DEFAULT_CURRENCY\n}\n\n/**\n * Amount input matching Figma `Text Input [1.1]` (node `266:5251`) **Amount** variant \u2014 leading\n * currency symbol inside the input, then a vertical divider, then a `Select`-driven currency\n * picker (flag + code + chevron). Numeric `inputMode=\"decimal\"`.\n *\n * Value shape is `{ amount: string, currency: string }` \u2014 the amount is stored as a string to\n * preserve raw user input (leading zeros, in-progress decimals). Parse to `Number` at the\n * API/persistence boundary.\n */\nexport const AmountInput = React.forwardRef<HTMLInputElement, AmountInputProps>(\n (\n {\n className,\n inputClassName,\n size,\n value,\n onChange,\n currencies: currenciesProp,\n showCurrency = true,\n placeholder,\n disabled,\n ...props\n },\n ref,\n ) => {\n const t = useT()\n const resolvedPlaceholder = placeholder ?? t('ui.inputs.amountInput.placeholder', '0.00')\n const currencies = currenciesProp ?? AMOUNT_CURRENCIES\n const current = findCurrency(value.currency, currencies)\n\n const handleAmountChange = React.useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n onChange({ amount: event.target.value, currency: current.code })\n },\n [current.code, onChange],\n )\n\n const handleCurrencyChange = React.useCallback(\n (nextCode: string) => {\n onChange({ amount: value.amount, currency: nextCode })\n },\n [onChange, value.amount],\n )\n\n return (\n <div\n className={cn(inputWrapperVariants({ size }), 'px-0 overflow-hidden', className)}\n data-slot=\"amount-input-wrapper\"\n >\n <div className=\"flex flex-1 min-w-0 items-center gap-2 pl-3\">\n <span\n className=\"text-sm text-muted-foreground select-none shrink-0 tabular-nums\"\n aria-hidden=\"true\"\n >\n {current.symbol}\n </span>\n <input\n ref={ref}\n type=\"text\"\n inputMode=\"decimal\"\n value={value.amount}\n onChange={handleAmountChange}\n placeholder={resolvedPlaceholder}\n disabled={disabled}\n className={cn(\n inputElementVariants({ size }),\n 'pr-3 tabular-nums',\n inputClassName,\n )}\n {...props}\n />\n </div>\n {showCurrency ? (\n <>\n <div aria-hidden=\"true\" className=\"w-px self-stretch bg-input\" />\n <Select value={current.code} onValueChange={handleCurrencyChange} disabled={disabled}>\n <SelectTrigger\n aria-label={t('ui.inputs.amountInput.currencyLabel', 'Currency')}\n className={cn(\n 'h-auto w-auto shrink-0 gap-1.5 rounded-none border-0 bg-transparent px-2.5 py-2 shadow-none',\n 'hover:bg-muted/40 focus:bg-muted/40 focus-visible:shadow-none focus-visible:border-0',\n 'disabled:bg-transparent disabled:hover:bg-transparent',\n )}\n >\n {current.flag ? (\n <span className=\"text-base leading-none\" aria-hidden=\"true\">{current.flag}</span>\n ) : null}\n <span className=\"text-sm text-foreground tabular-nums\">{current.code}</span>\n </SelectTrigger>\n <SelectContent align=\"end\">\n {currencies.map((c) => (\n <SelectItem key={c.code} value={c.code}>\n {c.flag ? (\n <SelectItemLeading>\n <span className=\"text-base leading-none\">{c.flag}</span>\n </SelectItemLeading>\n ) : null}\n <span className=\"flex-1 truncate\">{c.label}</span>\n <span className=\"ml-2 text-xs text-muted-foreground tabular-nums\">{c.code}</span>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </>\n ) : null}\n </div>\n )\n },\n)\nAmountInput.displayName = 'AmountInput'\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
5
|
+
import { inputWrapperVariants, inputElementVariants } from "./input.js";
|
|
6
|
+
const ButtonInput = React.forwardRef(
|
|
7
|
+
({ className, inputClassName, size, leftIcon, trailingAction, disabled, ...props }, ref) => {
|
|
8
|
+
return /* @__PURE__ */ jsxs(
|
|
9
|
+
"div",
|
|
10
|
+
{
|
|
11
|
+
className: cn(inputWrapperVariants({ size }), "px-0 overflow-hidden", className),
|
|
12
|
+
"data-slot": "button-input-wrapper",
|
|
13
|
+
children: [
|
|
14
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-w-0 items-center gap-2 pl-3 pr-2", children: [
|
|
15
|
+
leftIcon ? /* @__PURE__ */ jsx(
|
|
16
|
+
"span",
|
|
17
|
+
{
|
|
18
|
+
className: "flex shrink-0 items-center text-muted-foreground [&_svg]:size-4",
|
|
19
|
+
"aria-hidden": "true",
|
|
20
|
+
children: leftIcon
|
|
21
|
+
}
|
|
22
|
+
) : null,
|
|
23
|
+
/* @__PURE__ */ jsx(
|
|
24
|
+
"input",
|
|
25
|
+
{
|
|
26
|
+
ref,
|
|
27
|
+
disabled,
|
|
28
|
+
className: cn(
|
|
29
|
+
inputElementVariants({ size }),
|
|
30
|
+
inputClassName
|
|
31
|
+
),
|
|
32
|
+
...props
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
] }),
|
|
36
|
+
/* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "w-px self-stretch bg-input" }),
|
|
37
|
+
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-stretch", children: trailingAction })
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
ButtonInput.displayName = "ButtonInput";
|
|
44
|
+
export {
|
|
45
|
+
ButtonInput
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=button-input.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/button-input.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { VariantProps } from 'class-variance-authority'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { inputWrapperVariants, inputElementVariants } from './input'\n\nexport type ButtonInputProps = Omit<React.ComponentPropsWithoutRef<'input'>, 'size'> &\n VariantProps<typeof inputWrapperVariants> & {\n /** Optional leading icon (decorative `aria-hidden` wrapper). */\n leftIcon?: React.ReactNode\n /**\n * Required interactive trailing element rendered inside the wrapper, after the vertical\n * divider. Typically an `<IconButton>` (copy URL, send, refresh). The slot stays focusable\n * and screen-reader accessible \u2014 do NOT pass a decorative icon here.\n */\n trailingAction: React.ReactNode\n /** Optional className on the wrapper. */\n className?: string\n /** Optional className on the inner `<input>`. */\n inputClassName?: string\n }\n\n/**\n * Button input matching Figma `Text Input [1.1]` (node `266:5251`) **Button** variant \u2014 a text\n * input with an interactive trailing button slot, separated by a vertical divider. Built on the\n * shared `inputWrapperVariants` / `inputElementVariants` CVA so the visual contract matches the\n * foundation `Input` primitive.\n *\n * Common pairings: share-link + copy button, subscribe-email + send button, API key + regenerate\n * button. The trailing slot is rendered AS-IS (no auto-wrapping), so consumers control button\n * type, size, variant, and `aria-label` directly via the passed element.\n */\nexport const ButtonInput = React.forwardRef<HTMLInputElement, ButtonInputProps>(\n ({ className, inputClassName, size, leftIcon, trailingAction, disabled, ...props }, ref) => {\n return (\n <div\n className={cn(inputWrapperVariants({ size }), 'px-0 overflow-hidden', className)}\n data-slot=\"button-input-wrapper\"\n >\n <div className=\"flex flex-1 min-w-0 items-center gap-2 pl-3 pr-2\">\n {leftIcon ? (\n <span\n className=\"flex shrink-0 items-center text-muted-foreground [&_svg]:size-4\"\n aria-hidden=\"true\"\n >\n {leftIcon}\n </span>\n ) : null}\n <input\n ref={ref}\n disabled={disabled}\n className={cn(\n inputElementVariants({ size }),\n inputClassName,\n )}\n {...props}\n />\n </div>\n <div aria-hidden=\"true\" className=\"w-px self-stretch bg-input\" />\n <div className=\"flex shrink-0 items-stretch\">\n {trailingAction}\n </div>\n </div>\n )\n },\n)\nButtonInput.displayName = 'ButtonInput'\n"],
|
|
5
|
+
"mappings": ";AAwCQ,SAEI,KAFJ;AAtCR,YAAY,WAAW;AAEvB,SAAS,UAAU;AACnB,SAAS,sBAAsB,4BAA4B;AA4BpD,MAAM,cAAc,MAAM;AAAA,EAC/B,CAAC,EAAE,WAAW,gBAAgB,MAAM,UAAU,gBAAgB,UAAU,GAAG,MAAM,GAAG,QAAQ;AAC1F,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,GAAG,qBAAqB,EAAE,KAAK,CAAC,GAAG,wBAAwB,SAAS;AAAA,QAC/E,aAAU;AAAA,QAEV;AAAA,+BAAC,SAAI,WAAU,oDACZ;AAAA,uBACC;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,eAAY;AAAA,gBAEX;AAAA;AAAA,YACH,IACE;AAAA,YACJ;AAAA,cAAC;AAAA;AAAA,gBACC;AAAA,gBACA;AAAA,gBACA,WAAW;AAAA,kBACT,qBAAqB,EAAE,KAAK,CAAC;AAAA,kBAC7B;AAAA,gBACF;AAAA,gBACC,GAAG;AAAA;AAAA,YACN;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,eAAY,QAAO,WAAU,8BAA6B;AAAA,UAC/D,oBAAC,SAAI,WAAU,+BACZ,0BACH;AAAA;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;AACA,YAAY,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { CreditCard } from "lucide-react";
|
|
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.js";
|
|
8
|
+
const CARD_BRANDS = [
|
|
9
|
+
{ id: "amex", label: "AMEX", regex: /^3[47]/, format: [4, 6, 5], maxLength: 15, bg: "#006FCF" },
|
|
10
|
+
{ id: "visa", label: "VISA", regex: /^4/, format: [4, 4, 4, 4], maxLength: 16, bg: "#1A1F71" },
|
|
11
|
+
{ id: "mastercard", label: "MC", regex: /^(5[1-5]|2[2-7])/, format: [4, 4, 4, 4], maxLength: 16, bg: "#EB001B" },
|
|
12
|
+
{ id: "discover", label: "DISC", regex: /^(6011|64[4-9]|65|622)/, format: [4, 4, 4, 4], maxLength: 16, bg: "#FF6000" },
|
|
13
|
+
{ id: "diners", label: "DC", regex: /^(36|30[0-5]|309)/, format: [4, 6, 4], maxLength: 14, bg: "#0079BE" },
|
|
14
|
+
{ id: "jcb", label: "JCB", regex: /^35(2[89]|[3-8])/, format: [4, 4, 4, 4], maxLength: 16, bg: "#0E4C96" },
|
|
15
|
+
{ id: "unionpay", label: "UP", regex: /^62/, format: [4, 4, 4, 4], maxLength: 16, bg: "#E21836" }
|
|
16
|
+
];
|
|
17
|
+
const DEFAULT_FORMAT = [4, 4, 4, 4];
|
|
18
|
+
const DEFAULT_MAX_LENGTH = 16;
|
|
19
|
+
function detectBrand(digits, brands) {
|
|
20
|
+
if (!digits) return null;
|
|
21
|
+
for (const brand of brands) {
|
|
22
|
+
if (brand.regex.test(digits)) return brand;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function formatDigits(digits, format) {
|
|
27
|
+
if (!digits) return "";
|
|
28
|
+
const groups = [];
|
|
29
|
+
let cursor = 0;
|
|
30
|
+
for (const groupSize of format) {
|
|
31
|
+
if (cursor >= digits.length) break;
|
|
32
|
+
groups.push(digits.slice(cursor, cursor + groupSize));
|
|
33
|
+
cursor += groupSize;
|
|
34
|
+
}
|
|
35
|
+
if (cursor < digits.length) groups.push(digits.slice(cursor));
|
|
36
|
+
return groups.join(" ");
|
|
37
|
+
}
|
|
38
|
+
function sanitize(input, maxLength) {
|
|
39
|
+
return input.replace(/\D+/g, "").slice(0, maxLength);
|
|
40
|
+
}
|
|
41
|
+
const CardInput = React.forwardRef(
|
|
42
|
+
({
|
|
43
|
+
className,
|
|
44
|
+
inputClassName,
|
|
45
|
+
size,
|
|
46
|
+
value,
|
|
47
|
+
onChange,
|
|
48
|
+
onBrandChange,
|
|
49
|
+
brands: brandsProp,
|
|
50
|
+
showLeadingIcon = true,
|
|
51
|
+
showBrandBadge = true,
|
|
52
|
+
placeholder,
|
|
53
|
+
disabled,
|
|
54
|
+
...props
|
|
55
|
+
}, ref) => {
|
|
56
|
+
const t = useT();
|
|
57
|
+
const brands = brandsProp ?? CARD_BRANDS;
|
|
58
|
+
const sanitizedValue = sanitize(value ?? "", DEFAULT_MAX_LENGTH);
|
|
59
|
+
const detected = detectBrand(sanitizedValue, brands);
|
|
60
|
+
const format = detected?.format ?? DEFAULT_FORMAT;
|
|
61
|
+
const maxLength = detected?.maxLength ?? DEFAULT_MAX_LENGTH;
|
|
62
|
+
const truncated = sanitizedValue.slice(0, maxLength);
|
|
63
|
+
const displayed = formatDigits(truncated, format);
|
|
64
|
+
const resolvedPlaceholder = placeholder ?? t("ui.inputs.cardInput.placeholder", "0000 0000 0000 0000");
|
|
65
|
+
const lastBrandRef = React.useRef(null);
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
const next = detected?.id ?? null;
|
|
68
|
+
if (next !== lastBrandRef.current) {
|
|
69
|
+
lastBrandRef.current = next;
|
|
70
|
+
onBrandChange?.(detected);
|
|
71
|
+
}
|
|
72
|
+
}, [detected, onBrandChange]);
|
|
73
|
+
const handleChange = React.useCallback(
|
|
74
|
+
(event) => {
|
|
75
|
+
const nextBrand = detectBrand(sanitize(event.target.value, DEFAULT_MAX_LENGTH), brands);
|
|
76
|
+
const nextMax = nextBrand?.maxLength ?? DEFAULT_MAX_LENGTH;
|
|
77
|
+
const digits = sanitize(event.target.value, nextMax);
|
|
78
|
+
onChange(digits);
|
|
79
|
+
},
|
|
80
|
+
[brands, onChange]
|
|
81
|
+
);
|
|
82
|
+
return /* @__PURE__ */ jsxs(
|
|
83
|
+
"div",
|
|
84
|
+
{
|
|
85
|
+
className: cn(inputWrapperVariants({ size }), className),
|
|
86
|
+
"data-slot": "card-input-wrapper",
|
|
87
|
+
"data-card-brand": detected?.id ?? "unknown",
|
|
88
|
+
children: [
|
|
89
|
+
showLeadingIcon ? /* @__PURE__ */ jsx(CreditCard, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }) : null,
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
"input",
|
|
92
|
+
{
|
|
93
|
+
ref,
|
|
94
|
+
type: "text",
|
|
95
|
+
inputMode: "numeric",
|
|
96
|
+
autoComplete: "cc-number",
|
|
97
|
+
value: displayed,
|
|
98
|
+
onChange: handleChange,
|
|
99
|
+
placeholder: resolvedPlaceholder,
|
|
100
|
+
disabled,
|
|
101
|
+
className: cn(
|
|
102
|
+
inputElementVariants({ size }),
|
|
103
|
+
"tracking-[0.02em] tabular-nums",
|
|
104
|
+
inputClassName
|
|
105
|
+
),
|
|
106
|
+
...props
|
|
107
|
+
}
|
|
108
|
+
),
|
|
109
|
+
showBrandBadge && detected ? /* @__PURE__ */ jsx(
|
|
110
|
+
"span",
|
|
111
|
+
{
|
|
112
|
+
className: "flex shrink-0 items-center justify-center rounded-sm px-1.5 text-overline font-semibold uppercase tracking-wider tabular-nums leading-none select-none",
|
|
113
|
+
style: {
|
|
114
|
+
backgroundColor: detected.bg,
|
|
115
|
+
color: detected.fg ?? "#ffffff",
|
|
116
|
+
height: "24px",
|
|
117
|
+
minWidth: "32px"
|
|
118
|
+
},
|
|
119
|
+
"aria-hidden": "true",
|
|
120
|
+
children: detected.label
|
|
121
|
+
}
|
|
122
|
+
) : null
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
CardInput.displayName = "CardInput";
|
|
129
|
+
export {
|
|
130
|
+
CARD_BRANDS,
|
|
131
|
+
CardInput
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=card-input.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/card-input.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { CreditCard } from 'lucide-react'\nimport type { VariantProps } from 'class-variance-authority'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { inputWrapperVariants, inputElementVariants } from './input'\n\nexport type CardBrand = {\n /** Stable identifier (e.g. `'visa'`, `'mastercard'`). Used as the `data-card-brand` attribute. */\n id: string\n /** Short label rendered on the trailing badge (e.g. `'VISA'`, `'MC'`). */\n label: string\n /** Regex pattern matched against the digits-only card number. */\n regex: RegExp\n /** Grouping for display (e.g. `[4, 4, 4, 4]` for Visa, `[4, 6, 5]` for Amex). */\n format: number[]\n /** Maximum number of digits allowed (Visa/MC/Disc 16, Amex 15, Diners 14). */\n maxLength: number\n /** Background color for the brand badge. */\n bg: string\n /** Foreground (text) color for the brand badge label. Defaults to white. */\n fg?: string\n}\n\n/**\n * Default card brand list \u2014 covers the most common networks. Order matters: the first matching\n * regex wins, so brands with broader prefixes (e.g. UnionPay `^62`) MUST come after narrower ones\n * (e.g. Discover `^622`). Override with the `brands` prop for region-specific surfaces.\n */\nexport const CARD_BRANDS: CardBrand[] = [\n { id: 'amex', label: 'AMEX', regex: /^3[47]/, format: [4, 6, 5], maxLength: 15, bg: '#006FCF' },\n { id: 'visa', label: 'VISA', regex: /^4/, format: [4, 4, 4, 4], maxLength: 16, bg: '#1A1F71' },\n { id: 'mastercard', label: 'MC', regex: /^(5[1-5]|2[2-7])/, format: [4, 4, 4, 4], maxLength: 16, bg: '#EB001B' },\n { id: 'discover', label: 'DISC', regex: /^(6011|64[4-9]|65|622)/, format: [4, 4, 4, 4], maxLength: 16, bg: '#FF6000' },\n { id: 'diners', label: 'DC', regex: /^(36|30[0-5]|309)/, format: [4, 6, 4], maxLength: 14, bg: '#0079BE' },\n { id: 'jcb', label: 'JCB', regex: /^35(2[89]|[3-8])/, format: [4, 4, 4, 4], maxLength: 16, bg: '#0E4C96' },\n { id: 'unionpay', label: 'UP', regex: /^62/, format: [4, 4, 4, 4], maxLength: 16, bg: '#E21836' },\n]\n\nconst DEFAULT_FORMAT: number[] = [4, 4, 4, 4]\nconst DEFAULT_MAX_LENGTH = 16\n\nfunction detectBrand(digits: string, brands: CardBrand[]): CardBrand | null {\n if (!digits) return null\n for (const brand of brands) {\n if (brand.regex.test(digits)) return brand\n }\n return null\n}\n\nfunction formatDigits(digits: string, format: number[]): string {\n if (!digits) return ''\n const groups: string[] = []\n let cursor = 0\n for (const groupSize of format) {\n if (cursor >= digits.length) break\n groups.push(digits.slice(cursor, cursor + groupSize))\n cursor += groupSize\n }\n if (cursor < digits.length) groups.push(digits.slice(cursor))\n return groups.join(' ')\n}\n\nfunction sanitize(input: string, maxLength: number): string {\n return input.replace(/\\D+/g, '').slice(0, maxLength)\n}\n\nexport type CardInputProps = Omit<\n React.ComponentPropsWithoutRef<'input'>,\n 'size' | 'type' | 'value' | 'onChange'\n> &\n VariantProps<typeof inputWrapperVariants> & {\n /** Controlled value \u2014 digits-only (no spaces). Pass formatted strings if you must; non-digits are stripped. */\n value: string\n /** Called with the new digits-only string. The component handles formatting on display. */\n onChange: (digits: string) => void\n /** Called when the detected brand changes. `null` when nothing matches yet. */\n onBrandChange?: (brand: CardBrand | null) => void\n /** Override the card brand list (e.g. region-specific). */\n brands?: CardBrand[]\n /** Render the leading bank-card icon. Defaults to `true`. */\n showLeadingIcon?: boolean\n /** Render the trailing brand badge. Defaults to `true`. */\n showBrandBadge?: boolean\n /** Optional className on the wrapper. */\n className?: string\n /** Optional className on the inner `<input>`. */\n inputClassName?: string\n }\n\n/**\n * Card-number input matching Figma `Text Input [1.1]` (node `266:5251`) **Card** variant \u2014 leading\n * `CreditCard` icon, the formatted card-number input, and a trailing brand badge that auto-detects\n * the issuer (Visa, Mastercard, Amex, Discover, Diners, JCB, UnionPay) from the typed digits.\n *\n * Built on the shared `inputWrapperVariants` / `inputElementVariants` CVA. Brand detection is\n * regex-based \u2014 no external library dependency. The `value` prop is digits-only; the component\n * formats per brand on display (`[4,4,4,4]` default, `[4,6,5]` Amex, `[4,6,4]` Diners) and emits\n * digits-only via `onChange`.\n */\nexport const CardInput = React.forwardRef<HTMLInputElement, CardInputProps>(\n (\n {\n className,\n inputClassName,\n size,\n value,\n onChange,\n onBrandChange,\n brands: brandsProp,\n showLeadingIcon = true,\n showBrandBadge = true,\n placeholder,\n disabled,\n ...props\n },\n ref,\n ) => {\n const t = useT()\n const brands = brandsProp ?? CARD_BRANDS\n const sanitizedValue = sanitize(value ?? '', DEFAULT_MAX_LENGTH)\n const detected = detectBrand(sanitizedValue, brands)\n const format = detected?.format ?? DEFAULT_FORMAT\n const maxLength = detected?.maxLength ?? DEFAULT_MAX_LENGTH\n const truncated = sanitizedValue.slice(0, maxLength)\n const displayed = formatDigits(truncated, format)\n const resolvedPlaceholder = placeholder ?? t('ui.inputs.cardInput.placeholder', '0000 0000 0000 0000')\n\n const lastBrandRef = React.useRef<string | null>(null)\n React.useEffect(() => {\n const next = detected?.id ?? null\n if (next !== lastBrandRef.current) {\n lastBrandRef.current = next\n onBrandChange?.(detected)\n }\n }, [detected, onBrandChange])\n\n const handleChange = React.useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const nextBrand = detectBrand(sanitize(event.target.value, DEFAULT_MAX_LENGTH), brands)\n const nextMax = nextBrand?.maxLength ?? DEFAULT_MAX_LENGTH\n const digits = sanitize(event.target.value, nextMax)\n onChange(digits)\n },\n [brands, onChange],\n )\n\n return (\n <div\n className={cn(inputWrapperVariants({ size }), className)}\n data-slot=\"card-input-wrapper\"\n data-card-brand={detected?.id ?? 'unknown'}\n >\n {showLeadingIcon ? (\n <CreditCard className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n ) : null}\n <input\n ref={ref}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"cc-number\"\n value={displayed}\n onChange={handleChange}\n placeholder={resolvedPlaceholder}\n disabled={disabled}\n className={cn(\n inputElementVariants({ size }),\n 'tracking-[0.02em] tabular-nums',\n inputClassName,\n )}\n {...props}\n />\n {showBrandBadge && detected ? (\n <span\n className=\"flex shrink-0 items-center justify-center rounded-sm px-1.5 text-overline font-semibold uppercase tracking-wider tabular-nums leading-none select-none\"\n style={{\n backgroundColor: detected.bg,\n color: detected.fg ?? '#ffffff',\n height: '24px',\n minWidth: '32px',\n }}\n aria-hidden=\"true\"\n >\n {detected.label}\n </span>\n ) : null}\n </div>\n )\n },\n)\nCardInput.displayName = 'CardInput'\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -9,19 +9,20 @@ const Dialog = DialogPrimitive.Root;
|
|
|
9
9
|
const DialogTrigger = DialogPrimitive.Trigger;
|
|
10
10
|
const DialogPortal = DialogPrimitive.Portal;
|
|
11
11
|
const DialogClose = DialogPrimitive.Close;
|
|
12
|
-
const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
12
|
+
const DialogOverlay = React.forwardRef(({ className, elevated, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
13
13
|
DialogPrimitive.Overlay,
|
|
14
14
|
{
|
|
15
15
|
ref,
|
|
16
16
|
className: cn(
|
|
17
|
-
"fixed inset-0
|
|
17
|
+
"fixed inset-0 bg-black/50 backdrop-blur-sm transition-opacity data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
18
|
+
elevated ? "z-modal-elevated" : "z-modal",
|
|
18
19
|
className
|
|
19
20
|
),
|
|
20
21
|
...props
|
|
21
22
|
}
|
|
22
23
|
));
|
|
23
24
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
24
|
-
const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
25
|
+
const DialogContent = React.forwardRef(({ className, children, elevated, ...props }, ref) => {
|
|
25
26
|
const t = useT();
|
|
26
27
|
React.useEffect(() => {
|
|
27
28
|
return () => {
|
|
@@ -34,14 +35,15 @@ const DialogContent = React.forwardRef(({ className, children, ...props }, ref)
|
|
|
34
35
|
};
|
|
35
36
|
}, []);
|
|
36
37
|
return /* @__PURE__ */ jsxs(DialogPortal, { children: [
|
|
37
|
-
/* @__PURE__ */ jsx(DialogOverlay, {}),
|
|
38
|
+
/* @__PURE__ */ jsx(DialogOverlay, { elevated }),
|
|
38
39
|
/* @__PURE__ */ jsxs(
|
|
39
40
|
DialogPrimitive.Content,
|
|
40
41
|
{
|
|
41
42
|
ref,
|
|
42
43
|
"data-dialog-content": "",
|
|
43
44
|
className: cn(
|
|
44
|
-
"fixed inset-x-0 bottom-0
|
|
45
|
+
"fixed inset-x-0 bottom-0 flex min-h-[50vh] max-h-[70vh] w-full translate-x-0 translate-y-0 flex-col gap-4 overflow-y-auto rounded-t-2xl border-t bg-card p-6 shadow-lg",
|
|
46
|
+
elevated ? "z-modal-elevated" : "z-modal",
|
|
45
47
|
"sm:inset-auto sm:left-1/2 sm:top-1/2 sm:min-h-0 sm:h-auto sm:w-full sm:max-w-lg sm:max-h-[90vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-xl sm:border",
|
|
46
48
|
"focus-visible:outline-none data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
47
49
|
className
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/primitives/dialog.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nconst Dialog = DialogPrimitive.Root\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = DialogPrimitive.Portal\n\nconst DialogClose = DialogPrimitive.Close\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nconst Dialog = DialogPrimitive.Root\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = DialogPrimitive.Portal\n\nconst DialogClose = DialogPrimitive.Close\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> & {\n /** Render above popovers (z-modal-elevated, 55) instead of the default z-modal (40).\n * Use when this dialog is opened from inside a popover so it isn't occluded. */\n elevated?: boolean\n }\n>(({ className, elevated, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n 'fixed inset-0 bg-black/50 backdrop-blur-sm transition-opacity data-[state=open]:animate-in data-[state=closed]:animate-out',\n elevated ? 'z-modal-elevated' : 'z-modal',\n className\n )}\n {...props}\n />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {\n /** Render above popovers (z-modal-elevated, 55) instead of the default z-modal (40).\n * Set on dialogs that open from inside another popover (e.g. the SaveFilterDialog\n * inside the AdvancedFilterPanel popover) so they aren't hidden behind the popover. */\n elevated?: boolean\n }\n>(({ className, children, elevated, ...props }, ref) => {\n const t = useT()\n\n React.useEffect(() => {\n return () => {\n if (typeof window === 'undefined') return\n window.setTimeout(() => {\n if (document.querySelector('[data-dialog-content][data-state=\"open\"]')) return\n document.body.style.removeProperty('overflow')\n document.body.style.removeProperty('pointer-events')\n }, 0)\n }\n }, [])\n\n return (\n <DialogPortal>\n <DialogOverlay elevated={elevated} />\n <DialogPrimitive.Content\n ref={ref}\n data-dialog-content=\"\"\n className={cn(\n 'fixed inset-x-0 bottom-0 flex min-h-[50vh] max-h-[70vh] w-full translate-x-0 translate-y-0 flex-col gap-4 overflow-y-auto rounded-t-2xl border-t bg-card p-6 shadow-lg',\n elevated ? 'z-modal-elevated' : 'z-modal',\n 'sm:inset-auto sm:left-1/2 sm:top-1/2 sm:min-h-0 sm:h-auto sm:w-full sm:max-w-lg sm:max-h-[90vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-xl sm:border',\n 'focus-visible:outline-none data-[state=open]:animate-in data-[state=closed]:animate-out',\n className,\n )}\n {...props}\n >\n <DialogClose\n data-dialog-close=\"\"\n className=\"absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n aria-label={t('ui.dialog.close.ariaLabel', 'Close')}\n >\n <X className=\"h-4 w-4\" />\n </DialogClose>\n {children}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n})\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\nconst DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />\n)\nDialogHeader.displayName = 'DialogHeader'\n\nconst DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)} {...props} />\n)\nDialogFooter.displayName = 'DialogFooter'\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn('text-lg font-semibold leading-none tracking-tight', className)}\n {...props}\n />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn('text-sm text-muted-foreground', className)}\n {...props}\n />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogTrigger,\n DialogClose,\n DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n}\n"],
|
|
5
|
+
"mappings": ";AAwBE,cAqCI,YArCJ;AAtBF,YAAY,WAAW;AACvB,YAAY,qBAAqB;AACjC,SAAS,SAAS;AAClB,SAAS,UAAU;AACnB,SAAS,YAAY;AAErB,MAAM,SAAS,gBAAgB;AAE/B,MAAM,gBAAgB,gBAAgB;AAEtC,MAAM,eAAe,gBAAgB;AAErC,MAAM,cAAc,gBAAgB;AAEpC,MAAM,gBAAgB,MAAM,WAO1B,CAAC,EAAE,WAAW,UAAU,GAAG,MAAM,GAAG,QACpC;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA,WAAW,qBAAqB;AAAA,MAChC;AAAA,IACF;AAAA,IACC,GAAG;AAAA;AACN,CACD;AACD,cAAc,cAAc,gBAAgB,QAAQ;AAEpD,MAAM,gBAAgB,MAAM,WAQ1B,CAAC,EAAE,WAAW,UAAU,UAAU,GAAG,MAAM,GAAG,QAAQ;AACtD,QAAM,IAAI,KAAK;AAEf,QAAM,UAAU,MAAM;AACpB,WAAO,MAAM;AACX,UAAI,OAAO,WAAW,YAAa;AACnC,aAAO,WAAW,MAAM;AACtB,YAAI,SAAS,cAAc,0CAA0C,EAAG;AACxE,iBAAS,KAAK,MAAM,eAAe,UAAU;AAC7C,iBAAS,KAAK,MAAM,eAAe,gBAAgB;AAAA,MACrD,GAAG,CAAC;AAAA,IACN;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SACE,qBAAC,gBACC;AAAA,wBAAC,iBAAc,UAAoB;AAAA,IACnC;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC;AAAA,QACA,uBAAoB;AAAA,QACpB,WAAW;AAAA,UACT;AAAA,UACA,WAAW,qBAAqB;AAAA,UAChC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,qBAAkB;AAAA,cAClB,WAAU;AAAA,cACV,cAAY,EAAE,6BAA6B,OAAO;AAAA,cAElD,8BAAC,KAAE,WAAU,WAAU;AAAA;AAAA,UACzB;AAAA,UACC;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ,CAAC;AACD,cAAc,cAAc,gBAAgB,QAAQ;AAEpD,MAAM,eAAe,CAAC,EAAE,WAAW,GAAG,MAAM,MAC1C,oBAAC,SAAI,WAAW,GAAG,sDAAsD,SAAS,GAAI,GAAG,OAAO;AAElG,aAAa,cAAc;AAE3B,MAAM,eAAe,CAAC,EAAE,WAAW,GAAG,MAAM,MAC1C,oBAAC,SAAI,WAAW,GAAG,0DAA0D,SAAS,GAAI,GAAG,OAAO;AAEtG,aAAa,cAAc;AAE3B,MAAM,cAAc,MAAM,WAGxB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC;AAAA,IACA,WAAW,GAAG,qDAAqD,SAAS;AAAA,IAC3E,GAAG;AAAA;AACN,CACD;AACD,YAAY,cAAc,gBAAgB,MAAM;AAEhD,MAAM,oBAAoB,MAAM,WAG9B,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAC1B;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC;AAAA,IACA,WAAW,GAAG,iCAAiC,SAAS;AAAA,IACvD,GAAG;AAAA;AACN,CACD;AACD,kBAAkB,cAAc,gBAAgB,YAAY;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Mail } from "lucide-react";
|
|
5
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
|
+
import { Input } from "./input.js";
|
|
7
|
+
const EmailInput = React.forwardRef(
|
|
8
|
+
({ showIcon = true, placeholder, ...props }, ref) => {
|
|
9
|
+
const t = useT();
|
|
10
|
+
const resolvedPlaceholder = placeholder ?? t("ui.inputs.emailInput.placeholder", "name@example.com");
|
|
11
|
+
return /* @__PURE__ */ jsx(
|
|
12
|
+
Input,
|
|
13
|
+
{
|
|
14
|
+
ref,
|
|
15
|
+
type: "email",
|
|
16
|
+
inputMode: "email",
|
|
17
|
+
autoComplete: "email",
|
|
18
|
+
placeholder: resolvedPlaceholder,
|
|
19
|
+
leftIcon: showIcon ? /* @__PURE__ */ jsx(Mail, { "aria-hidden": "true" }) : void 0,
|
|
20
|
+
...props
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
EmailInput.displayName = "EmailInput";
|
|
26
|
+
export {
|
|
27
|
+
EmailInput
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=email-input.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/email-input.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Mail } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Input, type InputProps } from './input'\n\nexport type EmailInputProps = Omit<InputProps, 'leftIcon' | 'type'> & {\n /** Show the leading Mail icon. Defaults to `true` per Figma `Text Input [1.1]` Email variant. */\n showIcon?: boolean\n}\n\n/**\n * Email input matching Figma `Text Input [1.1]` (node `266:5251`) **Email** variant \u2014 a thin\n * `Input` wrapper that hardcodes `type=\"email\"`, `inputMode=\"email\"`, `autoComplete=\"email\"`,\n * and renders a leading `Mail` icon by default. Placeholder defaults to an auto-translated\n * `ui.inputs.emailInput.placeholder`.\n */\nexport const EmailInput = React.forwardRef<HTMLInputElement, EmailInputProps>(\n ({ showIcon = true, placeholder, ...props }, ref) => {\n const t = useT()\n const resolvedPlaceholder = placeholder ?? t('ui.inputs.emailInput.placeholder', 'name@example.com')\n return (\n <Input\n ref={ref}\n type=\"email\"\n inputMode=\"email\"\n autoComplete=\"email\"\n placeholder={resolvedPlaceholder}\n leftIcon={showIcon ? <Mail aria-hidden=\"true\" /> : undefined}\n {...props}\n />\n )\n },\n)\nEmailInput.displayName = 'EmailInput'\n"],
|
|
5
|
+
"mappings": ";AA6B6B;AA3B7B,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,aAA8B;AAahC,MAAM,aAAa,MAAM;AAAA,EAC9B,CAAC,EAAE,WAAW,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ;AACnD,UAAM,IAAI,KAAK;AACf,UAAM,sBAAsB,eAAe,EAAE,oCAAoC,kBAAkB;AACnG,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,WAAU;AAAA,QACV,cAAa;AAAA,QACb,aAAa;AAAA,QACb,UAAU,WAAW,oBAAC,QAAK,eAAY,QAAO,IAAK;AAAA,QAClD,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AACA,WAAW,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|