@ptbdynamic/plinth 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +258 -2
- package/package.json +99 -4
- package/packages/css/dist/avatar.css +122 -0
- package/packages/css/dist/badge.css +76 -0
- package/packages/css/dist/button.css +273 -0
- package/packages/css/dist/callout.css +143 -0
- package/packages/css/dist/code-block.css +283 -0
- package/packages/css/dist/confirm-dialog.css +183 -0
- package/packages/css/dist/data-table.css +470 -0
- package/packages/css/dist/dialog.css +113 -0
- package/packages/css/dist/drawer.css +156 -0
- package/packages/css/dist/fonts/geist/LICENSE +93 -0
- package/packages/css/dist/fonts/geist/geist-cyrillic-ext-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist/geist-cyrillic-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist/geist-latin-ext-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist/geist-latin-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist/geist-vietnamese-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/LICENSE +93 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-cyrillic-ext-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-cyrillic-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-latin-ext-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-latin-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-symbols2-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-vietnamese-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/index.css +109 -0
- package/packages/css/dist/header.css +110 -0
- package/packages/css/dist/icon.css +51 -0
- package/packages/css/dist/list-card.css +76 -0
- package/packages/css/dist/list-row.css +164 -0
- package/packages/css/dist/menu-sheet.css +84 -0
- package/packages/css/dist/menu.css +245 -0
- package/packages/css/dist/modal.css +367 -0
- package/packages/css/dist/page-header.css +124 -0
- package/packages/css/dist/pill-row.css +197 -0
- package/packages/css/dist/plinth.css +4446 -0
- package/packages/css/dist/section-menu.css +226 -0
- package/packages/css/dist/sheet.css +239 -0
- package/packages/css/dist/text-field.css +230 -0
- package/packages/css/dist/textarea.css +169 -0
- package/packages/css/dist/tokens.css +413 -0
- package/packages/react/dist/PlinthAvatar.d.ts +17 -0
- package/packages/react/dist/PlinthBadge.d.ts +7 -0
- package/packages/react/dist/PlinthButton.d.ts +29 -0
- package/packages/react/dist/PlinthCallout.d.ts +16 -0
- package/packages/react/dist/PlinthCodeBlock.d.ts +24 -0
- package/packages/react/dist/PlinthConfirmDialog.d.ts +38 -0
- package/packages/react/dist/PlinthDataTable.d.ts +11 -0
- package/packages/react/dist/PlinthDataTableCell.d.ts +15 -0
- package/packages/react/dist/PlinthDataTableColumn.d.ts +13 -0
- package/packages/react/dist/PlinthDataTableEmpty.d.ts +9 -0
- package/packages/react/dist/PlinthDataTableRow.d.ts +9 -0
- package/packages/react/dist/PlinthDataTableSort.d.ts +4 -0
- package/packages/react/dist/PlinthDialog.d.ts +26 -0
- package/packages/react/dist/PlinthDrawer.d.ts +29 -0
- package/packages/react/dist/PlinthIcon.d.ts +10 -0
- package/packages/react/dist/PlinthIconButton.d.ts +26 -0
- package/packages/react/dist/PlinthListCard.d.ts +14 -0
- package/packages/react/dist/PlinthListRow.d.ts +29 -0
- package/packages/react/dist/PlinthMenu.d.ts +12 -0
- package/packages/react/dist/PlinthMenuGroup.d.ts +9 -0
- package/packages/react/dist/PlinthMenuItem.d.ts +26 -0
- package/packages/react/dist/PlinthMenuSeparator.d.ts +4 -0
- package/packages/react/dist/PlinthMenuSheet.d.ts +24 -0
- package/packages/react/dist/PlinthPageHeader.d.ts +17 -0
- package/packages/react/dist/PlinthPill.d.ts +17 -0
- package/packages/react/dist/PlinthPillRow.d.ts +13 -0
- package/packages/react/dist/PlinthSectionMenu.d.ts +11 -0
- package/packages/react/dist/PlinthSectionMenuItem.d.ts +20 -0
- package/packages/react/dist/PlinthSheet.d.ts +26 -0
- package/packages/react/dist/PlinthTextField.d.ts +24 -0
- package/packages/react/dist/PlinthTextarea.d.ts +24 -0
- package/packages/react/dist/avatar.d.ts +12 -0
- package/packages/react/dist/button.d.ts +21 -0
- package/packages/react/dist/callout.d.ts +19 -0
- package/packages/react/dist/code-block.d.ts +16 -0
- package/packages/react/dist/confirm-dialog.d.ts +23 -0
- package/packages/react/dist/data-table.d.ts +15 -0
- package/packages/react/dist/dialog.d.ts +10 -0
- package/packages/react/dist/drawer.d.ts +20 -0
- package/packages/react/dist/heading.d.ts +5 -0
- package/packages/react/dist/icon.d.ts +5 -0
- package/packages/react/dist/index.d.ts +49 -0
- package/packages/react/dist/index.js +1326 -0
- package/packages/react/dist/list-row.d.ts +5 -0
- package/packages/react/dist/markup.d.ts +9 -0
- package/packages/react/dist/menu.d.ts +7 -0
- package/packages/react/dist/page-header.d.ts +5 -0
- package/packages/react/dist/sheet.d.ts +12 -0
- package/packages/react/dist/size.d.ts +13 -0
- package/packages/react/dist/swipe.d.ts +15 -0
- package/packages/react/dist/text-field.d.ts +10 -0
- package/packages/react/dist/tones.d.ts +5 -0
- package/packages/tokens/dist/index.d.ts +230 -0
- package/packages/tokens/dist/index.js +4091 -0
- package/packages/tokens/dist/tailwind-theme.css +80 -0
- package/packages/tokens/dist/tokens.css +413 -0
- package/packages/tokens/dist/tokens.json +3875 -0
- package/packages/vue/dist/PlinthAvatar.vue.d.ts +24 -0
- package/packages/vue/dist/PlinthBadge.vue.d.ts +21 -0
- package/packages/vue/dist/PlinthButton.vue.d.ts +42 -0
- package/packages/vue/dist/PlinthCallout.vue.d.ts +31 -0
- package/packages/vue/dist/PlinthCodeBlock.vue.d.ts +49 -0
- package/packages/vue/dist/PlinthConfirmDialog.vue.d.ts +51 -0
- package/packages/vue/dist/PlinthDataTable.vue.d.ts +21 -0
- package/packages/vue/dist/PlinthDataTableCell.vue.d.ts +29 -0
- package/packages/vue/dist/PlinthDataTableColumn.vue.d.ts +26 -0
- package/packages/vue/dist/PlinthDataTableEmpty.vue.d.ts +19 -0
- package/packages/vue/dist/PlinthDataTableRow.vue.d.ts +19 -0
- package/packages/vue/dist/PlinthDataTableSort.vue.d.ts +13 -0
- package/packages/vue/dist/PlinthDialog.vue.d.ts +45 -0
- package/packages/vue/dist/PlinthDrawer.vue.d.ts +48 -0
- package/packages/vue/dist/PlinthIcon.vue.d.ts +23 -0
- package/packages/vue/dist/PlinthIconButton.vue.d.ts +37 -0
- package/packages/vue/dist/PlinthListCard.vue.d.ts +26 -0
- package/packages/vue/dist/PlinthListRow.vue.d.ts +38 -0
- package/packages/vue/dist/PlinthMenu.vue.d.ts +24 -0
- package/packages/vue/dist/PlinthMenuGroup.vue.d.ts +17 -0
- package/packages/vue/dist/PlinthMenuItem.vue.d.ts +34 -0
- package/packages/vue/dist/PlinthMenuSeparator.vue.d.ts +3 -0
- package/packages/vue/dist/PlinthMenuSheet.vue.d.ts +48 -0
- package/packages/vue/dist/PlinthPageHeader.vue.d.ts +27 -0
- package/packages/vue/dist/PlinthPill.vue.d.ts +28 -0
- package/packages/vue/dist/PlinthPillRow.vue.d.ts +22 -0
- package/packages/vue/dist/PlinthSectionMenu.vue.d.ts +22 -0
- package/packages/vue/dist/PlinthSectionMenuItem.vue.d.ts +28 -0
- package/packages/vue/dist/PlinthSheet.vue.d.ts +45 -0
- package/packages/vue/dist/PlinthTextField.vue.d.ts +51 -0
- package/packages/vue/dist/PlinthTextarea.vue.d.ts +42 -0
- package/packages/vue/dist/avatar.d.ts +12 -0
- package/packages/vue/dist/button.d.ts +5 -0
- package/packages/vue/dist/callout.d.ts +19 -0
- package/packages/vue/dist/code-block.d.ts +16 -0
- package/packages/vue/dist/confirm-dialog.d.ts +23 -0
- package/packages/vue/dist/data-table.d.ts +15 -0
- package/packages/vue/dist/dialog.d.ts +10 -0
- package/packages/vue/dist/drawer.d.ts +20 -0
- package/packages/vue/dist/heading.d.ts +5 -0
- package/packages/vue/dist/icon.d.ts +5 -0
- package/packages/vue/dist/index.d.ts +53 -0
- package/packages/vue/dist/index.js +1732 -0
- package/packages/vue/dist/list-row.d.ts +5 -0
- package/packages/vue/dist/menu.d.ts +8 -0
- package/packages/vue/dist/page-header.d.ts +5 -0
- package/packages/vue/dist/sheet.d.ts +12 -0
- package/packages/vue/dist/size.d.ts +13 -0
- package/packages/vue/dist/slots.d.ts +3 -0
- package/packages/vue/dist/swipe.d.ts +12 -0
- package/packages/vue/dist/text-field.d.ts +10 -0
- package/packages/vue/dist/tones.d.ts +5 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Header: the top bar of the phone shell, with a leading action, the page's title and trailing
|
|
3
|
+
* actions. It sticks to the top of the page as the content scrolls under it.
|
|
4
|
+
*
|
|
5
|
+
* <header class="plinth-header">
|
|
6
|
+
* <div class="plinth-header-leading">
|
|
7
|
+
* <a class="plinth-button plinth-icon-button" href="/inbox" data-variant="ghost"
|
|
8
|
+
* data-size="md" aria-label="Back">
|
|
9
|
+
* <span class="plinth-icon" data-size="md" data-mirror aria-hidden="true"><svg>…</svg></span>
|
|
10
|
+
* </a>
|
|
11
|
+
* </div>
|
|
12
|
+
* <h1 class="plinth-header-title">Inbox</h1>
|
|
13
|
+
* <div class="plinth-header-actions">
|
|
14
|
+
* <button type="button" class="plinth-button plinth-icon-button" data-variant="ghost"
|
|
15
|
+
* data-size="md" aria-label="Search">…</button>
|
|
16
|
+
* </div>
|
|
17
|
+
* </header>
|
|
18
|
+
*
|
|
19
|
+
* The root is a <header>: at the top of the page (not inside <main>, <article> or <section>) it is
|
|
20
|
+
* the page's banner landmark, so a page has one. The title is an <h1>, the page's heading, and is
|
|
21
|
+
* required. It stays on one line and ends in an ellipsis when it doesn't fit.
|
|
22
|
+
*
|
|
23
|
+
* The leading part holds one action, usually an icon button: back (a link, with data-mirror on
|
|
24
|
+
* its icon so it points the other way in right-to-left text) or a menu button that opens a sheet
|
|
25
|
+
* (commandfor and command="show-modal"). The actions part holds the trailing actions, at most two
|
|
26
|
+
* or three icon buttons; put the rest in a menu. Both are optional, and printed only when they
|
|
27
|
+
* have content. Every icon button needs its own name (label). The header needs button.css and
|
|
28
|
+
* icon.css for them.
|
|
29
|
+
*
|
|
30
|
+
* Its height is the header.height component token (56px), plus env(safe-area-inset-top), which
|
|
31
|
+
* pads it below the status bar and the notch on a phone that draws under them (viewport-fit=cover);
|
|
32
|
+
* the inline padding clears the notch on a phone held sideways. It is sticky on the
|
|
33
|
+
* layer.sticky z-index, so it sits above the page and below sheets and menus (the top layer).
|
|
34
|
+
* Content scrolls under it, so the app sets scroll-padding-block-start on its scrolling element
|
|
35
|
+
* (calc(var(--plinth-header-height) + env(safe-area-inset-top))) to keep a focused element or an
|
|
36
|
+
* anchor's target from landing under the header.
|
|
37
|
+
*
|
|
38
|
+
* The actions sit var(--plinth-space-2) apart, so on a touch screen the 40px hit areas of 32px
|
|
39
|
+
* icon buttons don't overlap.
|
|
40
|
+
*
|
|
41
|
+
* The app owns the state: the header has no variants or states of its own. What the actions do is
|
|
42
|
+
* the app's (a link, a commandfor, wire:click, @click on the icon button).
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
@layer plinth {
|
|
46
|
+
.plinth-header {
|
|
47
|
+
position: sticky;
|
|
48
|
+
inset-block-start: 0;
|
|
49
|
+
z-index: var(--plinth-layer-sticky);
|
|
50
|
+
box-sizing: border-box;
|
|
51
|
+
display: flex;
|
|
52
|
+
gap: var(--plinth-space-2);
|
|
53
|
+
align-items: center;
|
|
54
|
+
min-block-size: calc(var(--plinth-header-height) + env(safe-area-inset-top));
|
|
55
|
+
padding-block-start: env(safe-area-inset-top);
|
|
56
|
+
padding-inline: max(var(--plinth-space-2), env(safe-area-inset-left))
|
|
57
|
+
max(var(--plinth-space-2), env(safe-area-inset-right));
|
|
58
|
+
font-family: var(--plinth-font-family-body);
|
|
59
|
+
color: var(--plinth-color-text);
|
|
60
|
+
background-color: var(--plinth-color-surface);
|
|
61
|
+
border-block-end: var(--plinth-border-width-thin) solid var(--plinth-color-divider);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.plinth-header:dir(rtl) {
|
|
65
|
+
padding-inline: max(var(--plinth-space-2), env(safe-area-inset-right))
|
|
66
|
+
max(var(--plinth-space-2), env(safe-area-inset-left));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.plinth-header-leading,
|
|
70
|
+
.plinth-header-actions {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex: none;
|
|
73
|
+
gap: var(--plinth-space-2);
|
|
74
|
+
align-items: center;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.plinth-header-title {
|
|
78
|
+
flex: 1 1 auto;
|
|
79
|
+
min-inline-size: 0;
|
|
80
|
+
margin: 0;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
text-overflow: ellipsis;
|
|
83
|
+
font-size: var(--plinth-font-size-lg);
|
|
84
|
+
font-weight: var(--plinth-font-weight-semibold);
|
|
85
|
+
line-height: var(--plinth-font-leading-tight);
|
|
86
|
+
letter-spacing: var(--plinth-font-tracking-tight);
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Without a leading action or trailing actions, the title keeps the text's distance from the edge. */
|
|
91
|
+
.plinth-header-title:first-child {
|
|
92
|
+
margin-inline-start: var(--plinth-space-2);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.plinth-header-title:last-child {
|
|
96
|
+
margin-inline-end: var(--plinth-space-2);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@media (min-width: 768px) {
|
|
100
|
+
.plinth-header {
|
|
101
|
+
padding-inline: max(var(--plinth-space-4), env(safe-area-inset-left))
|
|
102
|
+
max(var(--plinth-space-4), env(safe-area-inset-right));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.plinth-header:dir(rtl) {
|
|
106
|
+
padding-inline: max(var(--plinth-space-4), env(safe-area-inset-right))
|
|
107
|
+
max(var(--plinth-space-4), env(safe-area-inset-left));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Icon: a project's own SVG, sized by Plinth.
|
|
3
|
+
*
|
|
4
|
+
* Decorative (the default), hidden from assistive technology:
|
|
5
|
+
* <span class="plinth-icon" data-size="md" aria-hidden="true">
|
|
6
|
+
* <svg viewBox="0 0 24 24">…</svg>
|
|
7
|
+
* </span>
|
|
8
|
+
*
|
|
9
|
+
* Meaningful, with a label (an img role makes the SVG inside presentational). Blade, Vue and
|
|
10
|
+
* React take it as `label`, or as an `aria-label` attribute; a blank label leaves the icon
|
|
11
|
+
* decorative:
|
|
12
|
+
* <span class="plinth-icon" data-size="md" role="img" aria-label="Warning">
|
|
13
|
+
* <svg viewBox="0 0 24 24">…</svg>
|
|
14
|
+
* </span>
|
|
15
|
+
*
|
|
16
|
+
* Sizes (data-size): sm 16px, md 20px (default, also when the attribute is missing or unknown),
|
|
17
|
+
* lg 24px. data-mirror flips the icon in right-to-left text, for icons that point a direction.
|
|
18
|
+
*
|
|
19
|
+
* The SVG comes from the project, inline or as <use href="sprite.svg#name">. It should draw with
|
|
20
|
+
* currentColor; Plinth sets no fill or stroke, so outline and filled icon sets both keep theirs.
|
|
21
|
+
* The icon takes its colour from the text around it.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
@layer plinth {
|
|
25
|
+
.plinth-icon {
|
|
26
|
+
--plinth-icon-current-size: var(--plinth-size-icon-md);
|
|
27
|
+
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
flex: none;
|
|
30
|
+
inline-size: var(--plinth-icon-current-size);
|
|
31
|
+
block-size: var(--plinth-icon-current-size);
|
|
32
|
+
vertical-align: middle;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.plinth-icon[data-size='sm'] {
|
|
36
|
+
--plinth-icon-current-size: var(--plinth-size-icon-sm);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.plinth-icon[data-size='lg'] {
|
|
40
|
+
--plinth-icon-current-size: var(--plinth-size-icon-lg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.plinth-icon > svg {
|
|
44
|
+
inline-size: 100%;
|
|
45
|
+
block-size: 100%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.plinth-icon[data-mirror]:dir(rtl) > svg {
|
|
49
|
+
transform: scaleX(-1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* List card: a group of list rows on a raised surface, with an optional heading and footer note.
|
|
3
|
+
*
|
|
4
|
+
* <div class="plinth-list-card">
|
|
5
|
+
* <h2 class="plinth-list-card-heading">Account</h2>
|
|
6
|
+
* <ul class="plinth-list-card-list" role="list">
|
|
7
|
+
* <li><a class="plinth-list-row" href="…">…</a></li>
|
|
8
|
+
* <li><div class="plinth-list-row">…</div></li>
|
|
9
|
+
* </ul>
|
|
10
|
+
* <p class="plinth-list-card-footer">Changes apply to every device.</p>
|
|
11
|
+
* </div>
|
|
12
|
+
*
|
|
13
|
+
* The heading and the footer are optional, and sit outside the raised surface: the heading above
|
|
14
|
+
* it, the footer note below. The heading is an <h2> by default; pick the level that fits the page
|
|
15
|
+
* (heading-level h2 to h6 in Blade, headingLevel in Vue).
|
|
16
|
+
*
|
|
17
|
+
* The list is the raised surface (color.surface-raised, radius.container, a hairline in
|
|
18
|
+
* color.border; no shadow, per ADR 3: the card does not float). The card resets the list's
|
|
19
|
+
* margin, padding and bullets, and prints role="list" so Safari keeps announcing it. Each row
|
|
20
|
+
* goes in its own <li> and keeps its own root, states and divider; the surface clips the rows'
|
|
21
|
+
* hover and selection tints to its corners, and anything a row positions outside itself (a
|
|
22
|
+
* popover or a dialog is in the top layer, so it is not). The first and last rows take the
|
|
23
|
+
* surface's inner radius, so their focus ring, drawn inside the row, follows the corners.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
@layer plinth {
|
|
27
|
+
.plinth-list-card {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
gap: var(--plinth-space-2);
|
|
31
|
+
min-inline-size: 0;
|
|
32
|
+
font-family: var(--plinth-font-family-body);
|
|
33
|
+
color: var(--plinth-color-text);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.plinth-list-card-heading {
|
|
37
|
+
padding-inline: var(--plinth-space-4);
|
|
38
|
+
margin: 0;
|
|
39
|
+
font-size: var(--plinth-font-size-sm);
|
|
40
|
+
font-weight: var(--plinth-font-weight-medium);
|
|
41
|
+
line-height: var(--plinth-font-leading-normal);
|
|
42
|
+
color: var(--plinth-color-text-muted);
|
|
43
|
+
overflow-wrap: anywhere;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.plinth-list-card-list {
|
|
47
|
+
padding: 0;
|
|
48
|
+
margin: 0;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
list-style: none;
|
|
51
|
+
background-color: var(--plinth-color-surface-raised);
|
|
52
|
+
border: var(--plinth-border-width-thin) solid var(--plinth-color-border);
|
|
53
|
+
border-radius: var(--plinth-radius-container);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.plinth-list-card-footer {
|
|
57
|
+
padding-inline: var(--plinth-space-4);
|
|
58
|
+
margin: 0;
|
|
59
|
+
font-size: var(--plinth-font-size-sm);
|
|
60
|
+
line-height: var(--plinth-font-leading-normal);
|
|
61
|
+
color: var(--plinth-color-text-muted);
|
|
62
|
+
overflow-wrap: anywhere;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.plinth-list-card-list > li:first-child > .plinth-list-row {
|
|
66
|
+
border-start-start-radius: calc(
|
|
67
|
+
var(--plinth-radius-container) - var(--plinth-border-width-thin)
|
|
68
|
+
);
|
|
69
|
+
border-start-end-radius: calc(var(--plinth-radius-container) - var(--plinth-border-width-thin));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.plinth-list-card-list > li:last-child > .plinth-list-row {
|
|
73
|
+
border-end-start-radius: calc(var(--plinth-radius-container) - var(--plinth-border-width-thin));
|
|
74
|
+
border-end-end-radius: calc(var(--plinth-radius-container) - var(--plinth-border-width-thin));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* List row: one row of a list or a sheet, 44px tall at least.
|
|
3
|
+
*
|
|
4
|
+
* <div class="plinth-list-row">
|
|
5
|
+
* <span class="plinth-list-row-leading">an icon or an avatar</span>
|
|
6
|
+
* <span class="plinth-list-row-content">
|
|
7
|
+
* <span class="plinth-list-row-title">Notifications</span>
|
|
8
|
+
* <span class="plinth-list-row-description">Email and push</span>
|
|
9
|
+
* </span>
|
|
10
|
+
* <span class="plinth-list-row-trailing">
|
|
11
|
+
* a value, a badge or a control
|
|
12
|
+
* <span class="plinth-icon plinth-list-row-chevron" data-size="sm" data-mirror
|
|
13
|
+
* aria-hidden="true"><svg>…</svg></span>
|
|
14
|
+
* </span>
|
|
15
|
+
* </div>
|
|
16
|
+
*
|
|
17
|
+
* The leading slot, the description and the trailing slot are optional; the chevron is the
|
|
18
|
+
* row's own (chevron in Blade and Vue), and points the other way in right-to-left text.
|
|
19
|
+
*
|
|
20
|
+
* The root is the element the app talks to (ADR 2):
|
|
21
|
+
* - a static row is a <div>;
|
|
22
|
+
* - a row that navigates is <a class="plinth-list-row" href="…">;
|
|
23
|
+
* - a row that acts is <button type="button" class="plinth-list-row">.
|
|
24
|
+
* A link or button row holds no other control: a switch or an icon button in the trailing slot
|
|
25
|
+
* needs a static row, and its own accessible name.
|
|
26
|
+
*
|
|
27
|
+
* States:
|
|
28
|
+
* - current: aria-current="page" on a link row (or a static row) for the page being shown, set
|
|
29
|
+
* by the app; the selection tint (accent-subtle, accent-text) and a heavier title. A button
|
|
30
|
+
* row never prints it: a button is not a page.
|
|
31
|
+
* - disabled: `disabled` on a <button>; on a link, no href, role="link" and aria-disabled="true".
|
|
32
|
+
* - hover only where the device can hover; the focus ring on :focus-visible, drawn inside the
|
|
33
|
+
* row (focus-ring.offset-inset).
|
|
34
|
+
*
|
|
35
|
+
* A hairline divider (color.divider) sits between two rows that follow each other, as siblings
|
|
36
|
+
* or each as the direct child of its own <li>; a wrapper element in between drops it. The app
|
|
37
|
+
* resets the list (margin, padding, list-style) and adds role="list", which Safari needs to keep
|
|
38
|
+
* announcing a list without bullets. The height is --plinth-list-row-height (44px); a description makes the
|
|
39
|
+
* row taller. Long titles and descriptions wrap; the trailing slot keeps its width up to half the
|
|
40
|
+
* row, and only a value longer than that wraps.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
@layer plinth {
|
|
44
|
+
.plinth-list-row {
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: var(--plinth-space-3);
|
|
48
|
+
align-items: center;
|
|
49
|
+
inline-size: 100%;
|
|
50
|
+
min-block-size: var(--plinth-list-row-height);
|
|
51
|
+
padding-block: var(--plinth-space-2);
|
|
52
|
+
padding-inline: var(--plinth-space-4);
|
|
53
|
+
margin: 0;
|
|
54
|
+
font-family: var(--plinth-font-family-body);
|
|
55
|
+
font-size: var(--plinth-font-size-md);
|
|
56
|
+
font-weight: var(--plinth-font-weight-regular);
|
|
57
|
+
line-height: var(--plinth-font-leading-tight);
|
|
58
|
+
color: var(--plinth-color-text);
|
|
59
|
+
text-align: start;
|
|
60
|
+
text-decoration: none;
|
|
61
|
+
background-color: transparent;
|
|
62
|
+
border: 0;
|
|
63
|
+
transition: background-color var(--plinth-duration-fast) var(--plinth-easing-standard);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
:is(a, button).plinth-list-row {
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
li + li > .plinth-list-row {
|
|
71
|
+
border-block-start: var(--plinth-border-width-thin) solid var(--plinth-color-divider);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.plinth-list-row + .plinth-list-row {
|
|
75
|
+
border-block-start: var(--plinth-border-width-thin) solid var(--plinth-color-divider);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@media (hover: hover) {
|
|
79
|
+
a.plinth-list-row[href]:not([aria-current='page']):hover,
|
|
80
|
+
button.plinth-list-row:enabled:not([aria-current='page']):hover {
|
|
81
|
+
background-color: var(--plinth-color-surface-hover);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
a.plinth-list-row[href]:not([aria-current='page']):active,
|
|
86
|
+
button.plinth-list-row:enabled:not([aria-current='page']):active {
|
|
87
|
+
background-color: var(--plinth-color-surface-sunken);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.plinth-list-row[aria-current='page'] {
|
|
91
|
+
color: var(--plinth-color-accent-text);
|
|
92
|
+
background-color: var(--plinth-color-accent-subtle);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.plinth-list-row:disabled,
|
|
96
|
+
.plinth-list-row[aria-disabled='true'] {
|
|
97
|
+
cursor: not-allowed;
|
|
98
|
+
opacity: var(--plinth-opacity-disabled);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Inside the row, so a scrolling sheet or a card doesn't cut the ring off at the sides. */
|
|
102
|
+
.plinth-list-row:focus-visible {
|
|
103
|
+
outline: var(--plinth-focus-ring-width) solid var(--plinth-color-focus-ring);
|
|
104
|
+
outline-offset: var(--plinth-focus-ring-offset-inset);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.plinth-list-row-leading {
|
|
108
|
+
display: inline-flex;
|
|
109
|
+
flex: none;
|
|
110
|
+
align-items: center;
|
|
111
|
+
color: var(--plinth-color-text-muted);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.plinth-list-row-content {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex: 1 1 auto;
|
|
117
|
+
flex-direction: column;
|
|
118
|
+
gap: var(--plinth-space-0-5);
|
|
119
|
+
min-inline-size: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.plinth-list-row-title {
|
|
123
|
+
overflow-wrap: anywhere;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.plinth-list-row-description {
|
|
127
|
+
font-size: var(--plinth-font-size-sm);
|
|
128
|
+
line-height: var(--plinth-font-leading-normal);
|
|
129
|
+
color: var(--plinth-color-text-muted);
|
|
130
|
+
overflow-wrap: anywhere;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Keeps its width up to half the row, so a long title wraps before a short value does. */
|
|
134
|
+
.plinth-list-row-trailing {
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
flex: none;
|
|
137
|
+
gap: var(--plinth-space-2);
|
|
138
|
+
align-items: center;
|
|
139
|
+
max-inline-size: 50%;
|
|
140
|
+
color: var(--plinth-color-text-muted);
|
|
141
|
+
text-align: end;
|
|
142
|
+
overflow-wrap: anywhere;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.plinth-list-row-chevron {
|
|
146
|
+
color: var(--plinth-color-text-subtle);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.plinth-list-row[aria-current='page'] .plinth-list-row-title {
|
|
150
|
+
font-weight: var(--plinth-font-weight-medium);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@media (forced-colors: active) {
|
|
154
|
+
.plinth-list-row[aria-current='page'] {
|
|
155
|
+
color: HighlightText;
|
|
156
|
+
background-color: Highlight;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.plinth-list-row:disabled,
|
|
160
|
+
.plinth-list-row[aria-disabled='true'] {
|
|
161
|
+
color: GrayText;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Menu sheet: the app shell's menu, a sheet with the signed-in user, the navigation and the
|
|
3
|
+
* account actions. A side panel from 768px up, a bottom sheet on a phone.
|
|
4
|
+
*
|
|
5
|
+
* <dialog id="menu" class="plinth-sheet plinth-menu-sheet" data-side="end"
|
|
6
|
+
* data-side-phone="bottom" closedby="any" aria-labelledby="menu-title">
|
|
7
|
+
* <div class="plinth-sheet-panel">
|
|
8
|
+
* <div class="plinth-sheet-handle" aria-hidden="true"></div>
|
|
9
|
+
* <div class="plinth-sheet-header">
|
|
10
|
+
* <h2 id="menu-title" class="plinth-sheet-title">Menu</h2>
|
|
11
|
+
* <button … class="plinth-button plinth-icon-button plinth-sheet-close" commandfor="menu"
|
|
12
|
+
* command="close">…</button>
|
|
13
|
+
* </div>
|
|
14
|
+
* <div class="plinth-sheet-body">
|
|
15
|
+
* <a class="plinth-list-row plinth-menu-sheet-user" href="/profile">
|
|
16
|
+
* <span class="plinth-list-row-leading">
|
|
17
|
+
* <span class="plinth-avatar" data-size="lg" aria-hidden="true">…</span>
|
|
18
|
+
* </span>
|
|
19
|
+
* <span class="plinth-list-row-content">
|
|
20
|
+
* <span class="plinth-list-row-title">Sam Lee</span>
|
|
21
|
+
* <span class="plinth-list-row-description">sam@example.com</span>
|
|
22
|
+
* </span>
|
|
23
|
+
* <span class="plinth-list-row-trailing">…chevron…</span>
|
|
24
|
+
* </a>
|
|
25
|
+
* <nav class="plinth-menu-sheet-nav" aria-labelledby="menu-title">
|
|
26
|
+
* <div class="plinth-list-card">…link rows, one with aria-current="page"…</div>
|
|
27
|
+
* </nav>
|
|
28
|
+
* <div class="plinth-menu-sheet-account">
|
|
29
|
+
* <div class="plinth-list-card">…Settings, Sign out…</div>
|
|
30
|
+
* </div>
|
|
31
|
+
* </div>
|
|
32
|
+
* </div>
|
|
33
|
+
* </dialog>
|
|
34
|
+
*
|
|
35
|
+
* It is a sheet (sheet.css), always data-side="end" data-side-phone="bottom", with no footer;
|
|
36
|
+
* everything the sheet's contract says holds, and it needs modal.css, sheet.css, list-row.css, list-card.css, avatar.css,
|
|
37
|
+
* button.css and icon.css. The root's id is required in practice: the header's menu button opens
|
|
38
|
+
* it with <button commandfor="menu" command="show-modal">, the close button points at it too, and
|
|
39
|
+
* the dialog and the nav are named by the title, whose id is the root's id with -title.
|
|
40
|
+
*
|
|
41
|
+
* The parts, in the body:
|
|
42
|
+
* - the user row (optional): a list row with a decorative avatar (lg), the name and an optional
|
|
43
|
+
* detail (an email or a role); a static <div>, or a link to the profile with a chevron when it
|
|
44
|
+
* has an href. The avatar is decorative because the name is printed next to it.
|
|
45
|
+
* - the nav (always there): a <nav> named by the sheet's title (aria-labelledby), holding list
|
|
46
|
+
* cards of link rows, their headings <h3> under the sheet's <h2> title. The app marks the row
|
|
47
|
+
* of the page being shown with aria-current="page" (current on the list row) and keeps it up
|
|
48
|
+
* to date; the menu remembers nothing.
|
|
49
|
+
* - the account (optional): a list card of account actions, such as Settings and Sign out (a
|
|
50
|
+
* button row; for a form post, a button row with type="submit" and form="…").
|
|
51
|
+
*
|
|
52
|
+
* The parts stack with a gap between them. In the full-height side panel the account sits on the
|
|
53
|
+
* bottom edge (margin-block-start: auto) and rises with the content when the nav is long; the
|
|
54
|
+
* body scrolls as one.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
@layer plinth {
|
|
58
|
+
.plinth-menu-sheet .plinth-sheet-body {
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: var(--plinth-space-5);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.plinth-menu-sheet-nav,
|
|
65
|
+
.plinth-menu-sheet-account {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
gap: var(--plinth-space-5);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.plinth-menu-sheet-account {
|
|
72
|
+
margin-block-start: auto;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* The name reads as the row's heading. */
|
|
76
|
+
.plinth-menu-sheet-user .plinth-list-row-title {
|
|
77
|
+
font-weight: var(--plinth-font-weight-medium);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* A link to the profile rounds its hover tint like the list cards' corners below it. */
|
|
81
|
+
a.plinth-menu-sheet-user {
|
|
82
|
+
border-radius: var(--plinth-radius-container);
|
|
83
|
+
}
|
|
84
|
+
}
|