Skip to content

Commit f3bb088

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth
2 parents 30fae5c + 9db6f90 commit f3bb088

12 files changed

Lines changed: 159 additions & 39 deletions

File tree

‎adminforth/design/system-concepts.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Rules for the AdminForth UI (the "Hairline" theme). States and variants of every
99
- 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`, page dimming behind dialogs, modals and drawers `Scrim` (a photo lightbox stays `bg-black/*`, it is dark in both themes), 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.
1010
- 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`.
1111
- 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.
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. An app which wants its brand color on the primary button aliases the `Buttons*` tokens to `Primary` in `customization.styles` (see the branding docs); core and plugins keep styling a button with the `Buttons*` tokens, never with `Primary`.
1313
- Fonts are Geist and Geist Mono (`@fontsource-variable/*`, OFL), the default radius is 6px (`rounded-default`).
1414
- 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.
1515
- 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.

‎adminforth/modules/styleGenerator.ts‎

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -314,34 +314,40 @@ export class StylesGenerator {
314314
return plainCustomStyles;
315315
}
316316

317-
private changeAlias(str:any, mergedStyles:any){
318-
const {aliasMatch,opacityMatch,darkenMatch,lightenMatch, inverseMatch} = parseColorForAliases(str);
317+
// a token may alias a token which is itself an alias further down the list, so the target is resolved first;
318+
// `chain` is the tokens being resolved above this one and catches a cycle such as a -> b -> a
319+
private resolveColor(key: string, colors: Record<string, string>, chain: string[] = []): string {
320+
const { aliasMatch, opacityMatch, darkenMatch, lightenMatch, inverseMatch } = parseColorForAliases(colors[key]);
319321
if (!aliasMatch) {
320-
return str;
321-
} else {
322-
const alias = aliasMatch[1];
323-
let opacity = opacityMatch ? parseFloat(opacityMatch[1]) : 1;
324-
const color = mergedStyles[alias];
325-
if (darkenMatch) {
326-
return darkenRGBA(createRGBA(color, opacity))
327-
}
328-
if (lightenMatch){
329-
return lightenRGBA(createRGBA(color, opacity))
330-
}
331-
if (inverseMatch){
332-
return inverseRGBA(createRGBA(color, opacity))
333-
}
334-
return createRGBA(color, opacity);
322+
return colors[key];
335323
}
336-
}
337-
338-
mergeStyles() {
339-
let mergedStyles = deepMerge(this.defaultStyles, this.generatePlainStyles(this.styleConfig));
340-
let colors = mergedStyles.colors;
341-
Object.entries(colors).forEach(([key,value])=>{
324+
const alias = aliasMatch[1];
325+
if (!(alias in colors)) {
326+
throw new Error(`Styles color ${key} aliases unknown token ${alias}`);
327+
}
328+
if (alias === key || chain.includes(alias)) {
329+
throw new Error(`Styles color alias cycle: ${[...chain, key, alias].join(' -> ')}`);
330+
}
331+
const opacity = opacityMatch ? parseFloat(opacityMatch[1]) : 1;
332+
const color = createRGBA(this.resolveColor(alias, colors, [...chain, key]), opacity);
333+
if (darkenMatch) {
334+
return darkenRGBA(color);
335+
}
336+
if (lightenMatch) {
337+
return lightenRGBA(color);
338+
}
339+
if (inverseMatch) {
340+
return inverseRGBA(color);
341+
}
342+
return color;
343+
}
342344

343-
colors[key] = this.changeAlias(value,colors)
344-
})
345+
mergeStyles() {
346+
const mergedStyles = deepMerge(this.defaultStyles, this.generatePlainStyles(this.styleConfig));
347+
const colors = mergedStyles.colors;
348+
Object.keys(colors).forEach((key) => {
349+
colors[key] = this.resolveColor(key, colors);
350+
});
345351
return mergedStyles;
346352
}
347-
}
353+
}

‎adminforth/spa/src/afcl/Button.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
:disabled="props.disabled"
66
:class="buttonClasses"
77
>
8-
<Spinner v-if="props.loader" class="w-4 h-4 text-lightButtonsText dark:text-darkButtonsText fill-lightPrimary dark:fill-darkPrimary" />
8+
<Spinner v-if="props.loader" class="w-4 h-4" />
99
<!-- the spinner takes the place of the icon in a square button -->
1010
<slot v-if="!(props.iconOnly && props.loader)"></slot>
1111
</button>

‎adminforth/spa/src/afcl/JsonViewer.vue‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
</div>
2222
</teleport>
2323
</div>
24-
<div v-else ref="viewerEl" class="afcl-json-viewer group relative flex items-start gap-2 font-mono text-xs leading-5">
24+
<!-- the line width comes from the measured width of the viewer; inline-size containment keeps that width from depending on the
25+
lines, otherwise in a shrink-to-fit parent (a flex-wrap item, a cell of an auto table) every re-wrap resizes the parent and loops -->
26+
<div v-else ref="viewerEl" class="afcl-json-viewer group relative flex items-start gap-2 font-mono text-xs leading-5 [contain:inline-size]">
2527
<span ref="charEl" class="invisible absolute" aria-hidden="true">0</span>
2628
<div class="min-w-0">
2729
<JsonViewerNode :value="value" :depth="0" :last="true" :expandDepth="expandDepth" :lineWidth="lineWidth" />

‎adminforth/spa/src/afcl/Spinner.vue‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<template>
2-
<div role="status">
3-
<svg aria-hidden="true" class="text-lightProgressBarUnfilledColor animate-spin dark:text-darkProgressBarUnfilledColor fill-lightPrimary dark:fill-darkPrimary" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
4-
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
5-
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
2+
<!-- accent arc on a quiet track; inside an AFCL button both are drawn in the text color of the button (index.scss) -->
3+
<div role="status" class="af-spinner">
4+
<svg aria-hidden="true" class="animate-spin" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
5+
<path class="af-spinner-track fill-lightProgressBarUnfilledColor dark:fill-darkProgressBarUnfilledColor" d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"/>
6+
<path class="af-spinner-arc fill-lightPrimary dark:fill-darkPrimary" d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"/>
67
</svg>
78
<span class="sr-only">{{ $t('Loading...') }}</span>
89
</div>

‎adminforth/spa/src/components/ThreeDotsMenu.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
/>
5858
<Spinner
5959
v-if="actionLoadingStates[action.id!]"
60-
class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
60+
class="w-4 h-4"
6161
/>
6262
{{ action.name }}
6363
</span>

‎adminforth/spa/src/index.scss‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ body {
3333
box-shadow: 0 1px 1px rgb(24 24 27 / 0.05);
3434
}
3535

36+
// a spinner inside a button is drawn in the text color of the button, so it reads on ink and on a brand color alike
37+
.afcl-button .af-spinner, .afcl-link-button .af-spinner {
38+
.af-spinner-arc { fill: currentColor; }
39+
.af-spinner-track { fill: currentColor; opacity: 0.2; }
40+
}
41+
3642
*{
3743
-moz-user-select: none;
3844
}

‎adminforth/spa/src/views/ListView.vue‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
class="w-4 h-4 transition duration-75"></component>
6363
<Spinner
6464
v-if="bulkActionLoadingStates[action.id!]"
65-
class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
65+
class="w-4 h-4"
6666
/>
6767
{{ `${action.label} (${checkboxes.length})` }}
6868
<div v-if="action.badge" class="inline-flex items-center h-[1.125rem] px-1.5 ml-1 rounded-full bg-purple-600 text-white text-[11px] font-semibold">
@@ -85,7 +85,7 @@
8585
class="w-4 h-4 transition duration-75"></component>
8686
<Spinner
8787
v-if="customActionLoadingStates[action.id!]"
88-
class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
88+
class="w-4 h-4"
8989
/>
9090
{{ `${action.name} (${checkboxes.length})` }}
9191
</Button>

‎adminforth/spa/src/views/ShowView.vue‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
class="h-[2.125rem] text-lightListViewButtonText dark:text-darkListViewButtonText px-3"
1919
variant="secondary"
2020
>
21-
<Spinner v-if="isFetchingNextPage" class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText" />
21+
<Spinner v-if="isFetchingNextPage" class="w-4 h-4" />
2222
{{ $t('Next') }}
2323
</LinkButton>
2424

@@ -44,7 +44,7 @@
4444
/>
4545
<Spinner
4646
v-if="actionLoadingStates[action.id!]"
47-
class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
47+
class="w-4 h-4"
4848
/>
4949
{{ action.name }}
5050
</Button>

‎dev-demo/index.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,16 @@ export const admin = new AdminForth({
106106
styles:{
107107
colors: {
108108
light: {
109-
// color for buttons, links, etc.
109+
// color for links, focus rings and other accents
110110
primary: '#16537e',
111+
// the action buttons follow the brand color instead of ink
112+
buttons: {
113+
// background: 'alias:lightPrimary',
114+
// border: 'alias:lightPrimary',
115+
// hover: 'alias:lightPrimary darken',
116+
// borderHover: 'alias:lightPrimary darken',
117+
// text: 'alias:lightPrimary inverse',
118+
},
111119
// color for sidebar and text
112120
sidebar: {
113121
main:'#232323',
@@ -126,6 +134,13 @@ export const admin = new AdminForth({
126134
},
127135
announcementBG: '#262626',
128136
announcementText: '#E5E5E5',
137+
buttons: {
138+
background: 'alias:darkPrimary',
139+
border: 'alias:darkPrimary',
140+
hover: 'alias:darkPrimary darken',
141+
borderHover: 'alias:darkPrimary darken',
142+
text: 'alias:darkPrimary inverse',
143+
},
129144
}
130145
}
131146
}

0 commit comments

Comments
 (0)