@micha.bigler/ui-core-micha 2.34.0 → 2.36.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/WORK_ORDERS.md +17 -3
- package/dev/entries.jsx +79 -0
- package/dev/main.jsx +7 -1
- package/dist/components/MFAComponent.js +7 -6
- package/dist/components/SocialLoginButtons.js +2 -1
- package/dist/components/charts/ChartFrame.js +3 -1
- package/dist/components/charts/exportChart.js +2 -2
- package/dist/i18n/authTranslations.js +24 -0
- package/dist/i18n/sectionNavTranslations.js +20 -0
- package/dist/index.js +3 -1
- package/dist/layout/SectionNav.js +138 -0
- package/dist/pages/AccountPage.js +44 -2
- package/dist/theme/index.js +1 -1
- package/dist/theme/kitSxRegistry.js +8 -1
- package/dist/theme/themeCompleteness.js +218 -14
- package/package.json +1 -1
- package/src/components/MFAComponent.jsx +7 -6
- package/src/components/SocialLoginButtons.jsx +2 -1
- package/src/components/charts/ChartFrame.jsx +7 -1
- package/src/components/charts/exportChart.js +2 -2
- package/src/i18n/authTranslations.ts +24 -0
- package/src/i18n/sectionNavTranslations.ts +20 -0
- package/src/index.js +3 -0
- package/src/layout/SectionNav.jsx +299 -0
- package/src/pages/AccountPage.jsx +55 -14
- package/src/theme/index.js +1 -0
- package/src/theme/kitSxRegistry.js +11 -1
- package/src/theme/themeCompleteness.js +237 -14
- package/tests/AccountPage.test.jsx +193 -0
- package/tests/ChartFrame.test.jsx +5 -0
- package/tests/SectionNav.test.jsx +274 -0
- package/tests/themeCompleteness.test.js +156 -4
- package/work-orders/AUTH-4.md +183 -0
- package/work-orders/I18N-1.md +254 -0
- package/work-orders/SHELL-5.md +345 -0
- package/work-orders/THEME-5.md +341 -0
- package/work-orders/assets/AUTH-4-user-list-card.html +407 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="de">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>AUTH-4 — the account user list on a phone (prototype)</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* ------------------------------------------------------------------
|
|
9
|
+
Values are ui-core-micha baseline tokens (src/theme/tokens.js).
|
|
10
|
+
The accent is a NEUTRAL PLACEHOLDER: `palette.primary` is app
|
|
11
|
+
identity and the baseline deliberately has none, so every app
|
|
12
|
+
supplies its own. Judge composition here, not the accent hue.
|
|
13
|
+
------------------------------------------------------------------ */
|
|
14
|
+
:root {
|
|
15
|
+
--text-primary: #212529; /* palette.text.primary */
|
|
16
|
+
--text-secondary: #5B6670; /* palette.text.secondary */
|
|
17
|
+
--text-muted: #6A7178; /* palette.text.disabled */
|
|
18
|
+
--bg-default: #FAFAFA; /* palette.background.default */
|
|
19
|
+
--surface: #FFFFFF; /* palette.background.paper */
|
|
20
|
+
--subtle: #F4F5F6; /* palette.background.subtle (THEME-3) */
|
|
21
|
+
--divider: rgba(33,37,41,.10);
|
|
22
|
+
--control-border: rgba(33,37,41,.50);
|
|
23
|
+
--success-bg: #E5F4E9; --success-text: #35794A;
|
|
24
|
+
--stale-bg: #EAEDF1; --stale-text: #5B6B7D;
|
|
25
|
+
--error: #BF3227;
|
|
26
|
+
--radius-control: 3px; /* shape.borderRadius */
|
|
27
|
+
--radius-card: 8px; /* radius.card */
|
|
28
|
+
--sp-1: 8px; --sp-2: 16px; --sp-3: 24px; --sp-4: 32px;
|
|
29
|
+
--touch: 44px; /* density.touchTarget */
|
|
30
|
+
--control-h: 40px; /* density.controlHeight */
|
|
31
|
+
--cell-pad: 10px 16px; /* density.tableCellPadding */
|
|
32
|
+
--accent: #3D5A99; /* PLACEHOLDER, see note above */
|
|
33
|
+
}
|
|
34
|
+
* { box-sizing: border-box; }
|
|
35
|
+
body {
|
|
36
|
+
margin: 0; padding: var(--sp-3); background: #EDEFF1; color: var(--text-primary);
|
|
37
|
+
font-family: 'DM Sans', system-ui, -apple-system, sans-serif;
|
|
38
|
+
font-size: 14px; line-height: 1.55;
|
|
39
|
+
}
|
|
40
|
+
.doc { max-width: 1400px; margin: 0 auto; }
|
|
41
|
+
h1 { font-size: 28px; font-weight: 600; line-height: 1.22; margin: 0 0 var(--sp-1); }
|
|
42
|
+
h2 { font-size: 20px; font-weight: 600; margin: var(--sp-4) 0 var(--sp-1); }
|
|
43
|
+
p { margin: 0 0 var(--sp-1); max-width: 76ch; }
|
|
44
|
+
.lede { color: var(--text-secondary); }
|
|
45
|
+
code { font-family: ui-monospace, Menlo, monospace; font-size: 12.5px;
|
|
46
|
+
background: var(--stale-bg); padding: 1px 5px; border-radius: var(--radius-control); }
|
|
47
|
+
.note { border-left: 3px solid var(--accent); background: var(--surface);
|
|
48
|
+
padding: var(--sp-1) var(--sp-2); margin: var(--sp-2) 0;
|
|
49
|
+
border-radius: 0 var(--radius-card) var(--radius-card) 0; }
|
|
50
|
+
.note.warn { border-left-color: var(--error); }
|
|
51
|
+
.note.pick { border-left-color: var(--success-text); }
|
|
52
|
+
.note p:last-child { margin-bottom: 0; }
|
|
53
|
+
|
|
54
|
+
.frames { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: flex-start; }
|
|
55
|
+
.frame { background: var(--surface); border: 1px solid var(--divider);
|
|
56
|
+
border-radius: var(--radius-card); overflow: hidden; }
|
|
57
|
+
.frame-label { font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
|
|
58
|
+
color: var(--text-muted); padding: 6px var(--sp-1);
|
|
59
|
+
border-bottom: 1px solid var(--divider); background: var(--bg-default); }
|
|
60
|
+
.frame-label.bad { color: var(--error); background: #FBEAE8; }
|
|
61
|
+
.w375 { width: 375px; flex: 0 0 375px; }
|
|
62
|
+
.w1280 { width: 1280px; max-width: 100%; }
|
|
63
|
+
/* Wide content scrolls inside its own frame so the page body never scrolls sideways. */
|
|
64
|
+
.scroll-x { overflow-x: auto; }
|
|
65
|
+
.pane { background: var(--bg-default); padding: var(--sp-2); }
|
|
66
|
+
.pane.mobile { padding: var(--sp-1); }
|
|
67
|
+
|
|
68
|
+
/* --- shared controls (stay in both modes) ------------------------- */
|
|
69
|
+
.toolbar { display: flex; gap: var(--sp-1); align-items: center; flex-wrap: wrap;
|
|
70
|
+
margin-bottom: var(--sp-1); }
|
|
71
|
+
.search { flex: 1 1 160px; min-width: 0; height: var(--control-h);
|
|
72
|
+
border: 1px solid var(--control-border); border-radius: var(--radius-control);
|
|
73
|
+
display: flex; align-items: center; gap: 6px; padding: 0 10px;
|
|
74
|
+
background: var(--surface); color: var(--text-muted); font-size: 14px; }
|
|
75
|
+
.search svg { width: 18px; height: 18px; flex: none; }
|
|
76
|
+
.sortctl { height: var(--control-h); border: 1px solid var(--control-border);
|
|
77
|
+
border-radius: var(--radius-control); display: flex; align-items: center; gap: 6px;
|
|
78
|
+
padding: 0 10px; background: var(--surface); font-size: 13px; white-space: nowrap; }
|
|
79
|
+
.sortctl svg { width: 16px; height: 16px; }
|
|
80
|
+
|
|
81
|
+
/* --- the table (desktop, unchanged) ------------------------------- */
|
|
82
|
+
table { border-collapse: collapse; width: 100%; font-size: 14px; background: var(--surface); }
|
|
83
|
+
th, td { padding: var(--cell-pad); border-bottom: 1px solid var(--divider);
|
|
84
|
+
text-align: left; vertical-align: middle; white-space: nowrap; }
|
|
85
|
+
th { font-size: 13px; font-weight: 600; color: var(--text-secondary); }
|
|
86
|
+
th .sortable { display: inline-flex; align-items: center; gap: 4px; }
|
|
87
|
+
th svg { width: 14px; height: 14px; opacity: .5; }
|
|
88
|
+
.selectbox { display: inline-flex; align-items: center; justify-content: space-between;
|
|
89
|
+
gap: 6px; min-width: 120px; height: 36px; padding: 0 8px;
|
|
90
|
+
border: 1px solid var(--control-border); border-radius: var(--radius-control);
|
|
91
|
+
font-size: 14px; background: var(--surface); }
|
|
92
|
+
.selectbox svg { width: 16px; height: 16px; opacity: .6; }
|
|
93
|
+
.iconbtn { width: var(--touch); height: var(--touch); display: inline-grid; place-items: center;
|
|
94
|
+
border-radius: 50%; color: var(--text-secondary); }
|
|
95
|
+
.iconbtn svg { width: 20px; height: 20px; }
|
|
96
|
+
.iconbtn.danger { color: var(--error); }
|
|
97
|
+
.flag { display: inline-grid; place-items: center; width: 24px; height: 24px; }
|
|
98
|
+
.flag svg { width: 18px; height: 18px; }
|
|
99
|
+
.flag.yes { color: var(--success-text); }
|
|
100
|
+
.flag.no { color: var(--text-muted); }
|
|
101
|
+
|
|
102
|
+
/* the clipped "today" demo */
|
|
103
|
+
.clip { overflow: hidden; position: relative; }
|
|
104
|
+
.clip::after { content: ''; position: absolute; top: 0; right: 0; bottom: 0; width: 28px;
|
|
105
|
+
background: linear-gradient(to right, rgba(255,255,255,0), var(--error)); opacity: .18; }
|
|
106
|
+
.clip table { width: 1053px; }
|
|
107
|
+
|
|
108
|
+
/* --- the card list (mobile, the proposal) ------------------------- */
|
|
109
|
+
.cardlist { display: flex; flex-direction: column; gap: var(--sp-1); }
|
|
110
|
+
.ucard { background: var(--surface); border: 1px solid var(--divider);
|
|
111
|
+
border-radius: var(--radius-card); padding: var(--sp-2); box-shadow: none; }
|
|
112
|
+
.ucard-head { display: flex; align-items: flex-start; gap: var(--sp-1); }
|
|
113
|
+
.ucard-id { min-width: 0; flex: 1 1 auto; }
|
|
114
|
+
.ucard-email { font-size: 15px; font-weight: 600; overflow-wrap: anywhere; } /* subtitle1 */
|
|
115
|
+
.ucard-name { font-size: 13px; color: var(--text-secondary); } /* body2 */
|
|
116
|
+
.ucard-name.missing { color: var(--text-muted); font-style: italic; }
|
|
117
|
+
.chips { display: flex; gap: 6px; flex-wrap: wrap; margin-top: var(--sp-1); }
|
|
118
|
+
.chip { display: inline-flex; align-items: center; gap: 4px; height: 32px; padding: 0 10px;
|
|
119
|
+
border-radius: 16px; font-size: 13px; font-weight: 500; }
|
|
120
|
+
.chip svg { width: 15px; height: 15px; }
|
|
121
|
+
.chip.new { background: var(--success-bg); color: var(--success-text); }
|
|
122
|
+
.chip.never { background: var(--stale-bg); color: var(--stale-text); }
|
|
123
|
+
.ucard-foot { display: flex; align-items: center; gap: var(--sp-1);
|
|
124
|
+
margin-top: var(--sp-2); padding-top: var(--sp-1);
|
|
125
|
+
border-top: 1px solid var(--divider); }
|
|
126
|
+
.ucard-foot .selectbox { flex: 1 1 auto; min-width: 0; height: var(--control-h); }
|
|
127
|
+
.rolelabel { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
|
|
128
|
+
color: var(--text-muted); display: block; margin-bottom: 2px; }
|
|
129
|
+
.pagination { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-1);
|
|
130
|
+
padding: var(--sp-1) 0; font-size: 13px; color: var(--text-secondary); }
|
|
131
|
+
|
|
132
|
+
.measure { font-variant-numeric: tabular-nums; }
|
|
133
|
+
table.spec th, table.spec td { white-space: normal; }
|
|
134
|
+
</style>
|
|
135
|
+
</head>
|
|
136
|
+
<body>
|
|
137
|
+
<div class="doc">
|
|
138
|
+
|
|
139
|
+
<h1>AUTH-4 — the account user list on a phone</h1>
|
|
140
|
+
<p class="lede">Composition spec for <code>UserListComponent</code> below the <code>md</code>
|
|
141
|
+
breakpoint. Drawn from <code>ui-core-micha</code>'s baseline tokens.</p>
|
|
142
|
+
|
|
143
|
+
<div class="note">
|
|
144
|
+
<p><strong>Declared coverage.</strong> One surface: the <strong>users</strong> section of
|
|
145
|
+
<code>AccountPage</code> — its toolbar, its rows, its pagination. Nothing else.</p>
|
|
146
|
+
<p><strong>Not covered, therefore unchanged:</strong> every other account section; the section
|
|
147
|
+
navigation itself (that is <code>SHELL-5</code>); the delete confirmation dialog; the invite flow;
|
|
148
|
+
the role list an app supplies; and the <strong>desktop table above <code>md</code></strong>, which
|
|
149
|
+
this work order deliberately leaves exactly as it is.</p>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div class="note warn">
|
|
153
|
+
<p><strong>Authority.</strong> The Envelope wins on scope. The baseline wins on token values. This
|
|
154
|
+
sheet wins on composition — what sits where, at which size, in which state. <strong>The accent is a
|
|
155
|
+
neutral placeholder</strong>: <code>palette.primary</code> is app identity and the baseline has none,
|
|
156
|
+
so each app supplies its own. Do not read the blue as a decision.</p>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<h2>1. The defect, measured</h2>
|
|
160
|
+
|
|
161
|
+
<p>Measured 2026-08-11 in cockpit at <code>innerWidth</code> <strong>411</strong> CSS px — the mobile
|
|
162
|
+
preset yields 411, not 375, so at the real target every figure is worse. The table renders
|
|
163
|
+
<strong>1053 px inside a 325 px container</strong>. The container scrolls
|
|
164
|
+
(<code>overflow-x: auto</code>) and the document does <em>not</em> overflow, so this is not a broken
|
|
165
|
+
layout: it is a desktop table shown unchanged on a phone.</p>
|
|
166
|
+
|
|
167
|
+
<div class="frame w375">
|
|
168
|
+
<div class="frame-label bad">today — 411 px, 1053 px of table in a 325 px container</div>
|
|
169
|
+
<div class="pane mobile">
|
|
170
|
+
<div class="toolbar">
|
|
171
|
+
<div class="search">
|
|
172
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.8l-.3-.3a6.5 6.5 0 1 0-.7.7l.3.3v.8l5 5 1.5-1.5-5-5Zm-6 0a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9Z"/></svg>
|
|
173
|
+
Suchen
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="clip">
|
|
177
|
+
<table>
|
|
178
|
+
<thead><tr>
|
|
179
|
+
<th style="width:220px">Email address</th><th style="width:180px">Name</th>
|
|
180
|
+
<th style="width:90px">New</th><th style="width:163px">Successful Login</th>
|
|
181
|
+
<th style="width:180px">Role</th><th style="width:220px">Actions</th>
|
|
182
|
+
</tr></thead>
|
|
183
|
+
<tbody>
|
|
184
|
+
<tr>
|
|
185
|
+
<td>anna.baumgartner@example.ch</td><td>Anna Baumgartner</td>
|
|
186
|
+
<td><span class="flag yes"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4Z"/></svg></span></td>
|
|
187
|
+
<td><span class="flag no"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 13H5v-2h14Z"/></svg></span></td>
|
|
188
|
+
<td><span class="selectbox">admin <svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 10l5 5 5-5Z"/></svg></span></td>
|
|
189
|
+
<td><span class="iconbtn danger"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6Zm3-7h6v7H9ZM15.5 4l-1-1h-5l-1 1H5v2h14V4Z"/></svg></span></td>
|
|
190
|
+
</tr>
|
|
191
|
+
</tbody>
|
|
192
|
+
</table>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="pagination">Rows per page: 25 · 1–1 of 1</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<div class="note">
|
|
199
|
+
<p><strong>253 px of that width is columns nobody chose.</strong> <code>New</code> (90) and
|
|
200
|
+
<code>Successful Login</code> (163) are default-on props an app can switch off today —
|
|
201
|
+
<code>AccountPage</code> forwards all of them — and cockpit simply left the defaults. But the four
|
|
202
|
+
remaining columns are Email 220 + Name 180 + Role 180 + Actions 220 = <strong>800 px</strong>
|
|
203
|
+
against a 325 px container. <strong>Configuration cannot fix this; only structure can.</strong>
|
|
204
|
+
The defaults are deliberately left alone here — flipping them would be a silent estate-wide change.</p>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<h2>2. The proposal: a card per user below <code>md</code></h2>
|
|
208
|
+
|
|
209
|
+
<p>The screen exists so an administrator can <strong>change a role</strong> or <strong>remove
|
|
210
|
+
someone</strong>. Both are per-row controls, so any layout that puts them behind horizontal
|
|
211
|
+
scrolling defeats the screen's purpose. A card brings them to thumb reach.</p>
|
|
212
|
+
|
|
213
|
+
<div class="frames">
|
|
214
|
+
<div class="frame w375">
|
|
215
|
+
<div class="frame-label">proposed — 375 px</div>
|
|
216
|
+
<div class="pane mobile">
|
|
217
|
+
<div class="toolbar">
|
|
218
|
+
<div class="search">
|
|
219
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.8l-.3-.3a6.5 6.5 0 1 0-.7.7l.3.3v.8l5 5 1.5-1.5-5-5Zm-6 0a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9Z"/></svg>
|
|
220
|
+
Suchen
|
|
221
|
+
</div>
|
|
222
|
+
<div class="sortctl">Email
|
|
223
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 10l5 5 5-5Z"/></svg>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="cardlist">
|
|
228
|
+
<div class="ucard">
|
|
229
|
+
<div class="ucard-head">
|
|
230
|
+
<div class="ucard-id">
|
|
231
|
+
<div class="ucard-email">anna.baumgartner@example.ch</div>
|
|
232
|
+
<div class="ucard-name">Anna Baumgartner</div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="chips">
|
|
236
|
+
<span class="chip new">
|
|
237
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 9.2 8.6 2 9.2l5.5 4.8L5.8 21 12 17.3 18.2 21l-1.7-7 5.5-4.8-7.2-.6Z"/></svg>
|
|
238
|
+
Neu
|
|
239
|
+
</span>
|
|
240
|
+
<span class="chip never">
|
|
241
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 4a8 8 0 1 0 8 8h-2a6 6 0 1 1-6-6Zm1 3v5l4 2-.9 1.7L11 13V7Z"/></svg>
|
|
242
|
+
Noch nie angemeldet
|
|
243
|
+
</span>
|
|
244
|
+
</div>
|
|
245
|
+
<div class="ucard-foot">
|
|
246
|
+
<div style="flex:1 1 auto; min-width:0">
|
|
247
|
+
<span class="rolelabel">Rolle</span>
|
|
248
|
+
<span class="selectbox" style="width:100%">admin
|
|
249
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 10l5 5 5-5Z"/></svg>
|
|
250
|
+
</span>
|
|
251
|
+
</div>
|
|
252
|
+
<span class="iconbtn danger" title="Löschen">
|
|
253
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6Zm3-7h6v7H9ZM15.5 4l-1-1h-5l-1 1H5v2h14V4Z"/></svg>
|
|
254
|
+
</span>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div class="ucard">
|
|
259
|
+
<div class="ucard-head">
|
|
260
|
+
<div class="ucard-id">
|
|
261
|
+
<div class="ucard-email">t.k@example.ch</div>
|
|
262
|
+
<div class="ucard-name missing">kein Name hinterlegt</div>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
<div class="chips">
|
|
266
|
+
<span class="chip never" style="background:var(--success-bg); color:var(--success-text)">
|
|
267
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4Z"/></svg>
|
|
268
|
+
Angemeldet
|
|
269
|
+
</span>
|
|
270
|
+
</div>
|
|
271
|
+
<div class="ucard-foot">
|
|
272
|
+
<div style="flex:1 1 auto; min-width:0">
|
|
273
|
+
<span class="rolelabel">Rolle</span>
|
|
274
|
+
<span class="selectbox" style="width:100%">none
|
|
275
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 10l5 5 5-5Z"/></svg>
|
|
276
|
+
</span>
|
|
277
|
+
</div>
|
|
278
|
+
<span class="iconbtn danger" title="Löschen">
|
|
279
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6Zm3-7h6v7H9ZM15.5 4l-1-1h-5l-1 1H5v2h14V4Z"/></svg>
|
|
280
|
+
</span>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<div class="ucard">
|
|
285
|
+
<div class="ucard-head">
|
|
286
|
+
<div class="ucard-id">
|
|
287
|
+
<div class="ucard-email">michael.hofstetter-brunner@verwaltung.example.ch</div>
|
|
288
|
+
<div class="ucard-name">Michael Hofstetter-Brunner</div>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="chips">
|
|
292
|
+
<span class="chip never" style="background:var(--success-bg); color:var(--success-text)">
|
|
293
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4Z"/></svg>
|
|
294
|
+
Angemeldet
|
|
295
|
+
</span>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="ucard-foot">
|
|
298
|
+
<div style="flex:1 1 auto; min-width:0">
|
|
299
|
+
<span class="rolelabel">Rolle</span>
|
|
300
|
+
<span class="selectbox" style="width:100%">sekretariat
|
|
301
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 10l5 5 5-5Z"/></svg>
|
|
302
|
+
</span>
|
|
303
|
+
</div>
|
|
304
|
+
<span class="iconbtn danger" title="Löschen">
|
|
305
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6Zm3-7h6v7H9ZM15.5 4l-1-1h-5l-1 1H5v2h14V4Z"/></svg>
|
|
306
|
+
</span>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="pagination">25 pro Seite · 1–3 von 3</div>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<div class="note pick">
|
|
316
|
+
<p><strong>Four composition decisions, each with its reason:</strong></p>
|
|
317
|
+
<p><strong>The email is the card title, the name is secondary.</strong> The email is the identity an
|
|
318
|
+
administrator searches by and the only guaranteed-present field — a user may have no name. A card
|
|
319
|
+
with a missing name shows a muted placeholder rather than an empty line.</p>
|
|
320
|
+
<p><strong>The two boolean columns become labelled chips.</strong> In the table they are icon-only
|
|
321
|
+
with a tooltip, which a touch device cannot show at all. Chips carry the words. This is a small,
|
|
322
|
+
deliberate improvement over parity, not drift — and it is why the flags cost vertical space rather
|
|
323
|
+
than being dropped.</p>
|
|
324
|
+
<p><strong>Role and delete sit together in a footer, separated by a divider.</strong> They are the
|
|
325
|
+
two actions; grouping them keeps the card's top half purely identifying. The role select fills the
|
|
326
|
+
remaining width so its label is never truncated; delete keeps the full 44 px touch target.</p>
|
|
327
|
+
<p><strong>Sorting needs its own control.</strong> A card list has no column headers to click, so the
|
|
328
|
+
sortable-column affordance disappears with the table. A compact sort control sits beside the search
|
|
329
|
+
field. <strong>Without it the mobile view silently loses a capability the desktop has</strong> — the
|
|
330
|
+
kind of regression that is invisible in a diff.</p>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<h2>3. Above <code>md</code>: nothing changes</h2>
|
|
334
|
+
|
|
335
|
+
<p>The table is right for a desktop — six columns are scannable at 1136 px, and it was measured
|
|
336
|
+
with 570 px of headroom in the section strip beside it. Shown here only so "unchanged" is part
|
|
337
|
+
of the spec rather than an omission.</p>
|
|
338
|
+
|
|
339
|
+
<div class="frame w1280 scroll-x">
|
|
340
|
+
<div class="frame-label">unchanged — 1280 px</div>
|
|
341
|
+
<div class="pane">
|
|
342
|
+
<div class="toolbar">
|
|
343
|
+
<div class="search" style="max-width:320px">
|
|
344
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.8l-.3-.3a6.5 6.5 0 1 0-.7.7l.3.3v.8l5 5 1.5-1.5-5-5Zm-6 0a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9Z"/></svg>
|
|
345
|
+
Suchen
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
<table>
|
|
349
|
+
<thead><tr>
|
|
350
|
+
<th><span class="sortable">Email address <svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 14l5 5 5-5Z"/></svg></span></th>
|
|
351
|
+
<th><span class="sortable">Name <svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 14l5 5 5-5Z"/></svg></span></th>
|
|
352
|
+
<th>New</th><th>Successful Login</th><th>Role</th><th>Actions</th>
|
|
353
|
+
</tr></thead>
|
|
354
|
+
<tbody>
|
|
355
|
+
<tr>
|
|
356
|
+
<td>anna.baumgartner@example.ch</td><td>Anna Baumgartner</td>
|
|
357
|
+
<td><span class="flag yes"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4Z"/></svg></span></td>
|
|
358
|
+
<td><span class="flag no"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 13H5v-2h14Z"/></svg></span></td>
|
|
359
|
+
<td><span class="selectbox">admin <svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 10l5 5 5-5Z"/></svg></span></td>
|
|
360
|
+
<td><span class="iconbtn danger"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6Zm3-7h6v7H9ZM15.5 4l-1-1h-5l-1 1H5v2h14V4Z"/></svg></span></td>
|
|
361
|
+
</tr>
|
|
362
|
+
<tr>
|
|
363
|
+
<td>t.k@example.ch</td><td style="color:var(--text-muted); font-style:italic">kein Name hinterlegt</td>
|
|
364
|
+
<td><span class="flag no"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 13H5v-2h14Z"/></svg></span></td>
|
|
365
|
+
<td><span class="flag yes"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4Z"/></svg></span></td>
|
|
366
|
+
<td><span class="selectbox">none <svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 10l5 5 5-5Z"/></svg></span></td>
|
|
367
|
+
<td><span class="iconbtn danger"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6Zm3-7h6v7H9ZM15.5 4l-1-1h-5l-1 1H5v2h14V4Z"/></svg></span></td>
|
|
368
|
+
</tr>
|
|
369
|
+
</tbody>
|
|
370
|
+
</table>
|
|
371
|
+
<div class="pagination">Rows per page: 25 · 1–2 of 2</div>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<h2>4. What the app supplies, and what that costs</h2>
|
|
376
|
+
|
|
377
|
+
<p><code>UserListComponent</code> already takes <code>extraColumns</code> and
|
|
378
|
+
<code>extraRowActions</code>, so the column set is app-configurable. The card must therefore render
|
|
379
|
+
columns it has never seen.</p>
|
|
380
|
+
|
|
381
|
+
<table class="spec">
|
|
382
|
+
<thead><tr><th>Input</th><th>In the card</th></tr></thead>
|
|
383
|
+
<tbody>
|
|
384
|
+
<tr><td><code>extraColumns</code></td>
|
|
385
|
+
<td>rendered as <strong>label: value</strong> rows below the chips, in the order given. No new
|
|
386
|
+
required field on the column definition — an app that wants nothing else gets nothing else,
|
|
387
|
+
and one that already passes columns keeps working unchanged.</td></tr>
|
|
388
|
+
<tr><td><code>extraRowActions</code></td>
|
|
389
|
+
<td>join delete in the footer. Beyond three actions total they must collapse into an overflow
|
|
390
|
+
menu, or the footer wraps and the card loses its shape.</td></tr>
|
|
391
|
+
<tr><td>the three toggleable columns</td>
|
|
392
|
+
<td>as chips when on, absent when off. An app that switches both flags off gets a shorter card,
|
|
393
|
+
not an empty chip row.</td></tr>
|
|
394
|
+
</tbody>
|
|
395
|
+
</table>
|
|
396
|
+
|
|
397
|
+
<div class="note warn">
|
|
398
|
+
<p><strong>What this sheet cannot show.</strong> No motion, no interaction states beyond the resting
|
|
399
|
+
one — no hover, focus ring, pressed, or the open select menu. The delete confirmation dialog is
|
|
400
|
+
untouched and undrawn. A long list is drawn as three cards; the real scroll behaviour and the
|
|
401
|
+
pagination control at the bottom of a 25-card page are not represented. If DM Sans is not installed
|
|
402
|
+
locally this page falls back to the system font — sizes and weights are still exact.</p>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
</div>
|
|
406
|
+
</body>
|
|
407
|
+
</html>
|