create-mercato-app 0.5.1-develop.2856.35de414092 → 0.5.1-develop.2874.77704bccbd

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 (21) hide show
  1. package/package.json +1 -1
  2. package/template/.env.example +7 -0
  3. package/template/src/app/globals.css +70 -8
  4. package/template/src/components/DemoFeedbackWidget.tsx +8 -8
  5. package/template/src/components/GlobalNoticeBars.tsx +7 -7
  6. package/template/src/components/StartPageContent.tsx +9 -9
  7. package/template/src/components/ui/button.tsx +2 -2
  8. package/template/src/components/ui/checkbox.tsx +1 -29
  9. package/template/src/components/ui/input.tsx +1 -1
  10. package/template/src/modules/example/backend/payments/page.tsx +1 -1
  11. package/template/src/modules/example/components/TodosTable.tsx +1 -1
  12. package/template/src/modules/example/widgets/components.ts +3 -3
  13. package/template/src/modules/example/widgets/dashboard/notes/widget.client.tsx +1 -1
  14. package/template/src/modules/example/widgets/dashboard/todos/widget.client.tsx +4 -4
  15. package/template/src/modules/example/widgets/dashboard/welcome/widget.client.tsx +2 -2
  16. package/template/src/modules/example/widgets/injection/catalog-seo-report/widget.client.tsx +4 -4
  17. package/template/src/modules/example/widgets/injection/crud-validation/widget.client.tsx +1 -1
  18. package/template/src/modules/example/widgets/injection/portal-quick-links/widget.client.tsx +3 -3
  19. package/template/src/modules/example/widgets/injection/portal-recent-activity/widget.client.tsx +6 -6
  20. package/template/src/modules/example/widgets/injection/portal-stats/widget.client.tsx +2 -2
  21. package/template/src/modules/example/widgets/injection/sales-todos/widget.client.tsx +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mercato-app",
3
- "version": "0.5.1-develop.2856.35de414092",
3
+ "version": "0.5.1-develop.2874.77704bccbd",
4
4
  "type": "module",
5
5
  "description": "Create a new Open Mercato application",
6
6
  "main": "./dist/index.js",
@@ -107,6 +107,13 @@ NEXT_PUBLIC_OM_EXAMPLE_CHECKOUT_TEST_INJECTIONS_ENABLED=false
107
107
  # Node environment
108
108
  NODE_ENV=development
109
109
 
110
+ # Rate limiting is enabled by default. Auth endpoints (login/signup/reset)
111
+ # cap at 5–20 requests/minute which is normally fine in dev, but can bite
112
+ # when running integration tests against a long-lived dev server. Set
113
+ # OM_INTEGRATION_TEST=true to disable rate limiting globally for test runs.
114
+ # See: apps/docs/docs/framework/security/rate-limiting.mdx
115
+ # OM_INTEGRATION_TEST=false
116
+
110
117
  # Auto-spawn workers when starting the app (default: true)
111
118
  # Set to false to run workers separately in production
112
119
  AUTO_SPAWN_WORKERS=true
@@ -47,9 +47,31 @@ TODO: Fix that latter to have reference by the package names
47
47
  --color-secondary-foreground: var(--secondary-foreground);
48
48
  --color-secondary: var(--secondary);
49
49
  --color-primary-foreground: var(--primary-foreground);
50
+ --color-primary-hover: var(--primary-hover);
50
51
  --color-primary: var(--primary);
51
52
  --color-brand-violet: var(--brand-violet);
52
53
  --color-brand-violet-foreground: var(--brand-violet-foreground);
54
+
55
+ /* Accent indigo — used by selection controls (checkbox/radio/switch) */
56
+ --color-accent-indigo: var(--accent-indigo);
57
+ --color-accent-indigo-foreground: var(--accent-indigo-foreground);
58
+
59
+ /* Disabled control tokens */
60
+ --color-bg-disabled: var(--bg-disabled);
61
+ --color-text-disabled: var(--text-disabled);
62
+ --color-border-disabled: var(--border-disabled);
63
+
64
+ /* Figma focus ring shadow */
65
+ --shadow-focus: 0 0 0 2px var(--focus-ring-inner), 0 0 0 4px var(--focus-ring-outer);
66
+
67
+ /* Social brand colors (theme-invariant) */
68
+ --color-brand-apple: var(--brand-apple);
69
+ --color-brand-github: var(--brand-github);
70
+ --color-brand-x: var(--brand-x);
71
+ --color-brand-google-stroke: var(--brand-google-stroke);
72
+ --color-brand-facebook: var(--brand-facebook);
73
+ --color-brand-dropbox: var(--brand-dropbox);
74
+ --color-brand-linkedin: var(--brand-linkedin);
53
75
  --color-popover-foreground: var(--popover-foreground);
