@morphemecms/cms 0.9.0-next-20261105122349 → 0.9.0
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/CHANGELOG.md +17 -1
- package/defaults/collections.ts +110 -56
- package/defaults/editor.ts +2 -2
- package/defaults/texts.ts +14 -5
- package/demo/src/content/pages/app-store.md +0 -1
- package/demo/src/content/pages/blocks.md +0 -1
- package/icons/lucide/arrows-horizontal.svg +1 -0
- package/icons/lucide/columns-plus-left.svg +1 -0
- package/icons/lucide/columns-plus-right.svg +1 -0
- package/icons/lucide/file.svg +1 -0
- package/icons/material-symbols/arrows-horizontal.svg +1 -0
- package/icons/material-symbols/columns-plus-left.svg +1 -0
- package/icons/material-symbols/columns-plus-right.svg +1 -0
- package/icons/material-symbols/file.svg +1 -0
- package/icons/phosphor/arrows-horizontal.svg +1 -0
- package/icons/phosphor/columns-plus-left.svg +1 -0
- package/icons/phosphor/columns-plus-right.svg +1 -0
- package/icons/phosphor/file.svg +1 -0
- package/lib/fold-open.d.mts +2 -0
- package/lib/fold-open.mjs +22 -0
- package/lib/fold-open.test.mjs +24 -0
- package/lib/menu.test.mjs +16 -3
- package/lib/page-template.d.mts +54 -0
- package/lib/page-template.mjs +126 -0
- package/lib/page-template.test.mjs +129 -0
- package/lib/side-block.d.mts +2 -16
- package/lib/side-block.mjs +12 -30
- package/lib/side-block.test.mjs +13 -30
- package/package.json +1 -1
- package/scripts/check-side.mjs +49 -22
- package/scripts/editor-roundtrip.mjs +15 -6
- package/scripts/fixtures/side/side-full.md +8 -0
- package/scripts/fixtures/side/side-left.md +8 -0
- package/scripts/fixtures/side/side-none.md +7 -0
- package/scripts/fixtures/side/side-right.md +8 -0
- package/scripts/fixtures/side/side-single.md +8 -0
- package/scripts/side-width.mjs +29 -0
- package/scripts/side-width.test.mjs +30 -0
- package/site/content.config.ts +3 -4
- package/site/header.d.mts +21 -0
- package/site/header.mjs +35 -0
- package/site/header.test.mjs +59 -0
- package/site/icons.d.mts +16 -0
- package/site/icons.mjs +8 -0
- package/site/site.ts +8 -3
- package/site/templates.ts +7 -0
- package/site/theme.mjs +3 -0
- package/src/App.tsx +12 -24
- package/src/AppList.tsx +36 -14
- package/src/AppSettingsTab.tsx +5 -3
- package/src/BlockDialog.tsx +50 -5
- package/src/DataFileFields.tsx +35 -0
- package/src/DataForm.tsx +16 -38
- package/src/Deleted.tsx +7 -3
- package/src/EntryForm.tsx +41 -88
- package/src/FooterSettings.tsx +76 -0
- package/src/FormFrame.tsx +2 -1
- package/src/HeaderSettings.tsx +97 -0
- package/src/Library.tsx +26 -14
- package/src/MarkdownEditor.tsx +18 -36
- package/src/Media.tsx +6 -3
- package/src/Redirects.tsx +12 -9
- package/src/SectionSettings.tsx +21 -0
- package/src/SectionSettingsIcon.tsx +10 -0
- package/src/SettingsSections.tsx +57 -0
- package/src/ThemeStore.tsx +28 -9
- package/src/Versions.tsx +3 -15
- package/src/busyAction.d.mts +3 -0
- package/src/busyAction.mjs +8 -0
- package/src/busyAction.test.mjs +16 -0
- package/src/dataFileValues.ts +30 -0
- package/src/editor.css +37 -3
- package/src/fields.tsx +29 -10
- package/src/footerPreview.d.mts +46 -0
- package/src/footerPreview.mjs +84 -0
- package/src/footerPreview.test.mjs +100 -0
- package/src/formSections.ts +100 -0
- package/src/frontmatter.mjs +1 -1
- package/src/frontmatter.test.mjs +11 -11
- package/src/headerPreview.d.mts +48 -0
- package/src/headerPreview.mjs +168 -0
- package/src/headerPreview.test.mjs +183 -0
- package/src/headerTheme.tsx +58 -0
- package/src/i18n/da-DK/common.ts +1 -0
- package/src/i18n/da-DK/library.ts +2 -0
- package/src/i18n/da-DK/preview.ts +2 -0
- package/src/i18n/da-DK/text.ts +1 -0
- package/src/i18n/en-US/common.ts +1 -0
- package/src/i18n/en-US/library.ts +4 -0
- package/src/i18n/en-US/preview.ts +3 -0
- package/src/i18n/en-US/text.ts +2 -1
- package/src/images.ts +7 -2
- package/src/menuChildren.ts +1 -3
- package/src/movedRoutes.d.mts +3 -0
- package/src/movedRoutes.mjs +35 -0
- package/src/movedRoutes.test.mjs +22 -0
- package/src/pagePreview.tsx +40 -29
- package/src/partPreview.tsx +59 -0
- package/src/schema.ts +7 -1
- package/src/sheetPlacement.d.mts +3 -0
- package/src/sheetPlacement.mjs +9 -0
- package/src/sheetPlacement.test.mjs +23 -0
- package/src/standard/views.ts +3 -3
- package/src/templateField.ts +28 -0
- package/src/themes.ts +3 -0
- package/src/ui.tsx +35 -8
- package/src/useDataFiles.ts +94 -0
- package/starter/package.json +1 -1
- package/theme/components/Footer.astro +14 -7
- package/theme/components/Header.astro +58 -33
- package/theme/components/MobileNav.astro +5 -4
- package/theme/components/RelatedPosts.astro +1 -1
- package/theme/components/headerParts.ts +17 -0
- package/theme/layouts/PageLayout.astro +20 -16
- package/theme/styles/base.css +6 -5
- package/theme/styles/blocks.css +8 -12
- package/theme/styles/components.css +8 -1
- package/theme/styles/global.css +5 -9
- package/theme/styles/ui.css +111 -5
- package/theme/theme.json +1 -0
- package/theme/ui/BusyContent.tsx +16 -0
- package/theme/ui/Button.tsx +25 -5
- package/theme/ui/IconButton.tsx +17 -4
- package/scripts/fixtures/side/side-off.md +0 -8
- package/scripts/fixtures/side/side-plain.md +0 -7
- package/scripts/fixtures/side/side-wide.md +0 -8
package/theme/styles/ui.css
CHANGED
|
@@ -43,6 +43,14 @@
|
|
|
43
43
|
--button-toggle-selected-surface, -text, -hover, -shadow, -hover-shadow a filled toggle that is on
|
|
44
44
|
--button-tonal-selected-surface, -text, -hover; --button-elevated-selected-…; --button-outlined-selected-… the
|
|
45
45
|
other kinds on
|
|
46
|
+
A busy button or icon button (busy: disabled, aria-busy, .button-busy or .icon-button-busy; M3 loading indicator): for
|
|
47
|
+
the first delay it looks unchanged (full colours, though it is disabled); then its label and icons go, a button
|
|
48
|
+
shrinks to a circle (its width becomes its height; with reduced motion at once) and a loading indicator in the
|
|
49
|
+
label's colour, without a container, stands in the middle (an icon button keeps its size and the indicator takes
|
|
50
|
+
its icon's).
|
|
51
|
+
--button-busy-delay the wait before it shows (200ms; a shorter wait shows nothing)
|
|
52
|
+
--button-busy-indicator-size a button's indicator (1.5rem, or its icon's size where that is larger)
|
|
53
|
+
--icon-button-busy-indicator-size an icon button's indicator (its icon's size)
|
|
46
54
|
Icon buttons (IconButton.tsx, iconButtonClass): .icon-button with its kind, .icon-button-standard, -filled, -tonal or
|
|
47
55
|
-outlined, its size, .icon-button-xsmall, none (small), -medium, -large or -xlarge, its width, .icon-button-narrow,
|
|
48
56
|
none or -wide, and its shape, none (round) or .icon-button-square. A toggle as a button's (aria-pressed; the filled
|
|
@@ -57,8 +65,8 @@
|
|
|
57
65
|
--icon-button-tonal-selected-surface, -text; --icon-button-outlined-selected-surface, -text the toggles' that are
|
|
58
66
|
not the filled one's
|
|
59
67
|
Set here by the sizes, the widths and the shapes, not by a theme: --button-box, --button-inline, --button-glyph,
|
|
60
|
-
--button-round, --button-square, --button-pressed, --button-corners,
|
|
61
|
-
-square, -pressed and -corners.
|
|
68
|
+
--button-round, --button-square, --button-pressed, --button-corners, --button-busy-after, --button-busy-size,
|
|
69
|
+
--button-busy-from, and --icon-button-box, -ratio, -width, -glyph, -square, -pressed and -corners.
|
|
62
70
|
*/
|
|
63
71
|
/* The classes in the CMS's own code (this package, also from node_modules/, where Tailwind does not look by itself);
|
|
64
72
|
the names in the icon packs' maps (MATERIAL_SYMBOLS, LUCIDE, site/icons.mjs) and the tests' examples are not classes. */
|
|
@@ -207,6 +215,12 @@
|
|
|
207
215
|
--type-label-small-line-height: 1rem;
|
|
208
216
|
--type-label-small-weight: 500;
|
|
209
217
|
--type-label-small-tracking: 0.03125rem;
|
|
218
|
+
/* The lead (an intro paragraph: type-lead, a blog post's first paragraph, a single page's first paragraph). Each is the
|
|
219
|
+
title large's; a theme sets these four to change every lead. */
|
|
220
|
+
--type-lead-size: var(--type-title-large-size);
|
|
221
|
+
--type-lead-line-height: var(--type-title-large-line-height);
|
|
222
|
+
--type-lead-weight: var(--type-title-large-weight);
|
|
223
|
+
--type-lead-tracking: var(--type-title-large-tracking);
|
|
210
224
|
}
|
|
211
225
|
/* A role as a utility, for a theme's own components and layouts: type-title-large, medium:type-headline-small … */
|
|
212
226
|
@utility type-display-large {
|
|
@@ -251,6 +265,12 @@
|
|
|
251
265
|
font-weight: var(--type-title-large-weight);
|
|
252
266
|
letter-spacing: var(--type-title-large-tracking);
|
|
253
267
|
}
|
|
268
|
+
@utility type-lead {
|
|
269
|
+
font-size: var(--type-lead-size);
|
|
270
|
+
line-height: var(--type-lead-line-height);
|
|
271
|
+
font-weight: var(--type-lead-weight);
|
|
272
|
+
letter-spacing: var(--type-lead-tracking);
|
|
273
|
+
}
|
|
254
274
|
@utility type-title-medium {
|
|
255
275
|
font-size: var(--type-title-medium-size);
|
|
256
276
|
line-height: var(--type-title-medium-line-height);
|
|
@@ -352,6 +372,45 @@
|
|
|
352
372
|
opacity: 0.6;
|
|
353
373
|
cursor: default;
|
|
354
374
|
}
|
|
375
|
+
/* Busy (M3 loading indicator): under the delay it looks unchanged. After it the label and icons go and a button
|
|
376
|
+
shrinks to a circle (--button-busy-from is its width, set by Button.tsx); the indicator, in the label's colour,
|
|
377
|
+
stands in the middle. */
|
|
378
|
+
:is(.button-busy, .icon-button-busy) {
|
|
379
|
+
--button-busy-after: var(--button-busy-delay, 200ms);
|
|
380
|
+
position: relative;
|
|
381
|
+
}
|
|
382
|
+
/* Busy is disabled but not dimmed: full colours, the indicator too. */
|
|
383
|
+
:is(.button.button-busy, .icon-button.icon-button-busy):disabled {
|
|
384
|
+
opacity: 1;
|
|
385
|
+
cursor: progress;
|
|
386
|
+
}
|
|
387
|
+
.button-busy {
|
|
388
|
+
--button-busy-size: var(--button-busy-indicator-size, max(1.5rem, var(--button-glyph)));
|
|
389
|
+
overflow: hidden;
|
|
390
|
+
animation: button-busy-shrink var(--motion-spatial) var(--button-busy-after) both;
|
|
391
|
+
}
|
|
392
|
+
.icon-button-busy {
|
|
393
|
+
--button-busy-size: var(--icon-button-busy-indicator-size, var(--icon-button-glyph));
|
|
394
|
+
}
|
|
395
|
+
:is(.button-busy, .icon-button-busy) > .button-busy-content {
|
|
396
|
+
display: contents;
|
|
397
|
+
animation: button-busy-hide 0s var(--button-busy-after) forwards;
|
|
398
|
+
}
|
|
399
|
+
:is(.button-busy, .icon-button-busy) > .button-busy-indicator {
|
|
400
|
+
--loading-indicator-size: var(--button-busy-size);
|
|
401
|
+
--loading-indicator-shape-size: calc(var(--button-busy-size) * 2.375 / 3);
|
|
402
|
+
--loading-indicator-colour: currentColor;
|
|
403
|
+
position: absolute;
|
|
404
|
+
inset: 0;
|
|
405
|
+
margin: auto;
|
|
406
|
+
visibility: hidden;
|
|
407
|
+
animation: button-busy-show 0s var(--button-busy-after) forwards;
|
|
408
|
+
}
|
|
409
|
+
@media (prefers-reduced-motion: reduce) {
|
|
410
|
+
.button-busy {
|
|
411
|
+
animation-duration: 0s;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
355
414
|
/* The sizes: Material's five, the medium one the default. */
|
|
356
415
|
.button-xsmall {
|
|
357
416
|
--button-box: var(--button-xsmall-height, 2.25rem);
|
|
@@ -2045,10 +2104,13 @@
|
|
|
2045
2104
|
/*
|
|
2046
2105
|
Cards (Card.tsx, cardClass; m3.material.io/components/cards): the surface a block or an app's block is, .card with its
|
|
2047
2106
|
kind, .card-elevated (on the surface with its shadow), .card-filled (on the tint) or .card-outlined (on the surface
|
|
2048
|
-
with a thin edge); a card that is a link is an <a class="card …">, the whole card the link, lifted when pointed at;
|
|
2107
|
+
with a thin edge); a card that is a link is an <a class="card …">, the whole card the link, lifted and tinted when pointed at or focused;
|
|
2049
2108
|
.card-dragged while it is dragged to another place. What is on it (its icon, heading, text, picture) is the block's
|
|
2050
2109
|
own (block-…).
|
|
2051
|
-
--card-radius, --card-padding its corners and the room inside it
|
|
2110
|
+
--card-radius, --card-padding its corners and the room inside it (16px, as Material 3's)
|
|
2111
|
+
--card-gap the room between cards (8px; the grid or list of cards sets it as its gap)
|
|
2112
|
+
--card-hover-layer, --card-focus-layer the state layer of a card that links, in its text's colour: pointed at 8%,
|
|
2113
|
+
focused 10% (opacity)
|
|
2052
2114
|
--card-<kind>-surface, --card-<kind>-line, --card-<kind>-shadow each kind's surface, its edge (a 1px line inside
|
|
2053
2115
|
it, transparent for none) and its shadow (0 0 #0000 for none): elevated level 1,
|
|
2054
2116
|
filled and outlined level 0
|
|
@@ -2101,7 +2163,7 @@
|
|
|
2101
2163
|
.card {
|
|
2102
2164
|
display: block;
|
|
2103
2165
|
border-radius: var(--card-radius, var(--block-radius));
|
|
2104
|
-
padding: var(--card-padding,
|
|
2166
|
+
padding: var(--card-padding, 1rem);
|
|
2105
2167
|
}
|
|
2106
2168
|
a.card {
|
|
2107
2169
|
color: inherit;
|
|
@@ -2112,6 +2174,27 @@
|
|
|
2112
2174
|
:where(a.card) {
|
|
2113
2175
|
transition: box-shadow 0.3s var(--block-ease-quick);
|
|
2114
2176
|
}
|
|
2177
|
+
/* The state layer (M3's): a card that is a link or a button, tinted in its text's colour over the whole card in its
|
|
2178
|
+
shape, 8% when pointed at and 10% with focus from the keyboard. It is under the ripple (the ripple is the last in the
|
|
2179
|
+
card) and does not take a click. */
|
|
2180
|
+
:is(a, button).card::before {
|
|
2181
|
+
content: '';
|
|
2182
|
+
position: absolute;
|
|
2183
|
+
inset: 0;
|
|
2184
|
+
border-radius: inherit;
|
|
2185
|
+
background: currentColor;
|
|
2186
|
+
opacity: 0;
|
|
2187
|
+
pointer-events: none;
|
|
2188
|
+
transition: opacity var(--motion-effects);
|
|
2189
|
+
}
|
|
2190
|
+
@media (hover: hover) {
|
|
2191
|
+
:is(a, button:enabled).card:hover::before {
|
|
2192
|
+
opacity: var(--card-hover-layer, 0.08);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
:is(a, button:enabled).card:focus-visible::before {
|
|
2196
|
+
opacity: var(--card-focus-layer, 0.1);
|
|
2197
|
+
}
|
|
2115
2198
|
.card-elevated {
|
|
2116
2199
|
--card-shadow: var(--card-elevated-shadow, var(--elevation-1-rest-shadow));
|
|
2117
2200
|
}
|
|
@@ -5276,6 +5359,26 @@
|
|
|
5276
5359
|
}
|
|
5277
5360
|
}
|
|
5278
5361
|
|
|
5362
|
+
@keyframes button-busy-shrink {
|
|
5363
|
+
from {
|
|
5364
|
+
width: var(--button-busy-from, auto);
|
|
5365
|
+
}
|
|
5366
|
+
to {
|
|
5367
|
+
width: var(--button-box);
|
|
5368
|
+
padding-inline: 0;
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
@keyframes button-busy-hide {
|
|
5372
|
+
to {
|
|
5373
|
+
visibility: hidden;
|
|
5374
|
+
}
|
|
5375
|
+
}
|
|
5376
|
+
@keyframes button-busy-show {
|
|
5377
|
+
to {
|
|
5378
|
+
visibility: visible;
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
|
|
5279
5382
|
/* Material's shapes, each a polygon of the same 60 points, turning three times in all. */
|
|
5280
5383
|
/* prettier-ignore */
|
|
5281
5384
|
@keyframes loading-indicator-morph {
|
|
@@ -6872,6 +6975,9 @@
|
|
|
6872
6975
|
}
|
|
6873
6976
|
@layer fallback {
|
|
6874
6977
|
@media (prefers-reduced-motion: reduce) {
|
|
6978
|
+
:is(a, button).card::before {
|
|
6979
|
+
transition: none;
|
|
6980
|
+
}
|
|
6875
6981
|
.ripple-wave {
|
|
6876
6982
|
animation: ripple-in 105ms linear;
|
|
6877
6983
|
}
|
package/theme/theme.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"thirdParty": [],
|
|
8
8
|
"icons": "material-symbols",
|
|
9
9
|
"motion": "expressive",
|
|
10
|
+
"templates": ["single", "sidebar-right", "sidebar-left"],
|
|
10
11
|
"description": {
|
|
11
12
|
"da-DK": "Hjemmesiden i Googles Material Design 3, som mange kender fra Android: klar, enkel og velkendt.",
|
|
12
13
|
"en-US": "The website in Google's Material Design 3, which many know from Android: clear, simple and familiar."
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The inside of a busy Button or IconButton (.button-busy-content, .button-busy-indicator in ../styles/ui.css): the
|
|
2
|
+
// usual content, hidden after the delay, and a loading indicator (aria-hidden) in the middle. Exposes BusyContent.
|
|
3
|
+
// Reuses loadingIndicatorClass from ./LoadingIndicator.
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
import { loadingIndicatorClass } from './LoadingIndicator';
|
|
6
|
+
|
|
7
|
+
export default function BusyContent({ children }: { children: ReactNode }) {
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<span className="button-busy-content">{children}</span>
|
|
11
|
+
<span className={`${loadingIndicatorClass()} button-busy-indicator`} aria-hidden="true">
|
|
12
|
+
<span className="loading-indicator-shape"></span>
|
|
13
|
+
</span>
|
|
14
|
+
</>
|
|
15
|
+
);
|
|
16
|
+
}
|
package/theme/ui/Button.tsx
CHANGED
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
// is on, where the site draws the icon filled. With `href` it is a link (Link: on the site; in the editor only drawn;
|
|
9
9
|
// never a toggle), else a <button> (`type`, button by default). Other attributes (data-…, aria-…, disabled) go on it, and `className`
|
|
10
10
|
// after its own classes (its width). An Astro component without React gives an element the same classes with
|
|
11
|
-
// buttonClass, and a toggle aria-pressed itself.
|
|
12
|
-
|
|
11
|
+
// buttonClass, and a toggle aria-pressed itself. With `busy` (a <button> only; a link ignores it) the button is
|
|
12
|
+
// disabled, aria-busy and .button-busy while its action runs. Nothing else changes for the first --button-busy-delay
|
|
13
|
+
// (200 ms; M3 loading indicator); then the label and icons go, the button shrinks to a circle (its width becomes its
|
|
14
|
+
// height) and a loading indicator in the label's colour, without a container, stands in the middle (aria-hidden: the
|
|
15
|
+
// button keeps its name). When busy ends, it is the button again.
|
|
16
|
+
import { useEffect, useRef, type ReactNode } from 'react';
|
|
13
17
|
import { Glyph, Svg, iconFile, type IconRef } from '@morphemecms/cms/src/icon';
|
|
14
18
|
import { Link, type ButtonKind } from '@morphemecms/cms/src/standard/view';
|
|
19
|
+
import BusyContent from './BusyContent';
|
|
15
20
|
|
|
16
21
|
export const buttonSizes = ['xsmall', 'small', 'medium', 'large', 'xlarge'] as const;
|
|
17
22
|
export type ButtonSize = (typeof buttonSizes)[number];
|
|
@@ -40,6 +45,7 @@ export default function Button({
|
|
|
40
45
|
size,
|
|
41
46
|
shape,
|
|
42
47
|
selected,
|
|
48
|
+
busy,
|
|
43
49
|
icon,
|
|
44
50
|
trailingIcon,
|
|
45
51
|
href,
|
|
@@ -52,6 +58,7 @@ export default function Button({
|
|
|
52
58
|
size?: ButtonSize;
|
|
53
59
|
shape?: ButtonShape;
|
|
54
60
|
selected?: boolean;
|
|
61
|
+
busy?: boolean;
|
|
55
62
|
icon?: IconRef;
|
|
56
63
|
trailingIcon?: IconRef;
|
|
57
64
|
href?: string;
|
|
@@ -59,7 +66,13 @@ export default function Button({
|
|
|
59
66
|
className?: string;
|
|
60
67
|
children: ReactNode;
|
|
61
68
|
} & Record<string, unknown>) {
|
|
62
|
-
const
|
|
69
|
+
const isBusy = busy === true && href === undefined;
|
|
70
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
71
|
+
// The width it shrinks from (a width cannot move from auto), read while the label is still there.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (isBusy && ref.current) ref.current.style.setProperty('--button-busy-from', `${ref.current.offsetWidth}px`);
|
|
74
|
+
}, [isBusy]);
|
|
75
|
+
const classes = [buttonClass(kind, size, shape), isBusy && 'button-busy', className].filter(Boolean).join(' ');
|
|
63
76
|
const toggle = selected !== undefined && href === undefined;
|
|
64
77
|
const content = (
|
|
65
78
|
<>
|
|
@@ -69,8 +82,15 @@ export default function Button({
|
|
|
69
82
|
</>
|
|
70
83
|
);
|
|
71
84
|
return href === undefined ? (
|
|
72
|
-
<button
|
|
73
|
-
{
|
|
85
|
+
<button
|
|
86
|
+
ref={ref}
|
|
87
|
+
type={type}
|
|
88
|
+
className={classes}
|
|
89
|
+
aria-pressed={toggle ? selected : undefined}
|
|
90
|
+
{...rest}
|
|
91
|
+
{...(isBusy ? { disabled: true, 'aria-busy': true } : {})}
|
|
92
|
+
>
|
|
93
|
+
{isBusy ? <BusyContent>{content}</BusyContent> : content}
|
|
74
94
|
</button>
|
|
75
95
|
) : (
|
|
76
96
|
<Link href={href} className={classes} {...rest}>
|
package/theme/ui/IconButton.tsx
CHANGED
|
@@ -8,9 +8,12 @@
|
|
|
8
8
|
// it filled. `badge`: the theme's Badge at its icon's corner, a number or a dot (true), which `label` should say too. With
|
|
9
9
|
// `href` it is a link (Link: on the site; in the editor only drawn; never a toggle), else a <button type="button">.
|
|
10
10
|
// Other attributes (data-…, disabled) go on it, and `className` after its own classes (its place). An Astro component
|
|
11
|
-
// without React gives an element the same classes with iconButtonClass, and a toggle aria-pressed itself.
|
|
11
|
+
// without React gives an element the same classes with iconButtonClass, and a toggle aria-pressed itself. With `busy`
|
|
12
|
+
// (a <button> only) it is disabled, aria-busy and .icon-button-busy: after --button-busy-delay (200 ms) the icon is
|
|
13
|
+
// replaced by a loading indicator of its size (BusyContent); the button keeps its size.
|
|
12
14
|
import { Glyph, type IconRef } from '@morphemecms/cms/src/icon';
|
|
13
15
|
import Badge from '@theme/ui/Badge';
|
|
16
|
+
import BusyContent from '@theme/ui/BusyContent';
|
|
14
17
|
import { Link, type IconButtonKind } from '@morphemecms/cms/src/standard/view';
|
|
15
18
|
import { ToggleIcon, type ButtonShape, type ButtonSize } from '@theme/ui/Button';
|
|
16
19
|
|
|
@@ -44,6 +47,7 @@ export default function IconButton({
|
|
|
44
47
|
shape,
|
|
45
48
|
selected,
|
|
46
49
|
badge,
|
|
50
|
+
busy,
|
|
47
51
|
href,
|
|
48
52
|
className,
|
|
49
53
|
...rest
|
|
@@ -56,17 +60,26 @@ export default function IconButton({
|
|
|
56
60
|
shape?: ButtonShape;
|
|
57
61
|
selected?: boolean;
|
|
58
62
|
badge?: number | boolean;
|
|
63
|
+
busy?: boolean;
|
|
59
64
|
href?: string;
|
|
60
65
|
className?: string;
|
|
61
66
|
} & Record<string, unknown>) {
|
|
62
|
-
const
|
|
67
|
+
const isBusy = busy === true && href === undefined;
|
|
68
|
+
const classes = [iconButtonClass(kind, size, width, shape), isBusy && 'icon-button-busy', className].filter(Boolean).join(' ');
|
|
63
69
|
const toggle = selected !== undefined && href === undefined;
|
|
64
70
|
const glyph =
|
|
65
71
|
toggle && typeof icon === 'string' ? <ToggleIcon part="icon-button" name={icon} size={22} /> : <Glyph icon={icon} size={22} />;
|
|
66
72
|
const button =
|
|
67
73
|
href === undefined ? (
|
|
68
|
-
<button
|
|
69
|
-
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
aria-label={label}
|
|
77
|
+
className={classes}
|
|
78
|
+
aria-pressed={toggle ? selected : undefined}
|
|
79
|
+
{...rest}
|
|
80
|
+
{...(isBusy ? { disabled: true, 'aria-busy': true } : {})}
|
|
81
|
+
>
|
|
82
|
+
{isBusy ? <BusyContent>{glyph}</BusyContent> : glyph}
|
|
70
83
|
</button>
|
|
71
84
|
) : (
|
|
72
85
|
<Link href={href} aria-label={label} className={classes} {...rest}>
|