diff --git a/.gitignore b/.gitignore index 892dc204..4e9d32f5 100644 --- a/.gitignore +++ b/.gitignore @@ -430,6 +430,9 @@ template/SimpleModule.Host/wwwroot/* # Tailwind CSS module scan directory template/SimpleModule.Host/Styles/_scan/ +# Design system preview compiled CSS (derived from theme.css + Tailwind) +docs/design-system/app.css + *.stamp # k6 load test output diff --git a/.verify/screenshot.png b/.verify/screenshot.png new file mode 100644 index 00000000..bca6a584 Binary files /dev/null and b/.verify/screenshot.png differ diff --git a/biome.json b/biome.json index fd3046c6..bc74da7b 100644 --- a/biome.json +++ b/biome.json @@ -50,6 +50,7 @@ "!modules/*/src/*/types.ts", "!packages/SimpleModule.Client/src/routes.ts", "!template/SimpleModule.Host/ClientApp/routes.ts", + "!docs/design-system", "!test-projects" ] } diff --git a/docs/design-system/index.html b/docs/design-system/index.html new file mode 100644 index 00000000..bc01a678 --- /dev/null +++ b/docs/design-system/index.html @@ -0,0 +1,1607 @@ + + + + + +SimpleModule · Design System + + + + + + + + +
+ +
+ + +
+
+ +
+
SimpleModule
+
design system · v2.0
+
+
+
+ Theme: + +
+
+ +
+
+ + +
+

Foundations

+

+ A measured
design system
for SimpleModule. +

+

+ One file of tokens. Two carefully tuned themes. Every component below + is a real piece of the system — toggle the theme to see every surface, + shadow, and accent re-balance. +

+ +
+ + + + +
+
+

Foundations · Typography

+

Two voices, both intentional.

+

+ Fraunces — a variable serif with opsz and SOFT axes — handles every display moment. Geist carries body copy and UI labels. JetBrains Mono for code. +

+
+
+
Display 1 · h1

The quick brown fox

+
Display 2 · h2

The quick brown fox

+
Display 3 · h3

The quick brown fox jumps

+
Body large

The quick brown fox jumps over the lazy dog.

+
Body · default

The quick brown fox jumps over the lazy dog and contemplates its options.

+
Small · caption

Auxiliary text, captions, and metadata.

+
Mono · codeconst result = await fetch('/api/users');
+
Label · uppercasesection heading style
+
+
+ + +
+
+

Foundations · Color

+

An OKLCH-tuned palette.

+

Every color is defined in OKLCH so light and dark variants share the same hue and chroma — only lightness shifts.

+
+ +
+

Brand

+
+
primary
+
primary‑hover
+
primary‑light
+
primary‑subtle
+
accent
+
accent‑hover
+
+
+ +
+

Semantic

+
+
success
+
danger
+
warning
+
info
+
+
+ +
+

Surfaces

+
+
surface
+
surface‑raised
+
surface‑sunken
+
code‑bg
+
+
+ +
+

Text & borders

+
+
text
+
text‑secondary
+
text‑muted
+
border
+
border‑strong
+
text‑inverse
+
+
+
+ + +
+
+

Foundations · Spacing

+

A 4px-rooted scale.

+

Tailwind's default scale, used consistently. Components hit 2 / 3 / 4 / 5 / 6 internally; sections use 12 / 16 / 24.

+
+
+
1 · 4px
+
2 · 8px
+
3 · 12px
+
4 · 16px
+
5 · 20px
+
6 · 24px
+
8 · 32px
+
12 · 48px
+
16 · 64px
+
24 · 96px
+
+
+ + +
+
+

Foundations · Radius

+

Generous, but not playful.

+

Default UI surfaces use rounded-xl (12px). Small controls use rounded-lg (8px).

+
+
+
none0
+
sm4px
+
lg8px
+
xl12px
+
2xl16px
+
full
+
+
+ + +
+
+

Foundations · Elevation

+

Five steps of depth.

+

Shadows tune themselves per mode — heavier & darker in dark, soft & warm in light.

+
+
+
+
xs
+
sm
+
md
+
lg
+
xl
+
+
+
+ + +
+
+

Foundations · Motion

+

Movement with intent.

+

Durations and easings are exposed as CSS variables. Use --ease-out for entries, --ease-in-out for transitions, --ease-spring sparingly for delight.

+
+
+

Durations

+
--duration-instant80ms · click feedback, focus rings
+
--duration-fast150ms · hover states, small ui
+
--duration-base200ms · default for transitions
+
--duration-moderate300ms · accordion, sheet, dropdown
+
--duration-slow500ms · page transitions only
+ +

Easings (live)

+
+
--ease-out
+
--ease-in-out
+
--ease-spring
+
linear (avoid for UI)
+
+
+
+ + +
+
+

Foundations · Layering

+

Z-index, named.

+

Never reach for a number. Pick a semantic layer and let the system order it correctly.

+
+
+
--z-base0 · normal page flow
+
--z-sticky10 · sticky table headers, footnote bars
+
--z-overlay20 · sticky page header
+
--z-sidebar40 · app sidebar above content
+
--z-dropdown50 · menus, popovers, command-k
+
--z-modal60 · dialog + backdrop
+
--z-toast80 · transient notifications
+
--z-tooltip90 · always wins
+ +

Opacity scale

+
--opacity-disabled0.45 · for disabled controls
+
--opacity-muted0.65 · de-emphasized icon, hint
+
--opacity-overlay0.78 · glass / sticky bar
+
--opacity-backdrop0.5 · modal backdrop
+
+
+ + + + +
+
+

Components · Buttons

+

Buttons.

+

Five variants, three sizes, full keyboard focus support. Primary is flat emerald — no gradient.

+
+
+
Variants
+
Sizes
+
With icon
+
Disabled
+
Loading
+
+
+ + +
+
+

Components · Text inputs

+

Text fields.

+

One visual treatment across types — text, email, password, telephone — with a 4px focus ring keyed to primary.

+
+
+
+
+
+
+
+

This field is required.

+
+
+
+
+ + +
+
+

Components · Selection controls

+

Checkboxes, radios, switches.

+

Three control families for boolean and single-choice selection. Switches are reserved for state that takes effect immediately.

+
+
+
Checkbox
+ + + +
+
Radio group
+ + + +
+
Switch
+ Email notifications + Two-factor auth +
+
Slider
+
+
060100
+
+
+
+ + +
+
+

Components · Specialized inputs

+

Search, number, textarea, select, file.

+

Composite controls that build on the base input. Note the optional affordances (icons, steppers, badges).

+
+
+
+ +
+ +
+ + + ⌘K +
+
+ +
+ +
+ + + +
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+ react × + typescript × + tailwind × + +
+
+ +
+ +
+ +

Click to upload or drag and drop

+

PNG, JPG, SVG up to 4 MB

+
+
+ +
+
+
+ + + + +
+
+

Components · Badges

+

Badges & tags.

+

Pill-shaped, semantic. Use sparingly to surface status, not category.

+
+
+
Semantic
ActiveBetaDeprecatedCritical
+
With dot
OnlineDegradedOffline
+
+
+ + +
+
+

Components · Avatars

+

Avatars.

+

Initials by default, image when available. Stack with negative margin and a 2px surface ring.

+
+
+
Sizes
+ AJ + AJ + AJ + AJ +
+
With status
+ AJ + MK + RL +
+
Group
+ AJ + MK + RL + SC + +4 +
+
+
+ + +
+
+

Components · Alerts

+

Alerts.

+

Inline feedback. Persistent — unlike toasts, alerts stay until dismissed or resolved.

+
+
+
Migration completed. 14 modules registered without conflicts.
+
Heads up. A new release is available. See the changelog for breaking changes.
+
Pending migration. One migration is staged but not applied: AddRecoveryCodes.
+
Build failed. Source generator diagnostic SM0014: contract type missing [Dto] attribute.
+
+
+ + +
+
+

Components · Toasts

+

Toasts.

+

Transient. Stacked from bottom-right by default. Auto-dismiss after 5–8s; errors require explicit close.

+
+
+
+
stage · bottom-right stack
+
+
+ +
+

Module published

+

Products module v1.4 is now live.

+
+
+
+ ! +
+

Slow query detected

+

GET /audit/logs · 11.2 ms p50

+
+
+
+ × +
+

Save failed

+

Email field is required.

+
+
+
+
+
+
+ + + + +
+
+

Components · Tooltip & popover

+

Tooltip and popover.

+

Tooltips clarify icons and labels. Popovers carry interactive content. Both anchor to a trigger and respect --z-tooltip / --z-dropdown.

+
+
+
+
+ +
+
+
+ + +
+
+
+
+ tooltip · --z-tooltip + popover · --z-dropdown +
+
+
+ + + + + +
+
+

Components · Dialog

+

Dialog.

+

For destructive confirmations or focused tasks. Always pair with a backdrop and trap focus inside the panel.

+
+
+
+
+
+

Delete module "Products"?

+

+ This will remove the module from this workspace and revoke all referenced contracts. The operation is reversible for 7 days. +

+
+ + +
+
+
+
+
+ + +
+
+

Components · Sheet

+

Sheet / drawer.

+

Side panel for detail or quick edit. Slides in from the right with --duration-moderate + --ease-out.

+
+
+
+
+
+
+
+

Edit user

+

alex@simplemodule.dev

+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+

Components · Tabs

+

Tabs.

+

Underline-style. The active tab inherits primary color and bottom border; tabs do not wrap.

+
+
+
+
Overview
+
Endpoints 24
+
Settings
+
Audit log
+
+
+

Module overview

+

The Products module exposes 24 endpoints across 5 controllers. Health is nominal and latency is within target.

+
+
+
+ + +
+
+

Components · Accordion

+

Accordion.

+

Stack of disclosure panels. One or many can be open at once depending on configuration.

+
+
+
+
What is a SimpleModule module?
+
A module is a self-contained .NET project that exposes endpoints, services, contracts, and React pages. Modules communicate only through contracts or the event bus, never by reaching into another module's internals.
+
+
+
How is the page registry kept in sync?
+
The C# source generator discovers IViewEndpoint implementations and writes them into a generated extension method. The React side requires a matching entry in Pages/index.ts.
+
+
+
Can I share state between modules?
+
No shared mutable state. Use the event bus for fire-and-forget notifications or contracts for synchronous queries.
+
+
+
+ + + + + + + +
+
+

Components · Pagination

+

Pagination.

+

Use under tables. Show page numbers when fewer than 8 pages exist; otherwise condense with ellipses.

+
+
+
+ Showing 1–10 of 142 modules +
+ + 1 + 2 + 3 + + 15 + +
+
+
+
+ + +
+
+

Components · Loading states

+

Loading states.

+

+ Pick the right state for the right wait: skeleton for content shape known in advance, progress for measurable work, spinner for sub-second indeterminate waits. +

+
+
+
+

Skeleton

+
+ + + +
+
+
+
+

Progress

+
+
Importing modules62%
+
Migrating database100%
+
+
+
+

Spinner

+ + Loading… +
+
+
+ + + + +
+
+

Components · Cards

+

Cards.

+

Three styles: .card (flat), .dash-card (hover-lift), .glass-card (translucent, blurred).

+
+
+

card · flat

Standard surface

Grouped content with no interactive intent.

+

dash‑card · hover

Dashboard tile

Hover lifts 1px and expands the shadow.

+

glass‑card · blur

Glass surface

Translucent. Best over an atmospheric background.

+
+
+ + +
+
+

Components · Tables

+

Tables.

+

Default styling applies to every <table>. Use the mono family for numeric columns to keep them aligned.

+
+
+
+ + + + + + + + + + + + + + + + + +
ModuleEndpointStatusLatencyCalls
ProductsGET /products2002.4 ms12,094
OrdersPOST /orders2018.1 ms3,217
UsersGET /users/me3040.9 ms88,401
AuditLogsGET /audit/logs42911.2 ms412
EmailPOST /email/send50042.7 ms17
+
+
+
+ + +
+
+

Patterns · Data grid

+

Data grid.

+

Tables plus toolbar (search + filter + bulk actions) plus pagination. The canonical list view.

+
+
+ +
+
+ + +
+ + + + +
+ + +
+ + 3 selected + + + +
+ + +
+ + + + + + + + + + + + + + + + +
ModuleOwnerHealthEndpoints
ProductsAJAlex J.healthy24
OrdersMKMaya K.healthy18
UsersRLRumi L.degraded12
AuditLogsSCSam C.healthy8
+
+ + +
+ Showing 1–4 of 142 modules +
+ + 1 + 2 + 3 + +
+
+
+
+ + +
+
+

Components · Code

+

Code.

+

Block and inline code share the JetBrains Mono family. The block uses a dark inverted surface in both themes.

+
+
[Module("Products", RoutePrefix = "/products")]
+public sealed class ProductsModule : IModule
+{
+    public void ConfigureServices(IServiceCollection services)
+        => services.AddScoped<IProductRepository, ProductRepository>();
+
+    public void ConfigureEndpoints(IEndpointRouteBuilder app)
+    {
+        // Auto-discovered IEndpoint implementations attach here.
+    }
+}
+
+ + + + +
+
+

Patterns · Top navigation

+

Top navigation bar.

+

For marketing & public-facing pages. Translucent surface using --opacity-overlay + backdrop-blur.

+
+
+
+
+
+ S + SimpleModule +
+ +
+
+ + Get started + +
+
+
+

page content goes here

+
+
+
+ + + + + +
+
+

Patterns · Composed forms

+

Composed forms.

+

End-to-end pattern using glass card, inputs, primary + ghost buttons, and the spacing rhythm.

+
+
+
+
+
S
+

Welcome back

+

Sign in to your SimpleModule account

+
+
+
+ +

New here? Create an account

+
+
+

Anatomy

The login card uses glass-card over the ambient background. Inside: 2.25rem padding, centered logo block, 1.625rem display heading, body in muted secondary, then form groups stacked on the 5-unit (20px) gap.

+

Why a glass card?

On marketing / auth pages, the glass surface separates the form from atmospheric backgrounds without forcing a hard rectangle. In dark mode the inner 1px highlight ring adds depth.

+
+
+
+ + + + + +
+
+

Patterns · Detail view

+

Detail with side panel.

+

Master-detail layout where the right rail carries metadata, activity, or related items. Stacks under content on mobile.

+
+
+
+
+

GET /products/:id

+

Returns the product matching the given identifier. Includes pricing, availability, and the most-recent revision metadata.

+
Schema
Examples
History
+
{
+  "id": "prod_aZx1",
+  "name": "Wide-band Adapter",
+  "price": 24.95,
+  "availability": "in_stock"
+}
+
+ +
+
+
+ + +
+
+

Patterns · Form wizard

+

Stepped form.

+

Reserve for flows with mutually exclusive decisions. Show the full step list — never hide future steps from the user.

+
+
+
+
+ + Basics +
+
+
+ 2 + Contracts +
+
+
+ 3 + Review +
+
+ +

Step 2 · Contracts

+

Choose which interfaces this module exposes to other modules.

+
+
+
+
+
+ + +
+
+
+ + +
+
+

Patterns · Error pages

+

404 / 403 / 500.

+

Same shape, different copy. The number is set in display serif at the top; the action is always actionable.

+
+
+
+

404

+

Page not found

+

Check the URL or head back to the dashboard.

+ +
+
+

403

+

Permission denied

+

You don't have access to this module.

+ +
+
+

500

+

Something broke

+

We've logged the error. Please try again.

+ +
+
+
+ + +
+
+

Patterns · Empty state

+

Empty states.

+

A friendly nudge when there's nothing to show. Always pair with a clear next action.

+
+
+
+ +
+

No modules match this filter

+

Try widening your search, or scaffold a new module to get started.

+
+
+
+ + + + +
+
+

Documentation · Anatomy

+

Component anatomy.

+

The named parts of a button. Every component in this system has a similar anatomy diagram in its source file.

+
+
+
+ +
+
+
Container · padding 0.625rem 2rem, radius 12px
+
Surface · solid --color-primary
+
Leading icon · 16px, stroke 2.2, gap 0.5rem
+
Label · Geist 500, letter-spacing -0.005em
+
Elevation · --shadow-primary
+
Focus ring · 4px --color-primary-ring
+
+
+
+ + +
+
+

Documentation · State matrix

+

Every state, every variant.

+

A state matrix is the contract a component owes the system. Every interactive component should render correctly in each of these states.

+
+
+
+
default
+
hover
+
active
+
focus
+
disabled
+
loading
+
+
+
+ + +
+
+

Documentation · Do's & don'ts

+

Do's and don'ts.

+

The shortest path to consistent design is removing options. Below are the most common foot-guns.

+
+
+
+ Do +

Use a single primary button per surface to focus attention.

+
+
+
+ Don't +

Stack multiple primary buttons. The user can't tell which action you prefer.

+
+
+ +
+ Do +

Reach for an alert when feedback is persistent and contextual.

+
3 endpoints need attention.
+
+
+ Don't +

Use a toast for persistent state — it disappears before users react.

+
+ i +

3 endpoints need attention.

+
+
+ +
+ Do +

Reuse semantic colors. Success is emerald everywhere.

+
+
+ Don't +

Introduce a one-off hex value. If the token isn't there, add one — don't bypass.

+
+
+
+ + +
+
+

Documentation · Accessibility

+

Accessibility.

+

WCAG 2.1 AA is the baseline. Every interactive component must be keyboard-operable and survive a screen-reader.

+
+
+
ContrastBody text ≥ 4.5:1; large text ≥ 3:1; UI controls ≥ 3:1 against adjacent surface.
+
Focus visibleEvery interactive element exposes a 4px focus ring using --color-primary-ring. Never remove the outline.
+
KeyboardAll controls reachable via Tab. Menus and dialogs trap focus while open; Esc always closes.
+
ARIAUse semantic HTML first; reach for ARIA only when no semantic element exists. Active tabs use aria-selected, current page aria-current.
+
Reduced motionHonor prefers-reduced-motion. Skip non-essential transitions; preserve state changes.
+
Color independenceNever convey information by color alone — always pair with text, icon, or position.
+
Touch targetsMinimum 44×44 px hit area for touch. Icon buttons must include accessible names.
+
+
+ + +
+
+

Documentation · Composition

+

Composition rules.

+

What can combine with what. Following these prevents the most common patterns of UI confusion.

+
+
+
ModalMay contain forms, alerts, and tables. May not contain another modal, a sheet, or a sidebar.
+
SheetMay contain forms and tabs. May not contain a modal. Can be replaced by a route in mobile breakpoints.
+
ToastMax 3 stacked. Never contains buttons except a single "undo" affordance. Auto-dismiss between 5–8 s.
+
AlertMay contain inline links and a single dismiss control. May not contain other alerts or toasts.
+
CardMay not be nested more than once deep. If you find yourself wanting card-in-card, you want a list or a section.
+
Primary buttonOne per surface. Pair with secondary / ghost for cancel.
+
TooltipNo interactive content (use popover instead). No tooltip on a touchscreen-only target.
+
+
+ + +
+
+

Documentation · Voice & tone

+

How we write.

+

Direct, technical, warm. We talk to developers — assume competence; never patronize. Errors apologize once and offer recourse.

+
+
+
+ Do +

"Migration completed. 14 modules registered."

+

Outcome first, count second, no exclamation.

+
+
+ Don't +

"🎉 Great job! Your migration was super successful!!"

+

Cheerleading. Emoji noise. The work is its own reward.

+
+
+ Do +

"Couldn't reach the database. Retry, or check the connection string."

+

Plain cause + clear action. No blame.

+
+
+ Don't +

"An unexpected error occurred. Please contact support."

+

Vague + dead-end. Tells the user nothing they can act on.

+
+
+
+ + +
+
+

Documentation · Versioning

+

Versioning & migration.

+

The design system follows semver. The current version is 2.0.0.

+
+
+
MajorVisible visual change to existing components, or token renames. Requires migration notes.
+
MinorNew components, tokens, or non-breaking variants. Safe to upgrade.
+
PatchVisual fixes, accessibility improvements, doc updates. Always safe.
+
+
+

Changelog · v2.0.0

+ 2026-05-22 +
+
    +
  • + breaking + Removed all linear-gradient surface treatments; primary buttons are now flat. +
  • +
  • + added + Motion tokens (--duration-*, --ease-*). +
  • +
  • + added + Z-index and opacity scales. +
  • +
  • + added + Fraunces (display) + Geist (body) typography. +
  • +
  • + changed + Dark mode shifted from cold slate to warm graphite (OKLCH hue 60). +
  • +
+
+
+
+ + +
+
+

Reference · Tokens

+

Token names.

+

Custom properties live under @theme in packages/SimpleModule.Theme.Default/theme.css and override in .dark.

+
+
+
--color-primaryoklch(0.62 0.16 158) · dark: 0.72
+
--color-accentoklch(0.50 0.11 192) · dark: 0.62
+
--color-surfacelight: white · dark: oklch(0.205 0.006 60)
+
--color-textlight: 0.22 · dark: 0.96
+
--font-display"Fraunces", serif
+
--font-body"Geist", system-ui
+
--font-mono"JetBrains Mono"
+
--shadow-lg12px 24px / 0.12 (mode-aware)
+
--duration-base200ms
+
--ease-outcubic-bezier(0.16, 1, 0.3, 1)
+
--z-modal60
+
--opacity-disabled0.45
+
+
+ +
+ SimpleModule design system v2.0 · toggle the theme in the top right +
+ +
+ + + + +
+
+ + + + + diff --git a/modules/AuditLogs/src/SimpleModule.AuditLogs/Pages/Browse.tsx b/modules/AuditLogs/src/SimpleModule.AuditLogs/Pages/Browse.tsx index 159c1536..8f3b7e4b 100644 --- a/modules/AuditLogs/src/SimpleModule.AuditLogs/Pages/Browse.tsx +++ b/modules/AuditLogs/src/SimpleModule.AuditLogs/Pages/Browse.tsx @@ -1,6 +1,13 @@ import { router } from '@inertiajs/react'; import { useTranslation } from '@simplemodule/client/use-translation'; -import { Button, Card, CardContent, PageShell, TooltipProvider } from '@simplemodule/ui'; +import { + Button, + Card, + CardContent, + EmptyState, + PageShell, + TooltipProvider, +} from '@simplemodule/ui'; import { type FormEvent, useState } from 'react'; import { AuditLogsKeys } from '@/Locales/keys'; import type { AuditEntry, AuditQueryRequest } from '@/types'; @@ -141,18 +148,38 @@ export default function Browse({ result, filters }: Props) { {result.items.length === 0 ? ( - -

{t(AuditLogsKeys.Browse.EmptyTitle)}

-

- {hasActiveFilters - ? t(AuditLogsKeys.Browse.EmptyWithFilters) - : t(AuditLogsKeys.Browse.EmptyNoFilters)} -

- {hasActiveFilters && ( - - )} + +
) : ( diff --git a/modules/AuditLogs/src/SimpleModule.AuditLogs/Pages/components/KpiCard.tsx b/modules/AuditLogs/src/SimpleModule.AuditLogs/Pages/components/KpiCard.tsx index ccc3a439..94704c17 100644 --- a/modules/AuditLogs/src/SimpleModule.AuditLogs/Pages/components/KpiCard.tsx +++ b/modules/AuditLogs/src/SimpleModule.AuditLogs/Pages/components/KpiCard.tsx @@ -1,5 +1,10 @@ -import { Card, CardContent } from '@simplemodule/ui'; +import { Stat } from '@simplemodule/ui'; +/** + * Thin wrapper around the shared {@link Stat} component, kept for backwards + * compatibility with existing dashboard call sites. New code should reach for + * `Stat` directly. + */ export function KpiCard({ title, value, @@ -14,18 +19,12 @@ export function KpiCard({ onClick?: () => void; }) { return ( - - -

{title}

-

- {value} -

- {subtitle &&

{subtitle}

} -
-
+ {value} : value} + change={subtitle} + interactive={onClick != null} + onClick={onClick} + /> ); } diff --git a/modules/Dashboard/src/SimpleModule.Dashboard/Pages/Home.tsx b/modules/Dashboard/src/SimpleModule.Dashboard/Pages/Home.tsx index aa05ee8a..f4243694 100644 --- a/modules/Dashboard/src/SimpleModule.Dashboard/Pages/Home.tsx +++ b/modules/Dashboard/src/SimpleModule.Dashboard/Pages/Home.tsx @@ -144,13 +144,7 @@ function LandingView({ isDevelopment }: { isDevelopment: boolean }) { return (
- {/* Inline style required: Tailwind gradient utilities cannot reference CSS custom properties */} -
+
S

diff --git a/modules/Email/src/SimpleModule.Email/Pages/History.tsx b/modules/Email/src/SimpleModule.Email/Pages/History.tsx index d67148da..bb3b625e 100644 --- a/modules/Email/src/SimpleModule.Email/Pages/History.tsx +++ b/modules/Email/src/SimpleModule.Email/Pages/History.tsx @@ -5,6 +5,7 @@ import { Button, Card, CardContent, + EmptyState, PageShell, Table, TableBody, @@ -129,18 +130,38 @@ export default function History({ result, filters }: Props) { {result.items.length === 0 ? ( - -

{t(EmailKeys.History.EmptyTitle)}

-

- {hasActiveFilters - ? t(EmailKeys.History.EmptyWithFilters) - : t(EmailKeys.History.EmptyDescription)} -

- {hasActiveFilters && ( - - )} + +
) : ( diff --git a/modules/Email/src/SimpleModule.Email/Pages/Templates.tsx b/modules/Email/src/SimpleModule.Email/Pages/Templates.tsx index d34a7827..96fd19b3 100644 --- a/modules/Email/src/SimpleModule.Email/Pages/Templates.tsx +++ b/modules/Email/src/SimpleModule.Email/Pages/Templates.tsx @@ -11,6 +11,7 @@ import { DialogFooter, DialogHeader, DialogTitle, + EmptyState, Input, PageShell, Table, @@ -129,11 +130,27 @@ export default function Templates({ result, filters }: Props) { {/* Results Table */} {result.items.length === 0 ? ( - -

{t(EmailKeys.Templates.EmptyTitle)}

-

- {t(EmailKeys.Templates.EmptyDescription)} -

+ +
) : ( diff --git a/modules/RateLimiting/src/SimpleModule.RateLimiting/Pages/components/ActivePoliciesTable.tsx b/modules/RateLimiting/src/SimpleModule.RateLimiting/Pages/components/ActivePoliciesTable.tsx index 9c38397e..ddec9ad0 100644 --- a/modules/RateLimiting/src/SimpleModule.RateLimiting/Pages/components/ActivePoliciesTable.tsx +++ b/modules/RateLimiting/src/SimpleModule.RateLimiting/Pages/components/ActivePoliciesTable.tsx @@ -1,14 +1,18 @@ -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@simplemodule/ui'; +import { + EmptyState, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@simplemodule/ui'; import { PolicyTypeBadge, TargetBadge } from './PolicyBadges'; import type { ActivePolicy } from './rate-limiting-types'; export function ActivePoliciesTable({ policies }: { policies: ActivePolicy[] }) { if (policies.length === 0) { - return ( -

- No active rate limit policies. -

- ); + return ; } return ( diff --git a/modules/RateLimiting/src/SimpleModule.RateLimiting/Pages/components/RulesTable.tsx b/modules/RateLimiting/src/SimpleModule.RateLimiting/Pages/components/RulesTable.tsx index 8fefedd4..3167b350 100644 --- a/modules/RateLimiting/src/SimpleModule.RateLimiting/Pages/components/RulesTable.tsx +++ b/modules/RateLimiting/src/SimpleModule.RateLimiting/Pages/components/RulesTable.tsx @@ -1,5 +1,6 @@ import { Button, + EmptyState, Switch, Table, TableBody, @@ -20,9 +21,10 @@ interface Props { export function RulesTable({ rules, onToggle, onDelete }: Props) { if (rules.length === 0) { return ( -

- No rate limit rules configured yet. -

+ ); } diff --git a/modules/Settings/src/SimpleModule.Settings/Pages/MenuManager.tsx b/modules/Settings/src/SimpleModule.Settings/Pages/MenuManager.tsx index ade60fbe..305d0ceb 100644 --- a/modules/Settings/src/SimpleModule.Settings/Pages/MenuManager.tsx +++ b/modules/Settings/src/SimpleModule.Settings/Pages/MenuManager.tsx @@ -6,6 +6,7 @@ import { CardContent, CardHeader, CardTitle, + EmptyState, PageShell, ScrollArea, Tooltip, @@ -187,24 +188,25 @@ export default function MenuManager({ menuItems: initial, availablePages }: Menu {menus.length === 0 ? ( -
- -

- {t(SettingsKeys.MenuManager.EmptyTitle)} -

-

- {t(SettingsKeys.MenuManager.EmptyDescription)} -

-
+