54
76
  --color-popover: var(--popover);
55
77
  --color-card-foreground: var(--card-foreground);
@@ -89,14 +111,16 @@ TODO: Fix that latter to have reference by the package names
89
111
  --font-size-overline: 0.6875rem;
90
112
  --font-size-overline--line-height: 1rem;
91
113
 
92
- /* ═══ Design System: Z-Index Scale ═══ */
93
- --z-base: 0;
94
- --z-sticky: 10;
95
- --z-dropdown: 20;
96
- --z-overlay: 30;
97
- --z-modal: 40;
98
- --z-toast: 50;
99
- --z-tooltip: 60;
114
+ /* ═══ Design System: Z-Index Scale (Tailwind v4 namespace: z-{name} → var(--z-index-{name})) ═══ */
115
+ --z-index-base: 0;
116
+ --z-index-sticky: 10;
117
+ --z-index-dropdown: 20;
118
+ --z-index-overlay: 30;
119
+ --z-index-modal: 40;
120
+ --z-index-toast: 50;
121
+ --z-index-tooltip: 60;
122
+ --z-index-banner: 70;
123
+ --z-index-top: 100;
100
124
  }
101
125
 
102
126
  :root {
@@ -104,6 +128,17 @@ TODO: Fix that latter to have reference by the package names
104
128
  --font-geist-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
105
129
  --font-geist-mono: ui-monospace, "SFMono-Regular", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
106
130
  --radius: 0.625rem;
131
+ /* Focus ring tokens (opt-in via .focus-ring-fancy) */
132
+ --focus-ring-inner: rgba(255, 255, 255, 1);
133
+ --focus-ring-outer: rgba(153, 160, 174, 0.16);
134
+ /* Social brand colors — theme-invariant */
135
+ --brand-apple: #000000;
136
+ --brand-github: #181717;
137
+ --brand-x: #000000;
138
+ --brand-google-stroke: #DADCE0;
139
+ --brand-facebook: #1877F2;
140
+ --brand-dropbox: #0061FF;
141
+ --brand-linkedin: #0A66C2;
107
142
  --background: oklch(1 0 0);
108
143
  --foreground: oklch(0.145 0 0);
109
144
  --card: oklch(1 0 0);
@@ -114,6 +149,12 @@ TODO: Fix that latter to have reference by the package names
114
149
  --primary-foreground: oklch(0.985 0 0);
115
150
  --brand-violet: oklch(0.55 0.2 293);
116
151
  --brand-violet-foreground: oklch(0.985 0 0);
152
+ --primary-hover: oklch(0.145 0 0);
153
+ --accent-indigo: #6366f1;
154
+ --accent-indigo-foreground: #ffffff;
155
+ --bg-disabled: #f7f7f7;
156
+ --text-disabled: #d1d1d1;
157
+ --border-disabled: #ebebeb;
117
158
  --secondary: oklch(0.97 0 0);
118
159
  --secondary-foreground: oklch(0.205 0 0);
119
160
  --muted: oklch(0.97 0 0);
@@ -181,8 +222,18 @@ TODO: Fix that latter to have reference by the package names
181
222
  --status-neutral-icon: oklch(0.556 0 0); /* = --muted-foreground */
182
223
  }
183
224
 
