Skip to content

Commit 7e63836

Browse files
committed
feat: introduce Hairline design system and reuse AFCL across the SPA AdminForth/2005
BREAKING CHANGE: new design tokens and look, Lucide icons and AFCL DropdownMenu in core; SPA moves to Vite 8, vue-router 5, pinia 4, vue-i18n 11 and @vueuse/core 15, so custom components may need updates; plugins adapted to the design system require adminforth 4
1 parent 06af1fd commit 7e63836

87 files changed

Lines changed: 8852 additions & 4436 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Same for adapters, but with `/docs/tutorial/Adapters/<adapter-slug>/` path.
1818
Page names in docusarus should be human readabale. We should not use `AuditLog` but instead we should have `Audit Log` via whitespace.
1919

2020

21+
## Design system
22+
23+
UI rules (tokens, `af-`/`afcl-` class contract, AFCL first, Lucide icons, moving off flowbite) live in [Design concepts](adminforth/design/system-concepts.md), read them before changing UI in core or plugins. States and variants of components are drawn in `adminforth/design/system.html`, the progress of core and plugins is in `adminforth/design/adoption.md`.
24+
2125
## General engineering rules
2226

2327
Write code as if the system contracts are already defined and trusted.

‎adminforth/design/adoption.md‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Design system adoption
2+
3+
Which packages follow the [design concepts](./system-concepts.md): tokens instead of palette colors, AFCL components, Lucide icons, no flowbite. Put ✓ only when the whole package is done and checked in light and dark theme.
4+
5+
Moving a plugin to the design system makes it depend on adminforth 4 (AFCL components and Lucide icons ship with it), so apps on adminforth 3 must not get it with an update: set `peerDependencies.adminforth` to `^4.0.0` and commit with a `BREAKING CHANGE:` footer, semantic-release then publishes a new major (it ignores `feat!:`). Bump `devDependencies.adminforth` together with the lockfile once 4.0.0 is on npm, earlier `pnpm install --frozen-lockfile` fails on CI.
6+
7+
| Package | Done |
8+
|---|---|
9+
| adminforth (core) | |
10+
| @adminforth/agent | |
11+
| @adminforth/audit-log | ✓ |
12+
| @adminforth/auto-remove | no UI |
13+
| @adminforth/background-jobs | ✓ |
14+
| @adminforth/bulk-ai-flow | |
15+
| @adminforth/clone-row | |
16+
| @adminforth/crud-approve-plugin | |
17+
| @adminforth/dashboard | |
18+
| @adminforth/email-invite | |
19+
| @adminforth/email-password-reset | |
20+
| @adminforth/foreign-inline-list | |
21+
| @adminforth/foreign-inline-show | |
22+
| @adminforth/i18n | |
23+
| @adminforth/i18n-static | no UI |
24+
| @adminforth/import-export | ✓ |
25+
| @adminforth/inline-create | |
26+
| @adminforth/json-editor | |
27+
| @adminforth/json-form | |
28+
| @adminforth/list-in-place-edit | ✓ |
29+
| @adminforth/login-captcha | |
30+
| @adminforth/many2many | |
31+
| @adminforth/markdown | |
32+
| @adminforth/mcp | |
33+
| @adminforth/oauth | |
34+
| @adminforth/open-signup | |
35+
| @adminforth/quick-filters | ✓ |
36+
| @adminforth/rich-editor | |
37+
| @adminforth/text-complete | ✓ |
38+
| @adminforth/two-factors-auth | |
39+
| @adminforth/universal-search | |
40+
| @adminforth/upload | |
41+
| @adminforth/user-sessions | |
42+
| @adminforth/user-soft-delete | |
43+
44+
## Left in core
45+
46+
- `afcl/Tooltip.vue` uses the flowbite `Tooltip`, `afcl/DatePicker.vue` and `components/CustomDateRangePicker.vue` use `flowbite-datepicker`.
47+
- The Tooltip import is the last place where core loads `flowbite`, and loading it also runs flowbite's auto-init of `data-*` attributes (`data-tooltip-target`, `data-dropdown-toggle`, …) on page load. Apps and plugins may rely on it (foreign-inline-list uses `data-tooltip-target`), so a native Tooltip has to come together with moving those usages to AFCL.
48+
- Palette classes instead of tokens in `views/PageNotFound.vue`, `views/LoginView.vue`, `views/ListView.vue` (action badge), `renderers/URL.vue`, `afcl/BarChart.vue` (axis labels), the HTML in `utils/utils.ts` and a commented line in `components/Sidebar.vue`.
49+
- Backdrops of `afcl/Modal.vue`, `components/Filters.vue` and `App.vue` use `bg-zinc-900/30…40`, the design has one `--scrim` for them.
50+
51+
## Targets
52+
53+
- If simple - get of flowbite, prefer pure AFCL + Tailwind, move features we luck to AFCL
54+
- Existing icon fonts apart lucide - try to get rid of them if not complex
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Design system concepts
2+
3+
Rules for the AdminForth UI (the "Hairline" theme). States and variants of every component are drawn in [system.html](./system.html), the progress of core and plugins is tracked in [adoption.md](./adoption.md). Add a concept here when a design decision should apply everywhere.
4+
5+
## Theme lives in tokens
6+
7+
- Colors, shadows, radius and fonts are tokens in `adminforth/modules/styles.ts` (`light*` / `dark*` pairs, `alias:` points one token to another). Apps override them via `customization.styles`, so token names are a public contract: change values, never rename or remove tokens.
8+
- Components color themselves with tokens (`bg-lightListTableHeading dark:bg-darkListTableHeading`). Tailwind palette colors (`text-gray-400`, `bg-red-50`, `border-emerald-200`, `zinc-*`, …) are not used in core and plugins at all: they ignore `customization.styles` and drift from the theme. `black` and `white` appear only as a translucent tint over any surface (`hover:bg-black/5`), and only where no token fits.
9+
- Keep the token list short. Reuse a token with the same role before adding one: muted icons and secondary text `InputIconColor`, faint text and empty values (`-`) `InputPlaceholderText`, subtle fill `SecondaryDarken` / `SecondaryLighten`, a softer variant of a token is its opacity modifier (`stroke-lightPrimary/50`). Add a token for a new role, not for a new component, always as a `light*` / `dark*` pair.
10+
- Statuses use `Success`, `Warning` and `Danger` tokens: the base one for text and icons, `*Soft` for the background, `*Border` for the border. Badges, alerts, toasts and diffs take them, e.g. `bg-lightSuccessSoft text-lightSuccess border-lightSuccessBorder`.
11+
- Gray ground, white surfaces, 1px hairline borders. Shadows only on layers that float above the page: popovers, menus, dialogs.
12+
- One ink (near-black) primary button for the main action of a screen, everything else is `secondary`, destructive actions are `danger`. The accent color is the `primary` token: links, focus rings, selection, active states.
13+
- Fonts are Geist and Geist Mono (`@fontsource-variable/*`, OFL), the default radius is 6px (`rounded-default`).
14+
- Controls are 34px high: buttons, icon buttons, inputs, selects, date pickers and the header controls (theme toggle, avatar, plugin buttons). Small buttons (`size="sm"`) and the pagination of tables are 28px.
15+
- Focus ring is 3px of the `FocusRing` token. Buttons, checkboxes and toggles show it only on keyboard focus (`focus:outline-none focus-visible:ring-[3px]`), so a mouse click leaves no ring. Text fields show it on any focus (`focus:ring-[3px]`), the user types there next. A clickable element is a `<button>`, not a `<span>` or `<div>` with `@click`, so it gets keyboard focus at all.
16+
17+
## `af-` and `afcl-` classes are a contract
18+
19+
Apps restyle and test AdminForth by `af-*` and `afcl-*` classes. A restyle never removes such class and never moves it to another element. A new component gets its own `afcl-*` root class. Naming after af-<> is unique and clear. If you see it is missing for elements which are key for restyling anchoring - add it.
20+
21+
## AFCL first
22+
23+
- Build UI in core and plugins from AFCL components (`@/afcl`). A plugin may adjust a few classes of a component via `class` or props like `contentClass`.
24+
- When the same piece of UI shows up in several places, or a piece clearly is a component, add it to AFCL instead of copying markup. Follow AFCL conventions: typed props with `withDefaults`, slots for content, `v-model` for state, `defineExpose` for an imperative API, `mergeClasses` for class overrides, tokens for colors, an `afcl-*` root class.
25+
- A new AFCL component gets a section in the AFCL docs (`documentation/docs/tutorial/03-Customization/15-afcl.md`) and its states and variants in [system.html](./system.html). Docs examples are live, not screenshots: wrap the code blocks of an example into `<AfclPreview id="...">` and the docs build renders them with real AFCL (see `documentation/afcl-preview/vite.config.ts`).
26+
- Example: `DropdownMenu` and `DropdownMenuItem` replaced the hand-made menus of the list (⋯ in the header and in rows), the user menu and the import-export plugin.
27+
28+
## Disabled state
29+
30+
A disabled control gets the native `disabled` attribute (`aria-disabled` on a link, which then has no `href`), not `pointer-events-none`: the attribute also blocks the keyboard and form submit, is announced by screen readers and does not pass the click to a parent. Hover styles are left out for a disabled control.
31+
32+
## Lucide icons
33+
34+
- Use Lucide: `@iconify-prerendered/vue-lucide` in components (`IconPencil`, `IconTrash2`), `lucide:<name>` in config (`icon: 'lucide:download'`).
35+
- 16px (`w-4 h-4`) in controls and menus. Icons are muted (`text-lightInputIconColor`), destructive items color their icon too.
36+
- Don't add icons from other sets; replace flowbite, heroicons and humbleicons icons with Lucide when you touch a component. The old sets stay installed because apps and older plugins still import them.
37+
38+
## Moving off flowbite
39+
40+
flowbite is being removed step by step. Don't add new flowbite usages (`flowbite` JS components, `flowbite-datepicker`, `data-*-toggle` attributes). When you touch code which uses it, replace it with a native Vue implementation, usually an AFCL component. Example: the user menu moved from flowbite `Dropdown` to AFCL `DropdownMenu`.
41+
42+
## SPA dependencies
43+
44+
Fonts and icons are dependencies of `adminforth/spa`. Apps install the SPA with npm or pnpm, so a new SPA dependency goes to both `spa/package-lock.json` and `spa/pnpm-lock.yaml`.

0 commit comments

Comments
 (0)