@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,183 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Confirmation dialog: a dialog that asks a person to confirm an action before it happens, in
|
|
3
|
+
* three kinds: simple ("Archive 3 tickets?"), type to confirm ("Type harbour to delete this
|
|
4
|
+
* project") and password ("Enter your password to change the billing email").
|
|
5
|
+
*
|
|
6
|
+
* <dialog id="delete" class="plinth-dialog plinth-confirm-dialog" role="alertdialog"
|
|
7
|
+
* closedby="closerequest" aria-labelledby="delete-title" aria-describedby="delete-message">
|
|
8
|
+
* <form method="dialog" class="plinth-confirm-dialog-form">
|
|
9
|
+
* <div class="plinth-dialog-header">
|
|
10
|
+
* <h2 id="delete-title" class="plinth-dialog-title">Delete this project?</h2>
|
|
11
|
+
* </div>
|
|
12
|
+
* <div class="plinth-dialog-body">
|
|
13
|
+
* <p id="delete-message" class="plinth-confirm-dialog-message">Its tickets go too.</p>
|
|
14
|
+
* <div class="plinth-confirm-dialog-field">
|
|
15
|
+
* <label for="delete-field" class="plinth-confirm-dialog-label">
|
|
16
|
+
* Type <strong>harbour</strong> to confirm</label>
|
|
17
|
+
* <input id="delete-field" class="plinth-confirm-dialog-input" type="text"
|
|
18
|
+
* name="confirmation" required pattern="harbour" autocomplete="off"
|
|
19
|
+
* autocapitalize="none" spellcheck="false">
|
|
20
|
+
* </div>
|
|
21
|
+
* <p id="delete-error" class="plinth-confirm-dialog-error" role="alert">…</p>
|
|
22
|
+
* </div>
|
|
23
|
+
* <div class="plinth-dialog-footer">
|
|
24
|
+
* <button type="button" class="plinth-button" data-variant="secondary" data-size="md"
|
|
25
|
+
* commandfor="delete" command="close" value="cancel">
|
|
26
|
+
* <span class="plinth-button-label">Cancel</span></button>
|
|
27
|
+
* <button type="submit" class="plinth-button plinth-confirm-dialog-confirm"
|
|
28
|
+
* data-variant="danger" data-size="md" value="confirm">
|
|
29
|
+
* <span class="plinth-button-label">Delete</span></button>
|
|
30
|
+
* </div>
|
|
31
|
+
* </form>
|
|
32
|
+
* </dialog>
|
|
33
|
+
*
|
|
34
|
+
* It is a dialog (dialog.css, on modal.css, with button.css for its actions) that is never
|
|
35
|
+
* dismissible: role="alertdialog", closedby="closerequest" and no close button, so a tap on the
|
|
36
|
+
* backdrop leaves it open, and it closes through its buttons, Escape acting as Cancel. The root
|
|
37
|
+
* needs an id; the title's, message's, field's and error's ids are the root's id with -title,
|
|
38
|
+
* -message, -field and -error. The title and the message are required: they are the alert
|
|
39
|
+
* dialog's name and description. data-side-phone="bottom" makes it a bottom sheet below 768px, as
|
|
40
|
+
* on the dialog. A project may set its own role and closedby.
|
|
41
|
+
*
|
|
42
|
+
* Its content is a <form method="dialog">. Cancel is a button with command="close" and
|
|
43
|
+
* value="cancel", which closes the dialog with returnValue "cancel" and never submits, so it works
|
|
44
|
+
* whatever is in the field; Escape closes it with an empty returnValue.
|
|
45
|
+
* Confirm is the form's only submit button, so Enter in the field confirms too: once the browser
|
|
46
|
+
* has checked the field, the form fires submit (it bubbles to the dialog, where the app listens)
|
|
47
|
+
* and the dialog closes with returnValue "confirm". The browser focuses the first control when it
|
|
48
|
+
* opens: the field, or Cancel when there is none. The browser doesn't empty the field when the
|
|
49
|
+
* dialog closes, so the app calls reset() on the form on close: the phrase is typed afresh each
|
|
50
|
+
* time, and a password doesn't stay in the page.
|
|
51
|
+
*
|
|
52
|
+
* Kinds (no attribute: the markup is what differs):
|
|
53
|
+
* - simple: the message only.
|
|
54
|
+
* - type to confirm: a text field with required and pattern, the phrase with its pattern
|
|
55
|
+
* characters escaped. The browser blocks the submit until the text matches, and while the form
|
|
56
|
+
* is :invalid the confirm button looks disabled, through CSS alone (it isn't disabled: a press
|
|
57
|
+
* shows the browser's message and focuses the field). The label shows the phrase in <strong>;
|
|
58
|
+
* a label of the app's own must keep it (:phrase), or nobody can tell what to type.
|
|
59
|
+
* - password: a field with type="password", name="password", autocomplete="current-password" and
|
|
60
|
+
* required, so the confirm button looks disabled until something is typed. Plinth never checks
|
|
61
|
+
* a password: the app calls preventDefault() on the submit, sets
|
|
62
|
+
* the confirm button busy while it checks, then closes the dialog (close("confirm")) or shows
|
|
63
|
+
* the error.
|
|
64
|
+
*
|
|
65
|
+
* States:
|
|
66
|
+
* - error: a message under the field, in danger-text with role="alert" so it is announced when it
|
|
67
|
+
* appears; the field has aria-invalid="true", aria-describedby pointing at it, and a thick
|
|
68
|
+
* danger edge (thick, so it isn't only a colour).
|
|
69
|
+
* - busy: the confirm button's busy state (button.css: aria-busy, aria-disabled and a spinner).
|
|
70
|
+
* The app ignores another submit while it is busy.
|
|
71
|
+
* - tone: neutral (default) makes the confirm button primary; danger makes it the danger
|
|
72
|
+
* variant, for an action that destroys something.
|
|
73
|
+
*
|
|
74
|
+
* Fields are 16px text on a phone, so iOS doesn't zoom the page when one takes focus, and md from
|
|
75
|
+
* 768px up; they are control.md tall, and 40px on a touch screen.
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
@layer plinth {
|
|
79
|
+
/* The form takes the dialog's place as the column of parts, so the body still scrolls. */
|
|
80
|
+
.plinth-confirm-dialog-form {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex: 1 1 auto;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
min-block-size: 0;
|
|
85
|
+
margin: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.plinth-confirm-dialog-message {
|
|
89
|
+
margin: 0;
|
|
90
|
+
overflow-wrap: anywhere;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.plinth-confirm-dialog-field {
|
|
94
|
+
display: grid;
|
|
95
|
+
gap: var(--plinth-space-2);
|
|
96
|
+
margin-block-start: var(--plinth-space-4);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.plinth-confirm-dialog-label {
|
|
100
|
+
font-size: var(--plinth-font-size-sm);
|
|
101
|
+
font-weight: var(--plinth-font-weight-medium);
|
|
102
|
+
line-height: var(--plinth-font-leading-normal);
|
|
103
|
+
color: var(--plinth-color-text-muted);
|
|
104
|
+
overflow-wrap: anywhere;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.plinth-confirm-dialog-label strong {
|
|
108
|
+
font-family: var(--plinth-font-family-mono);
|
|
109
|
+
font-weight: var(--plinth-font-weight-semibold);
|
|
110
|
+
color: var(--plinth-color-text);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.plinth-confirm-dialog-input {
|
|
114
|
+
box-sizing: border-box;
|
|
115
|
+
inline-size: 100%;
|
|
116
|
+
min-block-size: var(--plinth-control-md);
|
|
117
|
+
padding-block: 0;
|
|
118
|
+
padding-inline: var(--plinth-space-3);
|
|
119
|
+
margin: 0;
|
|
120
|
+
font-family: var(--plinth-font-family-body);
|
|
121
|
+
font-size: var(--plinth-font-size-field);
|
|
122
|
+
line-height: var(--plinth-font-leading-tight);
|
|
123
|
+
color: var(--plinth-color-text);
|
|
124
|
+
background-color: var(--plinth-color-surface-sunken);
|
|
125
|
+
border: var(--plinth-border-width-thin) solid var(--plinth-color-border-strong);
|
|
126
|
+
border-radius: var(--plinth-radius-control);
|
|
127
|
+
transition: border-color var(--plinth-duration-fast) var(--plinth-easing-standard);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* The phrase is typed as it shows in the label. */
|
|
131
|
+
.plinth-confirm-dialog-input[type='text'] {
|
|
132
|
+
font-family: var(--plinth-font-family-mono);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.plinth-confirm-dialog-input:focus-visible {
|
|
136
|
+
outline: var(--plinth-focus-ring-width) solid var(--plinth-color-focus-ring);
|
|
137
|
+
outline-offset: var(--plinth-focus-ring-offset);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.plinth-confirm-dialog-input[aria-invalid='true'] {
|
|
141
|
+
border-color: var(--plinth-color-danger);
|
|
142
|
+
border-width: var(--plinth-border-width-thick);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.plinth-confirm-dialog-error {
|
|
146
|
+
margin-block: var(--plinth-space-2) 0;
|
|
147
|
+
font-size: var(--plinth-font-size-sm);
|
|
148
|
+
line-height: var(--plinth-font-leading-normal);
|
|
149
|
+
color: var(--plinth-color-danger-text);
|
|
150
|
+
overflow-wrap: anywhere;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Until the field matches, the confirm button looks disabled; it keeps its contrast. */
|
|
154
|
+
.plinth-confirm-dialog-form:invalid .plinth-confirm-dialog-confirm:not([aria-busy='true']) {
|
|
155
|
+
--plinth-button-surface: var(--plinth-color-surface-sunken);
|
|
156
|
+
--plinth-button-surface-hover: var(--plinth-color-surface-sunken);
|
|
157
|
+
--plinth-button-surface-active: var(--plinth-color-surface-sunken);
|
|
158
|
+
--plinth-button-text: var(--plinth-color-text-muted);
|
|
159
|
+
--plinth-button-edge: var(--plinth-color-border);
|
|
160
|
+
--plinth-button-inner-edge: none;
|
|
161
|
+
|
|
162
|
+
cursor: not-allowed;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@media (pointer: coarse) {
|
|
166
|
+
.plinth-confirm-dialog-input {
|
|
167
|
+
min-block-size: var(--plinth-touch-target);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@media (min-width: 768px) {
|
|
172
|
+
.plinth-confirm-dialog-input {
|
|
173
|
+
font-size: var(--plinth-font-size-md);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@media (forced-colors: active) {
|
|
178
|
+
.plinth-confirm-dialog-form:invalid .plinth-confirm-dialog-confirm:not([aria-busy='true']) {
|
|
179
|
+
color: GrayText;
|
|
180
|
+
border-color: GrayText;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Data table: a collection of records (orders, users, invoices) from one markup, a stack of cards
|
|
3
|
+
* on a phone and a table from 768px up. No script: the CSS alone changes the layout.
|
|
4
|
+
*
|
|
5
|
+
* <table class="plinth-data-table" role="table">
|
|
6
|
+
* <caption class="plinth-data-table-caption">Orders</caption>
|
|
7
|
+
* <thead class="plinth-data-table-head" role="rowgroup">
|
|
8
|
+
* <tr class="plinth-data-table-head-row" role="row">
|
|
9
|
+
* <th class="plinth-data-table-column" role="columnheader" scope="col">Order</th>
|
|
10
|
+
* <th class="plinth-data-table-column" role="columnheader" scope="col" aria-sort="descending">
|
|
11
|
+
* <button type="button" class="plinth-data-table-sort">
|
|
12
|
+
* <span class="plinth-data-table-sort-label">Date</span>
|
|
13
|
+
* <span class="plinth-icon plinth-data-table-sort-icon" data-size="sm" aria-hidden="true">
|
|
14
|
+
* <svg>…<path class="plinth-data-table-sort-up"/><path class="plinth-data-table-sort-down"/></svg>
|
|
15
|
+
* </span>
|
|
16
|
+
* </button>
|
|
17
|
+
* </th>
|
|
18
|
+
* <th class="plinth-data-table-column" role="columnheader" scope="col" data-numeric>Total</th>
|
|
19
|
+
* <th class="plinth-data-table-column" role="columnheader" scope="col" data-phone="hidden">…</th>
|
|
20
|
+
* </tr>
|
|
21
|
+
* </thead>
|
|
22
|
+
* <tbody class="plinth-data-table-body" role="rowgroup">
|
|
23
|
+
* <tr class="plinth-data-table-row" role="row">
|
|
24
|
+
* <td class="plinth-data-table-cell" role="cell" data-kind="title">
|
|
25
|
+
* <span class="plinth-data-table-label" aria-hidden="true">Order</span>
|
|
26
|
+
* <span class="plinth-data-table-value">#1042</span>
|
|
27
|
+
* </td>
|
|
28
|
+
* <td class="plinth-data-table-cell" role="cell" data-kind="field">…</td>
|
|
29
|
+
* <td class="plinth-data-table-cell" role="cell" data-kind="field" data-numeric>…</td>
|
|
30
|
+
* <td class="plinth-data-table-cell" role="cell" data-kind="actions">
|
|
31
|
+
* <span class="plinth-data-table-value">buttons</span>
|
|
32
|
+
* </td>
|
|
33
|
+
* </tr>
|
|
34
|
+
* <tr class="plinth-data-table-row" role="row" aria-selected="true">…</tr>
|
|
35
|
+
* </tbody>
|
|
36
|
+
* </table>
|
|
37
|
+
*
|
|
38
|
+
* With no rows, the body holds the empty state:
|
|
39
|
+
*
|
|
40
|
+
* <tbody class="plinth-data-table-body" role="rowgroup">
|
|
41
|
+
* <tr class="plinth-data-table-empty" role="row">
|
|
42
|
+
* <td class="plinth-data-table-empty-cell" role="cell" colspan="4">No orders yet.</td>
|
|
43
|
+
* </tr>
|
|
44
|
+
* </tbody>
|
|
45
|
+
*
|
|
46
|
+
* The explicit roles (table, rowgroup, row, columnheader, cell) are part of the contract: some
|
|
47
|
+
* browsers, Safari in particular, drop a table's semantics once its rows stop displaying as table
|
|
48
|
+
* rows. The caption is optional; without it, name the table with aria-label or aria-labelledby.
|
|
49
|
+
*
|
|
50
|
+
* Each cell repeats its column's name in plinth-data-table-label, shown only on a phone's card
|
|
51
|
+
* and aria-hidden, since the column header already names the cell. The label is optional (an
|
|
52
|
+
* actions cell has none). The header row stays in the accessibility tree on a phone, visually
|
|
53
|
+
* hidden, so every cell keeps its header.
|
|
54
|
+
*
|
|
55
|
+
* Choices, on the cells and the column headers:
|
|
56
|
+
* - data-kind on a cell: field (the default; the label, then the value), title (the card's title,
|
|
57
|
+
* first on the card, with no label) or actions (row actions, last on the card, with no label).
|
|
58
|
+
* - data-numeric: numbers, aligned to the end, with figures of one width.
|
|
59
|
+
* - data-phone="hidden": a less important column, left out of a phone's card and shown from 768px
|
|
60
|
+
* up. Put it on the column header and on every cell of the column, so the two always match.
|
|
61
|
+
* The title cell is the row's first cell and the actions cell its last: the card moves them to
|
|
62
|
+
* its top and bottom with `order`, which must not change the reading and focus order.
|
|
63
|
+
*
|
|
64
|
+
* The app owns sorting, paging and selection (ADR 2); the table only shows them:
|
|
65
|
+
* - aria-sort on a sortable column header: none, ascending or descending (one column at a time).
|
|
66
|
+
* Its label is a plinth-data-table-sort button, which the app listens to; its icon shows both
|
|
67
|
+
* arrows (none) or the one of the order. On a phone the sortable headers stay visible above the
|
|
68
|
+
* cards as a row of sort buttons; the other headers are visually hidden.
|
|
69
|
+
* - aria-selected on a row of a table whose rows can be selected: true for a selected row, false
|
|
70
|
+
* for the others. A selected row takes the selection tint (accent-subtle) and an accent edge at
|
|
71
|
+
* its start. aria-selected drives the styling only: screen readers don't announce it in a
|
|
72
|
+
* table, so the control that selects a row (a checkbox, a pressed button) is the app's, and it
|
|
73
|
+
* carries the state.
|
|
74
|
+
* - the sort button's states: hover where the device can hover, pressed (:active) a surface step
|
|
75
|
+
* down, disabled (while a sort loads, say) dimmed with opacity.disabled.
|
|
76
|
+
* - the empty state: one plinth-data-table-empty row in the body, whose plinth-data-table-empty-cell
|
|
77
|
+
* spans every column (colspan).
|
|
78
|
+
* Paging controls sit outside the table.
|
|
79
|
+
*
|
|
80
|
+
* From 768px up the actions sit at the start of their cell, under their column header.
|
|
81
|
+
*
|
|
82
|
+
* Text wraps in its cell. A table wider than its container, from 768px up, belongs in a
|
|
83
|
+
* scrolling container the app adds. --plinth-data-table-row-height is a body row's height (44px).
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
@layer plinth {
|
|
87
|
+
/* The phone: a stack of cards. */
|
|
88
|
+
.plinth-data-table {
|
|
89
|
+
position: relative;
|
|
90
|
+
display: block;
|
|
91
|
+
inline-size: 100%;
|
|
92
|
+
font-family: var(--plinth-font-family-body);
|
|
93
|
+
font-size: var(--plinth-font-size-md);
|
|
94
|
+
line-height: var(--plinth-font-leading-tight);
|
|
95
|
+
color: var(--plinth-color-text);
|
|
96
|
+
border-spacing: 0;
|
|
97
|
+
border-collapse: separate;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.plinth-data-table-caption {
|
|
101
|
+
display: block;
|
|
102
|
+
padding-inline: var(--plinth-space-4);
|
|
103
|
+
margin-block-end: var(--plinth-space-2);
|
|
104
|
+
font-size: var(--plinth-font-size-sm);
|
|
105
|
+
font-weight: var(--plinth-font-weight-medium);
|
|
106
|
+
line-height: var(--plinth-font-leading-normal);
|
|
107
|
+
color: var(--plinth-color-text-muted);
|
|
108
|
+
text-align: start;
|
|
109
|
+
overflow-wrap: anywhere;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.plinth-data-table-head {
|
|
113
|
+
display: block;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.plinth-data-table-head-row {
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-wrap: wrap;
|
|
119
|
+
gap: var(--plinth-space-2);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.plinth-data-table-head-row:has(> [aria-sort]:not([data-phone='hidden'])) {
|
|
123
|
+
margin-block-end: var(--plinth-space-3);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.plinth-data-table-column {
|
|
127
|
+
display: block;
|
|
128
|
+
padding: 0;
|
|
129
|
+
font-size: var(--plinth-font-size-sm);
|
|
130
|
+
font-weight: var(--plinth-font-weight-medium);
|
|
131
|
+
color: var(--plinth-color-text-muted);
|
|
132
|
+
text-align: start;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Visually hidden, still a column header to assistive technology. */
|
|
136
|
+
.plinth-data-table-column:not([aria-sort]) {
|
|
137
|
+
position: absolute;
|
|
138
|
+
inline-size: 1px;
|
|
139
|
+
block-size: 1px;
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
white-space: nowrap;
|
|
142
|
+
clip-path: inset(50%);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.plinth-data-table-sort {
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
gap: var(--plinth-space-1);
|
|
148
|
+
align-items: center;
|
|
149
|
+
min-inline-size: var(--plinth-touch-target);
|
|
150
|
+
min-block-size: var(--plinth-touch-target);
|
|
151
|
+
padding-inline: var(--plinth-space-3);
|
|
152
|
+
margin: 0;
|
|
153
|
+
font: inherit;
|
|
154
|
+
color: inherit;
|
|
155
|
+
text-align: inherit;
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
background-color: var(--plinth-color-surface-raised);
|
|
158
|
+
border: var(--plinth-border-width-thin) solid var(--plinth-color-border);
|
|
159
|
+
border-radius: var(--plinth-radius-pill);
|
|
160
|
+
transition:
|
|
161
|
+
color var(--plinth-duration-fast) var(--plinth-easing-standard),
|
|
162
|
+
background-color var(--plinth-duration-fast) var(--plinth-easing-standard);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.plinth-data-table-sort:focus-visible {
|
|
166
|
+
outline: var(--plinth-focus-ring-width) solid var(--plinth-color-focus-ring);
|
|
167
|
+
outline-offset: var(--plinth-focus-ring-offset);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
:is([aria-sort='ascending'], [aria-sort='descending']) > .plinth-data-table-sort {
|
|
171
|
+
color: var(--plinth-color-text);
|
|
172
|
+
border-color: var(--plinth-color-border-strong);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (hover: hover) {
|
|
176
|
+
.plinth-data-table-sort:hover {
|
|
177
|
+
color: var(--plinth-color-text);
|
|
178
|
+
background-color: var(--plinth-color-surface-hover);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.plinth-data-table-sort:disabled {
|
|
183
|
+
cursor: not-allowed;
|
|
184
|
+
opacity: var(--plinth-opacity-disabled);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.plinth-data-table-sort:enabled:active {
|
|
188
|
+
background-color: var(--plinth-color-surface-sunken);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.plinth-data-table-sort-label {
|
|
192
|
+
overflow-wrap: anywhere;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
[aria-sort='none'] .plinth-data-table-sort-icon {
|
|
196
|
+
color: var(--plinth-color-text-subtle);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
[aria-sort='ascending'] .plinth-data-table-sort-down,
|
|
200
|
+
[aria-sort='descending'] .plinth-data-table-sort-up {
|
|
201
|
+
display: none;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.plinth-data-table-body {
|
|
205
|
+
display: flex;
|
|
206
|
+
flex-direction: column;
|
|
207
|
+
gap: var(--plinth-space-3);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.plinth-data-table-row {
|
|
211
|
+
position: relative;
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
padding-block: var(--plinth-space-1);
|
|
215
|
+
background-color: var(--plinth-color-surface-raised);
|
|
216
|
+
border: var(--plinth-border-width-thin) solid var(--plinth-color-border);
|
|
217
|
+
border-radius: var(--plinth-radius-container);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.plinth-data-table-cell {
|
|
221
|
+
display: flex;
|
|
222
|
+
gap: var(--plinth-space-3);
|
|
223
|
+
align-items: baseline;
|
|
224
|
+
justify-content: space-between;
|
|
225
|
+
padding-block: var(--plinth-space-2);
|
|
226
|
+
padding-inline: var(--plinth-space-4);
|
|
227
|
+
overflow-wrap: anywhere;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.plinth-data-table-label {
|
|
231
|
+
flex: none;
|
|
232
|
+
max-inline-size: 50%;
|
|
233
|
+
font-size: var(--plinth-font-size-sm);
|
|
234
|
+
color: var(--plinth-color-text-muted);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.plinth-data-table-value {
|
|
238
|
+
min-inline-size: 0;
|
|
239
|
+
text-align: end;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.plinth-data-table-cell[data-kind='title'] {
|
|
243
|
+
order: -1;
|
|
244
|
+
font-weight: var(--plinth-font-weight-medium);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.plinth-data-table-cell:is([data-kind='title'], [data-kind='actions']) .plinth-data-table-label {
|
|
248
|
+
display: none;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.plinth-data-table-cell[data-kind='title'] .plinth-data-table-value {
|
|
252
|
+
text-align: start;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.plinth-data-table-cell[data-kind='actions'] {
|
|
256
|
+
justify-content: flex-end;
|
|
257
|
+
order: 1;
|
|
258
|
+
padding-block-start: var(--plinth-space-3);
|
|
259
|
+
margin-block-start: var(--plinth-space-1);
|
|
260
|
+
border-block-start: var(--plinth-border-width-thin) solid var(--plinth-color-divider);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.plinth-data-table-cell[data-kind='actions'] .plinth-data-table-value {
|
|
264
|
+
display: flex;
|
|
265
|
+
flex-wrap: wrap;
|
|
266
|
+
gap: var(--plinth-space-2);
|
|
267
|
+
align-items: center;
|
|
268
|
+
justify-content: flex-end;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.plinth-data-table [data-numeric] {
|
|
272
|
+
font-variant-numeric: tabular-nums;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.plinth-data-table [data-phone='hidden'] {
|
|
276
|
+
display: none;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.plinth-data-table-row[aria-selected='true'] {
|
|
280
|
+
background-color: var(--plinth-color-accent-subtle);
|
|
281
|
+
border-color: var(--plinth-color-accent);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* The selected row's edge, so the tint is not the only sign of it. */
|
|
285
|
+
.plinth-data-table-row[aria-selected='true']::before {
|
|
286
|
+
position: absolute;
|
|
287
|
+
inset-block: var(--plinth-space-3);
|
|
288
|
+
inset-inline-start: 0;
|
|
289
|
+
inline-size: var(--plinth-border-width-thick);
|
|
290
|
+
content: '';
|
|
291
|
+
background-color: var(--plinth-color-accent);
|
|
292
|
+
border-radius: var(--plinth-radius-pill);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.plinth-data-table-empty {
|
|
296
|
+
display: block;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.plinth-data-table-empty-cell {
|
|
300
|
+
display: block;
|
|
301
|
+
padding-block: var(--plinth-space-8);
|
|
302
|
+
padding-inline: var(--plinth-space-4);
|
|
303
|
+
color: var(--plinth-color-text-muted);
|
|
304
|
+
text-align: center;
|
|
305
|
+
overflow-wrap: anywhere;
|
|
306
|
+
background-color: var(--plinth-color-surface-raised);
|
|
307
|
+
border: var(--plinth-border-width-thin) solid var(--plinth-color-border);
|
|
308
|
+
border-radius: var(--plinth-radius-container);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* From 768px: a table. */
|
|
312
|
+
@media (min-width: 768px) {
|
|
313
|
+
.plinth-data-table {
|
|
314
|
+
display: table;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.plinth-data-table-caption {
|
|
318
|
+
display: table-caption;
|
|
319
|
+
padding-inline: 0;
|
|
320
|
+
caption-side: top;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.plinth-data-table-head {
|
|
324
|
+
display: table-header-group;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.plinth-data-table-head-row,
|
|
328
|
+
.plinth-data-table-head-row:has(> [aria-sort]:not([data-phone='hidden'])) {
|
|
329
|
+
display: table-row;
|
|
330
|
+
margin: 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.plinth-data-table-column,
|
|
334
|
+
.plinth-data-table-column:not([aria-sort]),
|
|
335
|
+
.plinth-data-table [data-phone='hidden'].plinth-data-table-column {
|
|
336
|
+
position: static;
|
|
337
|
+
box-sizing: border-box;
|
|
338
|
+
display: table-cell;
|
|
339
|
+
inline-size: auto;
|
|
340
|
+
block-size: var(--plinth-touch-target);
|
|
341
|
+
padding-block: 0;
|
|
342
|
+
padding-inline: var(--plinth-space-3);
|
|
343
|
+
overflow: visible;
|
|
344
|
+
vertical-align: middle;
|
|
345
|
+
white-space: normal;
|
|
346
|
+
border-block-end: var(--plinth-border-width-thin) solid var(--plinth-color-border);
|
|
347
|
+
clip-path: none;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.plinth-data-table-sort {
|
|
351
|
+
padding-inline: var(--plinth-space-1);
|
|
352
|
+
margin-inline: calc(var(--plinth-space-1) * -1);
|
|
353
|
+
background-color: transparent;
|
|
354
|
+
border: 0;
|
|
355
|
+
border-radius: var(--plinth-radius-control);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
@media (hover: hover) {
|
|
359
|
+
.plinth-data-table-sort:hover {
|
|
360
|
+
background-color: transparent;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.plinth-data-table-sort:enabled:active {
|
|
365
|
+
background-color: var(--plinth-color-surface-sunken);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.plinth-data-table-body {
|
|
369
|
+
display: table-row-group;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.plinth-data-table-row {
|
|
373
|
+
position: static;
|
|
374
|
+
display: table-row;
|
|
375
|
+
padding: 0;
|
|
376
|
+
background-color: transparent;
|
|
377
|
+
border: 0;
|
|
378
|
+
border-radius: 0;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.plinth-data-table-cell,
|
|
382
|
+
.plinth-data-table-cell[data-kind='actions'],
|
|
383
|
+
.plinth-data-table [data-phone='hidden'].plinth-data-table-cell {
|
|
384
|
+
position: relative;
|
|
385
|
+
box-sizing: border-box;
|
|
386
|
+
display: table-cell;
|
|
387
|
+
block-size: var(--plinth-data-table-row-height);
|
|
388
|
+
padding-block: var(--plinth-space-2);
|
|
389
|
+
padding-inline: var(--plinth-space-3);
|
|
390
|
+
margin: 0;
|
|
391
|
+
vertical-align: middle;
|
|
392
|
+
text-align: start;
|
|
393
|
+
overflow-wrap: break-word;
|
|
394
|
+
border: 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.plinth-data-table-row + .plinth-data-table-row > .plinth-data-table-cell {
|
|
398
|
+
border-block-start: var(--plinth-border-width-thin) solid var(--plinth-color-divider);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.plinth-data-table-label {
|
|
402
|
+
display: none;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.plinth-data-table-value {
|
|
406
|
+
text-align: inherit;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.plinth-data-table-cell[data-kind='actions'] .plinth-data-table-value {
|
|
410
|
+
display: inline-flex;
|
|
411
|
+
flex-wrap: nowrap;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.plinth-data-table-column[data-numeric],
|
|
415
|
+
.plinth-data-table-cell[data-numeric] {
|
|
416
|
+
text-align: end;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.plinth-data-table-row[aria-selected='true'] {
|
|
420
|
+
border-color: transparent;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.plinth-data-table-row[aria-selected='true'] > .plinth-data-table-cell {
|
|
424
|
+
background-color: var(--plinth-color-accent-subtle);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.plinth-data-table-row[aria-selected='true']::before {
|
|
428
|
+
content: none;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.plinth-data-table-row[aria-selected='true'] > .plinth-data-table-cell:first-child::before {
|
|
432
|
+
position: absolute;
|
|
433
|
+
inset-block: 0;
|
|
434
|
+
inset-inline-start: 0;
|
|
435
|
+
inline-size: var(--plinth-border-width-thick);
|
|
436
|
+
content: '';
|
|
437
|
+
background-color: var(--plinth-color-accent);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.plinth-data-table-empty {
|
|
441
|
+
display: table-row;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.plinth-data-table-empty-cell {
|
|
445
|
+
display: table-cell;
|
|
446
|
+
background-color: transparent;
|
|
447
|
+
border: 0;
|
|
448
|
+
border-radius: 0;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
@media (min-width: 1280px) {
|
|
453
|
+
.plinth-data-table-column,
|
|
454
|
+
.plinth-data-table-column:not([aria-sort]),
|
|
455
|
+
.plinth-data-table [data-phone='hidden'].plinth-data-table-column,
|
|
456
|
+
.plinth-data-table-cell,
|
|
457
|
+
.plinth-data-table-cell[data-kind='actions'],
|
|
458
|
+
.plinth-data-table [data-phone='hidden'].plinth-data-table-cell {
|
|
459
|
+
padding-inline: var(--plinth-space-4);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
@media (forced-colors: active) {
|
|
464
|
+
.plinth-data-table-row[aria-selected='true'],
|
|
465
|
+
.plinth-data-table-row[aria-selected='true'] > .plinth-data-table-cell {
|
|
466
|
+
color: HighlightText;
|
|
467
|
+
background-color: Highlight;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|