225
+ /* Opt-in Figma-style focus ring: 2px inner ring + 4px outer ring */
226
+ .focus-ring-fancy:focus-visible {
227
+ outline: none;
228
+ box-shadow:
229
+ 0 0 0 2px var(--focus-ring-inner),
230
+ 0 0 0 6px var(--focus-ring-outer);
231
+ }
232
+
184
233
  .dark {
185
234
  color-scheme: dark;
235
+ --focus-ring-inner: var(--background);
236
+ --focus-ring-outer: rgba(255, 255, 255, 0.18);
186
237
  --background: oklch(0.145 0 0);
187
238
  --foreground: oklch(0.985 0 0);
188
239
  --card: oklch(0.205 0 0);
@@ -193,6 +244,12 @@ TODO: Fix that latter to have reference by the package names
193
244
  --primary-foreground: oklch(0.205 0 0);
194
245
  --brand-violet: oklch(0.65 0.2 293);
195
246
  --brand-violet-foreground: oklch(0.985 0 0);
247
+ --primary-hover: oklch(0.85 0 0);
248
+ --accent-indigo: #818cf8;
249
+ --accent-indigo-foreground: #ffffff;
250
+ --bg-disabled: oklch(0.25 0 0);
251
+ --text-disabled: oklch(0.45 0 0);
252
+ --border-disabled: oklch(0.30 0 0);
196
253
  --secondary: oklch(0.269 0 0);
197
254
  --secondary-foreground: oklch(0.985 0 0);
198
255
  --muted: oklch(0.269 0 0);
@@ -267,6 +324,11 @@ TODO: Fix that latter to have reference by the package names
267
324
  body {
268
325
  @apply bg-background text-foreground;
269
326
  }
327
+ /* Native form controls — use the DS indigo accent */
328
+ input[type="checkbox"],
329
+ input[type="radio"] {
330
+ accent-color: var(--accent-indigo);
331
+ }
270
332
  select,
271
333
  select option {
272
334
  background-color: var(--background);
@@ -176,7 +176,7 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
176
176
  <button
177
177
  type="button"
178
178
  onClick={() => { setOpen(true); if (submitState === 'sent') resetForm() }}
179
- className="fixed bottom-6 right-6 z-[60] flex items-center gap-2 rounded-full px-5 py-3 text-sm font-semibold text-white shadow-xl transition-all hover:scale-105 hover:shadow-2xl active:scale-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 animate-[subtle-bounce_2s_ease-in-out_infinite]"
179
+ className="fixed bottom-6 right-6 z-banner flex items-center gap-2 rounded-full px-5 py-3 text-sm font-semibold text-white shadow-xl transition-all hover:scale-105 hover:shadow-2xl active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 animate-[subtle-bounce_2s_ease-in-out_infinite]"
180
180
  style={{
181
181
  background: 'linear-gradient(135deg, #B4F372 0%, #EEFB63 50%, #BC9AFF 100%)',
182
182
  color: '#1B1B1B',
@@ -209,18 +209,18 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
209
209
  </DialogHeader>
210
210
 
211
211
  {submitState === 'sent' ? (
212
- <div className="rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-6 text-center dark:border-emerald-900 dark:bg-emerald-950/40">
213
- <p className="font-medium text-emerald-800 dark:text-emerald-200">
212
+ <div className="rounded-lg border border-status-success-border bg-status-success-bg px-4 py-6 text-center">
213
+ <p className="font-medium text-status-success-text">
214
214
  {t('demoFeedback.dialog.successTitle', 'Thank you!')}
215
215
  </p>
216
- <p className="mt-1 text-sm text-emerald-700 dark:text-emerald-300">
216
+ <p className="mt-1 text-sm text-status-success-text">
217
217
  {t('demoFeedback.dialog.successBody', 'We\u2019ll get back to you shortly.')}
218
218
  </p>
219
219
  </div>
220
220
  ) : (
221
221
  <div className="grid gap-3">
222
222
  {submitError && (
223
- <div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-red-900 dark:bg-red-950/40 dark:text-red-300">
223
+ <div className="rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text">
224
224
  {submitError}
225
225
  </div>
226
226
  )}
@@ -234,9 +234,9 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
234
234
  onChange={(e) => setEmail(e.target.value)}
235
235
  disabled={submitState === 'sending'}
236
236
  aria-invalid={Boolean(fieldErrors.email)}
237
- className={fieldErrors.email ? 'border-red-500 focus-visible:ring-red-500' : undefined}
237
+ className={fieldErrors.email ? 'border-status-error-border aria-invalid:ring-destructive' : undefined}
238
238
  />
239
- {fieldErrors.email && <p className="text-xs text-red-600">{fieldErrors.email}</p>}
239
+ {fieldErrors.email && <p className="text-xs text-status-error-text">{fieldErrors.email}</p>}
240
240
  </div>
241
241
 
242
242
  <textarea
@@ -270,7 +270,7 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
270
270
  {t('demoFeedback.form.privacyLink', 'Privacy Policy')}
271
271
  </a>
272
272
  {fieldErrors.termsAccepted && (
273
- <span className="mt-0.5 block text-red-600">{fieldErrors.termsAccepted}</span>
273
+ <span className="mt-0.5 block text-status-error-text">{fieldErrors.termsAccepted}</span>
274
274
  )}
275
275
  </span>
276
276
  </label>
@@ -54,11 +54,11 @@ export function GlobalNoticeBars({ demoModeEnabled }: { demoModeEnabled: boolean
54
54
  }
55
55
 
56
56
  return (
57
- <div className="pointer-events-none fixed inset-x-0 bottom-4 z-[70] flex flex-col items-center gap-3 px-4">
57
+ <div className="pointer-events-none fixed inset-x-0 bottom-4 z-banner flex flex-col items-center gap-3 px-4">
58
58
  {showDemoNotice ? (
59
- <div className="pointer-events-auto w-full max-w-4xl rounded-lg border border-amber-200 bg-amber-50/90 p-4 shadow-lg backdrop-blur supports-[backdrop-filter]:bg-amber-50/70 dark:border-amber-900/70 dark:bg-amber-950/40">
59
+ <div className="pointer-events-auto w-full max-w-4xl rounded-lg border border-status-warning-border bg-status-warning-bg p-4 shadow-lg backdrop-blur">
60
60
  <div className="flex items-start gap-3">
61
- <div className="flex-1 text-sm text-amber-900 dark:text-amber-50 space-y-1">
61
+ <div className="flex-1 text-sm text-status-warning-text space-y-1">
62
62
  <p className="font-medium">{t('notices.demo.title', 'Demo Environment')}</p>
63
63
  <p>
64
64
  {t('notices.demo.description', 'This instance is provided for demo purposes only. Data may be reset at any time and is not retained for any guaranteed period.')}
@@ -69,22 +69,22 @@ export function GlobalNoticeBars({ demoModeEnabled }: { demoModeEnabled: boolean
69
69
  href="https://github.com/open-mercato"
70
70
  target="_blank"
71
71
  rel="noreferrer"
72
- className="underline font-medium hover:text-amber-800 dark:hover:text-amber-200"
72
+ className="underline font-medium hover:text-status-warning-text"
73
73
  >
74
74
  {t('notices.demo.installLink', 'Install Open Mercato locally')}
75
75
  </a>
76
76
  . {t('notices.demo.reviewLinks', 'Review our')}{' '}
77
- <Link className="underline font-medium hover:text-amber-800 dark:hover:text-amber-200" href="/terms">
77
+ <Link className="underline font-medium hover:text-status-warning-text" href="/terms">
78
78
  {t('common.terms')}
79
79
  </Link>{' '}
80
80
  {t('notices.demo.and', 'and')}{' '}
81
- <Link className="underline font-medium hover:text-amber-800 dark:hover:text-amber-200" href="/privacy">
81
+ <Link className="underline font-medium hover:text-status-warning-text" href="/privacy">
82
82
  {t('common.privacy')}
83
83
  </Link>
84
84
  .
85
85
  </p>
86
86
  </div>
87
- <Button variant="ghost" size="icon" onClick={handleDismissDemo} className="shrink-0 text-amber-900 dark:text-amber-100">
87
+ <Button variant="ghost" size="icon" onClick={handleDismissDemo} className="shrink-0 text-status-warning-text">
88
88
  <X className="size-4" />
89
89
  </Button>
90
90
  </div>
@@ -125,7 +125,7 @@ export function StartPageContent({ showStartPage: initialShowStartPage, showOnbo
125
125
  </div>
126
126
  </div>
127
127
  <div className="md:ml-auto">
128
- <Button asChild className="bg-emerald-600 hover:bg-emerald-700 focus-visible:ring-emerald-600 px-6 py-5 text-base font-semibold text-white shadow-md">
128
+ <Button asChild className="bg-emerald-600 hover:bg-emerald-700 focus-visible:ring-ring px-6 py-5 text-base font-semibold text-white shadow-md">
129
129
  <Link href="/onboarding">
130
130
  {t('startPage.onboarding.cta', 'Start onboarding')}
131
131
  <ArrowRight className="size-4" aria-hidden />
@@ -135,16 +135,16 @@ export function StartPageContent({ showStartPage: initialShowStartPage, showOnbo
135
135
  </section>
136
136
  ) : null}
137
137
 
138
- <section className="rounded-lg border bg-blue-50 dark:bg-blue-950/20 border-blue-200 dark:border-blue-900 p-4">
138
+ <section className="rounded-lg border bg-status-info-bg border-status-info-border p-4">
139
139
  <div className="flex items-start gap-3">
140
- <Info className="size-5 text-blue-600 dark:text-blue-400 shrink-0 mt-0.5" />
140
+ <Info className="size-5 text-status-info-icon shrink-0 mt-0.5" />
141
141
  <div className="flex-1">
142
- <h3 className="text-sm font-semibold text-blue-900 dark:text-blue-100 mb-1">{t('startPage.defaultPassword.title', 'Default Password')}</h3>
143
- <p className="text-sm text-blue-800 dark:text-blue-200">
142
+ <h3 className="text-sm font-semibold text-status-info-text mb-1">{t('startPage.defaultPassword.title', 'Default Password')}</h3>
143
+ <p className="text-sm text-status-info-text">
144
144
  {t('startPage.defaultPassword.description1', 'The default password for all demo accounts is')}{' '}
145
- <code className="px-1.5 py-0.5 rounded bg-blue-100 dark:bg-blue-900 font-mono text-xs">secret</code>.
145
+ <code className="px-1.5 py-0.5 rounded bg-status-info-bg font-mono text-xs">secret</code>.
146
146
  {' '}{t('startPage.defaultPassword.description2', 'To change passwords, use the CLI command:')}{' '}
147
- <code className="px-1.5 py-0.5 rounded bg-blue-100 dark:bg-blue-900 font-mono text-xs">yarn mercato auth set-password --email &lt;email&gt; --password &lt;newPassword&gt;</code>
147
+ <code className="px-1.5 py-0.5 rounded bg-status-info-bg font-mono text-xs">yarn mercato auth set-password --email &lt;email&gt; --password &lt;newPassword&gt;</code>
148
148
  <span className="mt-2 block">{t('startPage.defaultPassword.description3', 'Demo account emails are printed in the terminal output during yarn initialize.')}</span>
149
149
  </p>
150
150
  </div>
@@ -248,7 +248,7 @@ export function StartPageContent({ showStartPage: initialShowStartPage, showOnbo
248
248
  </div>
249
249
  <p className="text-xs text-muted-foreground">
250
250
  {t('startPage.apiResources.baseUrl', 'Current API base URL:')}{' '}
251
- <code className="rounded bg-muted px-2 py-0.5 text-[10px] text-foreground">{baseUrl}</code>
251
+ <code className="rounded bg-muted px-2 py-0.5 text-overline text-foreground">{baseUrl}</code>
252
252
  </p>
253
253
  </section>
254
254
 
@@ -260,7 +260,7 @@ export function StartPageContent({ showStartPage: initialShowStartPage, showOnbo
260
260
  />
261
261
  <label
262
262
  htmlFor="show-start-page"
263
- className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
263
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 cursor-pointer"
264
264
  >
265
265
  {t('startPage.showNextTime', 'Display this start page next time')}
266
266
  </label>
@@ -5,14 +5,14 @@ import { cva, type VariantProps } from "class-variance-authority"
5
5
  import { cn } from "@open-mercato/shared/lib/utils"
6
6
 
7
7
  const buttonVariants = cva(
8
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
9
  {
10
10
  variants: {
11
11
  variant: {
12
12
  default:
13
13
  "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14
14
  destructive:
15
- "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
15
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 aria-invalid:ring-destructive dark:aria-invalid:ring-destructive dark:bg-destructive/10",
16
16
  outline:
17
17
  "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18
18
  secondary:
@@ -1,29 +1 @@
1
- import * as React from "react"
2
- import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
- import { Check } from "lucide-react"
4
-
5
- import { cn } from "@open-mercato/shared/lib/utils"
6
-
7
- const Checkbox = React.forwardRef<
8
- React.ElementRef<typeof CheckboxPrimitive.Root>,
9
- React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
- >(({ className, ...props }, ref) => (
11
- <CheckboxPrimitive.Root
12
- ref={ref}
13
- className={cn(
14
- "peer size-4 shrink-0 rounded-sm border border-input bg-background shadow-xs ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary",
15
- className
16
- )}
17
- {...props}
18
- >
19
- <CheckboxPrimitive.Indicator
20
- className={cn("flex items-center justify-center text-current")}
21
- >
22
- <Check className="size-3.5" />
23
- </CheckboxPrimitive.Indicator>
24
- </CheckboxPrimitive.Root>
25
- ))
26
- Checkbox.displayName = CheckboxPrimitive.Root.displayName
27
-
28
- export { Checkbox }
29
-
1
+ export { Checkbox, checkboxVariants, type CheckboxProps } from '@open-mercato/ui/primitives/checkbox'
@@ -9,7 +9,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
9
9
  data-slot="input"
10
10
  className={cn(
11
11
  "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
12
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-2",
13
13
  "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
14
  className
15
15
  )}
@@ -180,7 +180,7 @@ function StripePaymentForm({
180
180
  }, [clientSecret, elements, onError, onSuccess, stripe, t])
181
181
 
182
182
  return (
183
- <div className="space-y-4 rounded-lg border bg-muted/20 p-4">
183
+ <div className="space-y-4 rounded-lg border bg-muted/30 p-4">
184
184
  <div className="space-y-1">
185
185
  <p className="text-sm font-semibold">{t('example.payments.stripe.form.title', 'Complete Stripe payment')}</p>
186
186
  <p className="text-sm text-muted-foreground">
@@ -72,7 +72,7 @@ function buildBaseColumns(t: (key: string, params?: Record<string, string | numb
72
72
  return (
73
73
  <span className="flex flex-wrap gap-1">
74
74
  {vals.map((v) => (
75
- <span key={v} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border bg-accent/20">
75
+ <span key={v} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border bg-accent/50">
76
76
  {v}
77
77
  </span>
78
78
  ))}
@@ -18,7 +18,7 @@ const alwaysEnabledComponentOverrides: ComponentOverride[] = [
18
18
  React.createElement(
19
19
  'div',
20
20
  {
21
- className: 'rounded-md border border-dotted border-border/40 p-2',
21
+ className: 'rounded-md border border-dotted border-border/70 p-2',
22
22
  'data-testid': 'example-notes-wrapper',
23
23
  },
24
24
  React.createElement(Original, props as object)
@@ -39,7 +39,7 @@ const checkoutTestComponentOverrides: ComponentOverride[] = [
39
39
  React.createElement(
40
40
  'div',
41
41
  {
42
- className: 'rounded-2xl border border-dashed border-blue-300 bg-blue-50/40 p-3',
42
+ className: 'rounded-xl border border-dashed border-blue-300 bg-blue-50/40 p-3',
43
43
  'data-testid': 'example-checkout-summary-wrapper',
44
44
  },
45
45
  React.createElement(Original, props as object)
@@ -57,7 +57,7 @@ const checkoutTestComponentOverrides: ComponentOverride[] = [
57
57
  React.createElement(
58
58
  'div',
59
59
  {
60
- className: 'rounded-2xl border border-dashed border-amber-300 bg-amber-50/40 p-3',
60
+ className: 'rounded-xl border border-dashed border-amber-300 bg-amber-50/40 p-3',
61
61
  'data-testid': 'example-checkout-help-wrapper',
62
62
  },
63
63
  React.createElement(Original, props as object)
@@ -24,7 +24,7 @@ const NotesWidgetClient: React.FC<DashboardWidgetComponentProps<NotesSettings>>
24
24
  </label>
25
25
  <textarea
26
26
  id="dashboard-notes"
27
- className="min-h-[160px] w-full resize-y rounded-md border px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
27
+ className="min-h-[160px] w-full resize-y rounded-md border px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
28
28
  value={value.text}
29
29
  onChange={(event) => onSettingsChange({ text: event.target.value })}
30
30
  placeholder={t('example.widgets.notes.settings.placeholder', 'Write quick notes you want to keep handy.')}
@@ -143,7 +143,7 @@ const TodoWidgetClient: React.FC<DashboardWidgetComponentProps<TodoSettings>> =
143
143
  type="number"
144
144
  min={1}
145
145
  max={20}
146
- className="w-24 rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
146
+ className="w-24 rounded-md border px-2 py-1 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
147
147
  value={value.pageSize}
148
148
  onChange={(event) => onSettingsChange({ ...value, pageSize: Number(event.target.value) })}
149
149
  />
@@ -168,7 +168,7 @@ const TodoWidgetClient: React.FC<DashboardWidgetComponentProps<TodoSettings>> =
168
168
  <div className="flex gap-2">
169
169
  <input
170
170
  type="text"
171
- className="flex-1 rounded-md border px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
171
+ className="flex-1 rounded-md border px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
172
172
  placeholder={t('example.widgets.todo.input.placeholder')}
173
173
  value={draft}
174
174
  onChange={(event) => setDraft(event.target.value)}
@@ -187,14 +187,14 @@ const TodoWidgetClient: React.FC<DashboardWidgetComponentProps<TodoSettings>> =
187
187
  ) : (
188
188
  <ul className="space-y-2">
189
189
  {items.length === 0 ? (
190
- <li className="rounded-md border bg-muted/40 px-3 py-6 text-sm text-muted-foreground text-center">
190
+ <li className="rounded-md border bg-muted/50 px-3 py-6 text-sm text-muted-foreground text-center">
191
191
  {value.showCompleted ? t('example.widgets.todo.state.empty') : t('example.widgets.todo.state.allCaughtUp')}
192
192
  </li>
193
193
  ) : null}
194
194
  {items.map((item) => (
195
195
  <li
196
196
  key={item.id}
197
- className="flex items-center justify-between gap-2 rounded-md border bg-muted/40 px-3 py-2 text-sm"
197
+ className="flex items-center justify-between gap-2 rounded-md border bg-muted/50 px-3 py-2 text-sm"
198
198
  >
199
199
  <label className="flex flex-1 items-center gap-2">
200
200
  <input
@@ -40,7 +40,7 @@ const WelcomeWidgetClient: React.FC<DashboardWidgetComponentProps<WelcomeSetting
40
40
  </label>
41
41
  <input
42
42
  id="welcome-headline"
43
- className="w-full rounded-md border px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
43
+ className="w-full rounded-md border px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
44
44
  value={value.headline}
45
45
  onChange={(event) => handleChange('headline', event.target.value)}
46
46
  placeholder={t('example.widgets.welcome.settings.headlinePlaceholder', 'Welcome back, {{user}}!')}
@@ -55,7 +55,7 @@ const WelcomeWidgetClient: React.FC<DashboardWidgetComponentProps<WelcomeSetting
55
55
  </label>
56
56
  <textarea
57
57
  id="welcome-message"
58
- className="min-h-[120px] w-full resize-y rounded-md border px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
58
+ className="min-h-[120px] w-full resize-y rounded-md border px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
59
59
  value={value.message ?? ''}
60
60
  onChange={(event) => handleChange('message', event.target.value)}
61
61
  placeholder={DEFAULT_SETTINGS.message}
@@ -96,15 +96,15 @@ export default function CatalogSeoReportWidget(_props: InjectionWidgetComponentP
96
96
  ) : loading ? (
97
97
  <p className="mt-2 text-xs text-muted-foreground">{t('common.loading', 'Loading…')}</p>
98
98
  ) : issues.length === 0 ? (
99
- <p className="mt-2 text-xs text-emerald-700">{t('example.widgets.catalogSeoReport.healthy', 'All reviewed items look good!')}</p>
99
+ <p className="mt-2 text-xs text-status-success-text">{t('example.widgets.catalogSeoReport.healthy', 'All reviewed items look good!')}</p>
100
100
  ) : (
101
101
  <ul className="mt-3 space-y-2">
102
102
  {issues.map((issue) => (
103
- <li key={issue.id} className="rounded border border-amber-200 dark:border-amber-900/70 bg-amber-50 dark:bg-amber-950/40 px-3 py-2">
103
+ <li key={issue.id} className="rounded border border-status-warning-border bg-status-warning-bg px-3 py-2">
104
104
  <div className="flex items-center justify-between gap-3">
105
105
  <div>
106
- <div className="text-sm font-medium text-foreground dark:text-amber-50">{issue.title}</div>
107
- <div className="text-[11px] text-amber-800 dark:text-amber-300">{issue.issue}</div>
106
+ <div className="text-sm font-medium text-foreground">{issue.title}</div>
107
+ <div className="text-overline text-status-warning-text">{issue.issue}</div>
108
108
  </div>
109
109
  <Button asChild size="sm" variant="outline">
110
110
  <a href={`/backend/catalog/products/${issue.id}`} className="text-xs">
@@ -72,7 +72,7 @@ export default function ValidationWidget({ context, data, disabled }: InjectionW
72
72
  <div data-testid="widget-transform-display-data" className="text-xs text-muted-foreground">transformDisplayData={print(lastTransformDisplayData)}</div>
73
73
  <div data-testid="widget-transform-validation" className="text-xs text-muted-foreground">transformValidation={print(lastTransformValidation)}</div>
74
74
  <div data-testid="widget-recursive-before-save" className="text-xs text-muted-foreground">recursiveBeforeSave={print(lastRecursiveAddonBeforeSave)}</div>
75
- <div data-testid="widget-recursive-addon-host" className="mt-2 rounded border border-dashed border-border/80 bg-background/60 p-2">
75
+ <div data-testid="widget-recursive-addon-host" className="mt-2 rounded border border-dashed border-border/70 bg-background/80 p-2">
76
76
  <InjectionSpot
77
77
  spotId="widget:example.injection.crud-validation:addon"
78
78
  context={context}
@@ -16,12 +16,12 @@ export default function PortalQuickLinksWidget() {
16
16
  href={link.href}
17
17
  className="flex items-center gap-3 rounded-lg border p-3 transition-colors hover:bg-muted"
18
18
  >
19
- <div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-foreground text-[12px] font-bold text-background">
19
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-foreground text-xs font-bold text-background">
20
20
  {link.icon}
21
21
  </div>
22
22
  <div className="min-w-0">
23
- <p className="text-[13px] font-medium">{link.label}</p>
24
- <p className="text-[11px] text-muted-foreground">{link.description}</p>
23
+ <p className="text-sm font-medium">{link.label}</p>
24
+ <p className="text-overline text-muted-foreground">{link.description}</p>
25
25
  </div>
26
26
  </a>
27
27
  ))}
@@ -10,11 +10,11 @@ const MOCK_ACTIVITY = [
10
10
 
11
11
  function ActivityIcon({ type }: { type: string }) {
12
12
  const colors: Record<string, string> = {
13
- login: 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400',
14
- profile: 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400',
15
- order: 'bg-amber-100 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400',
13
+ login: 'bg-status-success-bg text-status-success-icon',
14
+ profile: 'bg-status-info-bg text-status-info-icon',
15
+ order: 'bg-status-warning-bg text-status-warning-icon',
16
16
  download: 'bg-violet-100 text-violet-600 dark:bg-violet-900/30 dark:text-violet-400',
17
- security: 'bg-rose-100 text-rose-600 dark:bg-rose-900/30 dark:text-rose-400',
17
+ security: 'bg-status-error-bg text-status-error-icon',
18
18
  }
19
19
  return (
20
20
  <div className={`flex size-8 shrink-0 items-center justify-center rounded-lg text-xs font-bold ${colors[type] ?? 'bg-muted text-muted-foreground'}`}>
@@ -30,8 +30,8 @@ export default function PortalRecentActivityWidget() {
30
30
  <div key={item.id} className={`flex items-center gap-3 py-2.5 ${idx > 0 ? 'border-t' : ''}`}>
31
31
  <ActivityIcon type={item.icon} />
32
32
  <div className="min-w-0 flex-1">
33
- <p className="text-[13px] font-medium">{item.action}</p>
34
- <p className="text-[11px] text-muted-foreground">{item.time}</p>
33
+ <p className="text-sm font-medium">{item.action}</p>
34
+ <p className="text-overline text-muted-foreground">{item.time}</p>
35
35
  </div>
36
36
  </div>
37
37
  ))}
@@ -12,13 +12,13 @@ export default function PortalStatsWidget() {
12
12
  <div className="grid grid-cols-2 gap-4">
13
13
  {STATS.map((stat) => (
14
14
  <div key={stat.label}>
15
- <p className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
15
+ <p className="text-overline font-semibold uppercase tracking-wider text-muted-foreground/60">
16
16
  {stat.label}
17
17
  </p>
18
18
  <p className={`mt-0.5 text-2xl font-bold tracking-tight ${stat.color}`}>
19
19
  {stat.value}
20
20
  </p>
21
- <p className="mt-0.5 text-[11px] text-muted-foreground">{stat.trend}</p>
21
+ <p className="mt-0.5 text-overline text-muted-foreground">{stat.trend}</p>
22
22
  </div>
23
23
  ))}
24
24
  </div>
@@ -148,10 +148,10 @@ export default function SalesTodosWidget({ context }: InjectionWidgetComponentPr
148
148
  </Button>
149
149
  </form>
150
150
  {lastEvent ? (
151
- <div className="flex items-center gap-2 rounded bg-blue-50 px-3 py-1.5 text-xs text-blue-700 dark:bg-blue-950 dark:text-blue-300">
152
- <span className="inline-block h-2 w-2 animate-pulse rounded-full bg-blue-500" />
151
+ <div className="flex items-center gap-2 rounded bg-status-info-bg px-3 py-1.5 text-xs text-status-info-text">
152
+ <span className="inline-block h-2 w-2 animate-pulse rounded-full bg-status-info-icon" />
153
153
  SSE Event received: <code className="font-mono">{lastEvent.id}</code>
154
- <span className="text-blue-500/70">
154
+ <span className="text-status-info-text">
155
155
  {new Date(lastEvent.timestamp).toLocaleTimeString()}
156
156
  </span>
157
157
  </div>