@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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +258 -2
  3. package/package.json +99 -4
  4. package/packages/css/dist/avatar.css +122 -0
  5. package/packages/css/dist/badge.css +76 -0
  6. package/packages/css/dist/button.css +273 -0
  7. package/packages/css/dist/callout.css +143 -0
  8. package/packages/css/dist/code-block.css +283 -0
  9. package/packages/css/dist/confirm-dialog.css +183 -0
  10. package/packages/css/dist/data-table.css +470 -0
  11. package/packages/css/dist/dialog.css +113 -0
  12. package/packages/css/dist/drawer.css +156 -0
  13. package/packages/css/dist/fonts/geist/LICENSE +93 -0
  14. package/packages/css/dist/fonts/geist/geist-cyrillic-ext-wght-normal.woff2 +0 -0
  15. package/packages/css/dist/fonts/geist/geist-cyrillic-wght-normal.woff2 +0 -0
  16. package/packages/css/dist/fonts/geist/geist-latin-ext-wght-normal.woff2 +0 -0
  17. package/packages/css/dist/fonts/geist/geist-latin-wght-normal.woff2 +0 -0
  18. package/packages/css/dist/fonts/geist/geist-vietnamese-wght-normal.woff2 +0 -0
  19. package/packages/css/dist/fonts/geist-mono/LICENSE +93 -0
  20. package/packages/css/dist/fonts/geist-mono/geist-mono-cyrillic-ext-wght-normal.woff2 +0 -0
  21. package/packages/css/dist/fonts/geist-mono/geist-mono-cyrillic-wght-normal.woff2 +0 -0
  22. package/packages/css/dist/fonts/geist-mono/geist-mono-latin-ext-wght-normal.woff2 +0 -0
  23. package/packages/css/dist/fonts/geist-mono/geist-mono-latin-wght-normal.woff2 +0 -0
  24. package/packages/css/dist/fonts/geist-mono/geist-mono-symbols2-wght-normal.woff2 +0 -0
  25. package/packages/css/dist/fonts/geist-mono/geist-mono-vietnamese-wght-normal.woff2 +0 -0
  26. package/packages/css/dist/fonts/index.css +109 -0
  27. package/packages/css/dist/header.css +110 -0
  28. package/packages/css/dist/icon.css +51 -0
  29. package/packages/css/dist/list-card.css +76 -0
  30. package/packages/css/dist/list-row.css +164 -0
  31. package/packages/css/dist/menu-sheet.css +84 -0
  32. package/packages/css/dist/menu.css +245 -0
  33. package/packages/css/dist/modal.css +367 -0
  34. package/packages/css/dist/page-header.css +124 -0
  35. package/packages/css/dist/pill-row.css +197 -0
  36. package/packages/css/dist/plinth.css +4446 -0
  37. package/packages/css/dist/section-menu.css +226 -0
  38. package/packages/css/dist/sheet.css +239 -0
  39. package/packages/css/dist/text-field.css +230 -0
  40. package/packages/css/dist/textarea.css +169 -0
  41. package/packages/css/dist/tokens.css +413 -0
  42. package/packages/react/dist/PlinthAvatar.d.ts +17 -0
  43. package/packages/react/dist/PlinthBadge.d.ts +7 -0
  44. package/packages/react/dist/PlinthButton.d.ts +29 -0
  45. package/packages/react/dist/PlinthCallout.d.ts +16 -0
  46. package/packages/react/dist/PlinthCodeBlock.d.ts +24 -0
  47. package/packages/react/dist/PlinthConfirmDialog.d.ts +38 -0
  48. package/packages/react/dist/PlinthDataTable.d.ts +11 -0
  49. package/packages/react/dist/PlinthDataTableCell.d.ts +15 -0
  50. package/packages/react/dist/PlinthDataTableColumn.d.ts +13 -0
  51. package/packages/react/dist/PlinthDataTableEmpty.d.ts +9 -0
  52. package/packages/react/dist/PlinthDataTableRow.d.ts +9 -0
  53. package/packages/react/dist/PlinthDataTableSort.d.ts +4 -0
  54. package/packages/react/dist/PlinthDialog.d.ts +26 -0
  55. package/packages/react/dist/PlinthDrawer.d.ts +29 -0
  56. package/packages/react/dist/PlinthIcon.d.ts +10 -0
  57. package/packages/react/dist/PlinthIconButton.d.ts +26 -0
  58. package/packages/react/dist/PlinthListCard.d.ts +14 -0
  59. package/packages/react/dist/PlinthListRow.d.ts +29 -0
  60. package/packages/react/dist/PlinthMenu.d.ts +12 -0
  61. package/packages/react/dist/PlinthMenuGroup.d.ts +9 -0
  62. package/packages/react/dist/PlinthMenuItem.d.ts +26 -0
  63. package/packages/react/dist/PlinthMenuSeparator.d.ts +4 -0
  64. package/packages/react/dist/PlinthMenuSheet.d.ts +24 -0
  65. package/packages/react/dist/PlinthPageHeader.d.ts +17 -0
  66. package/packages/react/dist/PlinthPill.d.ts +17 -0
  67. package/packages/react/dist/PlinthPillRow.d.ts +13 -0
  68. package/packages/react/dist/PlinthSectionMenu.d.ts +11 -0
  69. package/packages/react/dist/PlinthSectionMenuItem.d.ts +20 -0
  70. package/packages/react/dist/PlinthSheet.d.ts +26 -0
  71. package/packages/react/dist/PlinthTextField.d.ts +24 -0
  72. package/packages/react/dist/PlinthTextarea.d.ts +24 -0
  73. package/packages/react/dist/avatar.d.ts +12 -0
  74. package/packages/react/dist/button.d.ts +21 -0
  75. package/packages/react/dist/callout.d.ts +19 -0
  76. package/packages/react/dist/code-block.d.ts +16 -0
  77. package/packages/react/dist/confirm-dialog.d.ts +23 -0
  78. package/packages/react/dist/data-table.d.ts +15 -0
  79. package/packages/react/dist/dialog.d.ts +10 -0
  80. package/packages/react/dist/drawer.d.ts +20 -0
  81. package/packages/react/dist/heading.d.ts +5 -0
  82. package/packages/react/dist/icon.d.ts +5 -0
  83. package/packages/react/dist/index.d.ts +49 -0
  84. package/packages/react/dist/index.js +1326 -0
  85. package/packages/react/dist/list-row.d.ts +5 -0
  86. package/packages/react/dist/markup.d.ts +9 -0
  87. package/packages/react/dist/menu.d.ts +7 -0
  88. package/packages/react/dist/page-header.d.ts +5 -0
  89. package/packages/react/dist/sheet.d.ts +12 -0
  90. package/packages/react/dist/size.d.ts +13 -0
  91. package/packages/react/dist/swipe.d.ts +15 -0
  92. package/packages/react/dist/text-field.d.ts +10 -0
  93. package/packages/react/dist/tones.d.ts +5 -0
  94. package/packages/tokens/dist/index.d.ts +230 -0
  95. package/packages/tokens/dist/index.js +4091 -0
  96. package/packages/tokens/dist/tailwind-theme.css +80 -0
  97. package/packages/tokens/dist/tokens.css +413 -0
  98. package/packages/tokens/dist/tokens.json +3875 -0
  99. package/packages/vue/dist/PlinthAvatar.vue.d.ts +24 -0
  100. package/packages/vue/dist/PlinthBadge.vue.d.ts +21 -0
  101. package/packages/vue/dist/PlinthButton.vue.d.ts +42 -0
  102. package/packages/vue/dist/PlinthCallout.vue.d.ts +31 -0
  103. package/packages/vue/dist/PlinthCodeBlock.vue.d.ts +49 -0
  104. package/packages/vue/dist/PlinthConfirmDialog.vue.d.ts +51 -0
  105. package/packages/vue/dist/PlinthDataTable.vue.d.ts +21 -0
  106. package/packages/vue/dist/PlinthDataTableCell.vue.d.ts +29 -0
  107. package/packages/vue/dist/PlinthDataTableColumn.vue.d.ts +26 -0
  108. package/packages/vue/dist/PlinthDataTableEmpty.vue.d.ts +19 -0
  109. package/packages/vue/dist/PlinthDataTableRow.vue.d.ts +19 -0
  110. package/packages/vue/dist/PlinthDataTableSort.vue.d.ts +13 -0
  111. package/packages/vue/dist/PlinthDialog.vue.d.ts +45 -0
  112. package/packages/vue/dist/PlinthDrawer.vue.d.ts +48 -0
  113. package/packages/vue/dist/PlinthIcon.vue.d.ts +23 -0
  114. package/packages/vue/dist/PlinthIconButton.vue.d.ts +37 -0
  115. package/packages/vue/dist/PlinthListCard.vue.d.ts +26 -0
  116. package/packages/vue/dist/PlinthListRow.vue.d.ts +38 -0
  117. package/packages/vue/dist/PlinthMenu.vue.d.ts +24 -0
  118. package/packages/vue/dist/PlinthMenuGroup.vue.d.ts +17 -0
  119. package/packages/vue/dist/PlinthMenuItem.vue.d.ts +34 -0
  120. package/packages/vue/dist/PlinthMenuSeparator.vue.d.ts +3 -0
  121. package/packages/vue/dist/PlinthMenuSheet.vue.d.ts +48 -0
  122. package/packages/vue/dist/PlinthPageHeader.vue.d.ts +27 -0
  123. package/packages/vue/dist/PlinthPill.vue.d.ts +28 -0
  124. package/packages/vue/dist/PlinthPillRow.vue.d.ts +22 -0
  125. package/packages/vue/dist/PlinthSectionMenu.vue.d.ts +22 -0
  126. package/packages/vue/dist/PlinthSectionMenuItem.vue.d.ts +28 -0
  127. package/packages/vue/dist/PlinthSheet.vue.d.ts +45 -0
  128. package/packages/vue/dist/PlinthTextField.vue.d.ts +51 -0
  129. package/packages/vue/dist/PlinthTextarea.vue.d.ts +42 -0
  130. package/packages/vue/dist/avatar.d.ts +12 -0
  131. package/packages/vue/dist/button.d.ts +5 -0
  132. package/packages/vue/dist/callout.d.ts +19 -0
  133. package/packages/vue/dist/code-block.d.ts +16 -0
  134. package/packages/vue/dist/confirm-dialog.d.ts +23 -0
  135. package/packages/vue/dist/data-table.d.ts +15 -0
  136. package/packages/vue/dist/dialog.d.ts +10 -0
  137. package/packages/vue/dist/drawer.d.ts +20 -0
  138. package/packages/vue/dist/heading.d.ts +5 -0
  139. package/packages/vue/dist/icon.d.ts +5 -0
  140. package/packages/vue/dist/index.d.ts +53 -0
  141. package/packages/vue/dist/index.js +1732 -0
  142. package/packages/vue/dist/list-row.d.ts +5 -0
  143. package/packages/vue/dist/menu.d.ts +8 -0
  144. package/packages/vue/dist/page-header.d.ts +5 -0
  145. package/packages/vue/dist/sheet.d.ts +12 -0
  146. package/packages/vue/dist/size.d.ts +13 -0
  147. package/packages/vue/dist/slots.d.ts +3 -0
  148. package/packages/vue/dist/swipe.d.ts +12 -0
  149. package/packages/vue/dist/text-field.d.ts +10 -0
  150. 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
+ }