@masmarino/gabarit 2.0.0 → 2.1.1
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/README.md +131 -85
- package/accordion/README.md +56 -0
- package/alert/README.md +87 -0
- package/app-shell/README.md +133 -0
- package/auth/README.md +155 -0
- package/auth-activate/README.md +119 -0
- package/auth-login/README.md +113 -0
- package/auth-register/README.md +104 -0
- package/auth-reset-password/README.md +120 -0
- package/autocomplete/README.md +77 -0
- package/avatar/README.md +34 -0
- package/avatar-group/README.md +47 -0
- package/badge/README.md +177 -0
- package/bar-chart/README.md +44 -0
- package/breadcrumb/README.md +38 -0
- package/button/README.md +175 -0
- package/card/README.md +179 -0
- package/checkbox/README.md +50 -0
- package/checkbox-group/README.md +78 -0
- package/confirm-danger-modal/README.md +77 -0
- package/copy-button/README.md +90 -0
- package/copy-field/README.md +54 -0
- package/date-picker/README.md +84 -0
- package/description-list/README.md +63 -0
- package/dimension-card/README.md +39 -0
- package/disclosure/README.md +48 -0
- package/divider/README.md +31 -0
- package/drawer/README.md +49 -0
- package/empty-state/README.md +84 -0
- package/fesm2022/masmarino-gabarit-accordion.mjs +243 -0
- package/fesm2022/masmarino-gabarit-accordion.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-alert.mjs +208 -0
- package/fesm2022/masmarino-gabarit-alert.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-app-shell.mjs +378 -0
- package/fesm2022/masmarino-gabarit-app-shell.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-auth-activate.mjs +415 -0
- package/fesm2022/masmarino-gabarit-auth-activate.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-auth-login.mjs +467 -0
- package/fesm2022/masmarino-gabarit-auth-login.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-auth-register.mjs +457 -0
- package/fesm2022/masmarino-gabarit-auth-register.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-auth-reset-password.mjs +364 -0
- package/fesm2022/masmarino-gabarit-auth-reset-password.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-auth.mjs +844 -0
- package/fesm2022/masmarino-gabarit-auth.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-autocomplete.mjs +475 -0
- package/fesm2022/masmarino-gabarit-autocomplete.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-avatar-group.mjs +202 -0
- package/fesm2022/masmarino-gabarit-avatar-group.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-avatar.mjs +107 -0
- package/fesm2022/masmarino-gabarit-avatar.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-badge.mjs +449 -0
- package/fesm2022/masmarino-gabarit-badge.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-bar-chart.mjs +408 -0
- package/fesm2022/masmarino-gabarit-bar-chart.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-breadcrumb.mjs +59 -0
- package/fesm2022/masmarino-gabarit-breadcrumb.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-button.mjs +374 -0
- package/fesm2022/masmarino-gabarit-button.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-card.mjs +400 -0
- package/fesm2022/masmarino-gabarit-card.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-chart.mjs +1289 -0
- package/fesm2022/masmarino-gabarit-chart.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-checkbox-group.mjs +335 -0
- package/fesm2022/masmarino-gabarit-checkbox-group.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-checkbox.mjs +170 -0
- package/fesm2022/masmarino-gabarit-checkbox.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-confirm-danger-modal.mjs +387 -0
- package/fesm2022/masmarino-gabarit-confirm-danger-modal.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-copy-button.mjs +407 -0
- package/fesm2022/masmarino-gabarit-copy-button.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-copy-field.mjs +229 -0
- package/fesm2022/masmarino-gabarit-copy-field.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-date-picker.mjs +1242 -0
- package/fesm2022/masmarino-gabarit-date-picker.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-description-list.mjs +127 -0
- package/fesm2022/masmarino-gabarit-description-list.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-dimension-card.mjs +162 -0
- package/fesm2022/masmarino-gabarit-dimension-card.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-disclosure.mjs +154 -0
- package/fesm2022/masmarino-gabarit-disclosure.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-divider.mjs +90 -0
- package/fesm2022/masmarino-gabarit-divider.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-docs.mjs +16 -2
- package/fesm2022/masmarino-gabarit-docs.mjs.map +1 -1
- package/fesm2022/masmarino-gabarit-drawer.mjs +209 -0
- package/fesm2022/masmarino-gabarit-drawer.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-empty-state.mjs +226 -0
- package/fesm2022/masmarino-gabarit-empty-state.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-file-upload.mjs +335 -0
- package/fesm2022/masmarino-gabarit-file-upload.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-floating-panel.mjs +24 -0
- package/fesm2022/masmarino-gabarit-floating-panel.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-format.mjs +345 -0
- package/fesm2022/masmarino-gabarit-format.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-funnel-chart.mjs +247 -0
- package/fesm2022/masmarino-gabarit-funnel-chart.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-gauge-bar.mjs +180 -0
- package/fesm2022/masmarino-gabarit-gauge-bar.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-git-field.mjs +127 -0
- package/fesm2022/masmarino-gabarit-git-field.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-icon-marker.mjs +150 -0
- package/fesm2022/masmarino-gabarit-icon-marker.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-icon.mjs +308 -0
- package/fesm2022/masmarino-gabarit-icon.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-input.mjs +493 -0
- package/fesm2022/masmarino-gabarit-input.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-job-graph.mjs +216 -0
- package/fesm2022/masmarino-gabarit-job-graph.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-job-status.mjs +104 -0
- package/fesm2022/masmarino-gabarit-job-status.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-line-chart.mjs +574 -0
- package/fesm2022/masmarino-gabarit-line-chart.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-list-card.mjs +320 -0
- package/fesm2022/masmarino-gabarit-list-card.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-list-row.mjs +58 -0
- package/fesm2022/masmarino-gabarit-list-row.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-list-toolbar.mjs +369 -0
- package/fesm2022/masmarino-gabarit-list-toolbar.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-menu.mjs +464 -0
- package/fesm2022/masmarino-gabarit-menu.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-mfa-enrollment.mjs +897 -0
- package/fesm2022/masmarino-gabarit-mfa-enrollment.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-mfa-settings.mjs +629 -0
- package/fesm2022/masmarino-gabarit-mfa-settings.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-modal.mjs +260 -0
- package/fesm2022/masmarino-gabarit-modal.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-nav-tabs.mjs +332 -0
- package/fesm2022/masmarino-gabarit-nav-tabs.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-notification-dot.mjs +119 -0
- package/fesm2022/masmarino-gabarit-notification-dot.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-page-header.mjs +78 -0
- package/fesm2022/masmarino-gabarit-page-header.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-page-layout.mjs +157 -0
- package/fesm2022/masmarino-gabarit-page-layout.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-pagination.mjs +415 -0
- package/fesm2022/masmarino-gabarit-pagination.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-panel.mjs +80 -0
- package/fesm2022/masmarino-gabarit-panel.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-passkey-settings.mjs +606 -0
- package/fesm2022/masmarino-gabarit-passkey-settings.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-pie-chart.mjs +317 -0
- package/fesm2022/masmarino-gabarit-pie-chart.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-popover.mjs +142 -0
- package/fesm2022/masmarino-gabarit-popover.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-radio-group.mjs +202 -0
- package/fesm2022/masmarino-gabarit-radio-group.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-save-status.mjs +142 -0
- package/fesm2022/masmarino-gabarit-save-status.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-search-bar.mjs +576 -0
- package/fesm2022/masmarino-gabarit-search-bar.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-secret-reveal.mjs +348 -0
- package/fesm2022/masmarino-gabarit-secret-reveal.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-segmented-control.mjs +272 -0
- package/fesm2022/masmarino-gabarit-segmented-control.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-select.mjs +512 -0
- package/fesm2022/masmarino-gabarit-select.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-skeleton-list.mjs +189 -0
- package/fesm2022/masmarino-gabarit-skeleton-list.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-skeleton.mjs +109 -0
- package/fesm2022/masmarino-gabarit-skeleton.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-slider.mjs +239 -0
- package/fesm2022/masmarino-gabarit-slider.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-sparkline.mjs +276 -0
- package/fesm2022/masmarino-gabarit-sparkline.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-spinner.mjs +86 -0
- package/fesm2022/masmarino-gabarit-spinner.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-stat-grid.mjs +139 -0
- package/fesm2022/masmarino-gabarit-stat-grid.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-stat-tile.mjs +312 -0
- package/fesm2022/masmarino-gabarit-stat-tile.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-stepper.mjs +219 -0
- package/fesm2022/masmarino-gabarit-stepper.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-switch.mjs +151 -0
- package/fesm2022/masmarino-gabarit-switch.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-table.mjs +206 -0
- package/fesm2022/masmarino-gabarit-table.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-tabs.mjs +210 -0
- package/fesm2022/masmarino-gabarit-tabs.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-tag-input.mjs +288 -0
- package/fesm2022/masmarino-gabarit-tag-input.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-tag.mjs +155 -0
- package/fesm2022/masmarino-gabarit-tag.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-textarea.mjs +369 -0
- package/fesm2022/masmarino-gabarit-textarea.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-timeline-chart.mjs +521 -0
- package/fesm2022/masmarino-gabarit-timeline-chart.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-toaster.mjs +220 -0
- package/fesm2022/masmarino-gabarit-toaster.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-tooltip.mjs +185 -0
- package/fesm2022/masmarino-gabarit-tooltip.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-tree.mjs +258 -0
- package/fesm2022/masmarino-gabarit-tree.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit-user-chip.mjs +106 -0
- package/fesm2022/masmarino-gabarit-user-chip.mjs.map +1 -0
- package/fesm2022/masmarino-gabarit.mjs +83 -24785
- package/fesm2022/masmarino-gabarit.mjs.map +1 -1
- package/file-upload/README.md +47 -0
- package/funnel-chart/README.md +39 -0
- package/gauge-bar/README.md +35 -0
- package/git-field/README.md +34 -0
- package/icon/README.md +52 -0
- package/icon-marker/README.md +37 -0
- package/input/README.md +120 -0
- package/job-graph/README.md +76 -0
- package/job-status/README.md +34 -0
- package/line-chart/README.md +52 -0
- package/list-card/README.md +144 -0
- package/list-row/README.md +72 -0
- package/list-toolbar/README.md +52 -0
- package/menu/README.md +90 -0
- package/mfa-enrollment/README.md +189 -0
- package/mfa-settings/README.md +154 -0
- package/modal/README.md +72 -0
- package/nav-tabs/README.md +106 -0
- package/notification-dot/README.md +43 -0
- package/package.json +329 -1
- package/page-header/README.md +56 -0
- package/page-layout/README.md +76 -0
- package/pagination/README.md +73 -0
- package/panel/README.md +56 -0
- package/passkey-settings/README.md +120 -0
- package/pie-chart/README.md +85 -0
- package/popover/README.md +62 -0
- package/radio-group/README.md +47 -0
- package/save-status/README.md +44 -0
- package/search-bar/README.md +84 -0
- package/secret-reveal/README.md +60 -0
- package/segmented-control/README.md +79 -0
- package/select/README.md +61 -0
- package/skeleton/README.md +49 -0
- package/skeleton-list/README.md +40 -0
- package/slider/README.md +44 -0
- package/sparkline/README.md +37 -0
- package/spinner/README.md +41 -0
- package/stat-grid/README.md +47 -0
- package/stat-tile/README.md +75 -0
- package/stepper/README.md +67 -0
- package/switch/README.md +51 -0
- package/table/README.md +75 -0
- package/tabs/README.md +34 -0
- package/tag/README.md +34 -0
- package/tag-input/README.md +45 -0
- package/textarea/README.md +76 -0
- package/timeline-chart/README.md +49 -0
- package/toaster/README.md +92 -0
- package/tooltip/README.md +50 -0
- package/tree/README.md +79 -0
- package/types/masmarino-gabarit-accordion.d.ts +54 -0
- package/types/masmarino-gabarit-alert.d.ts +65 -0
- package/types/masmarino-gabarit-app-shell.d.ts +102 -0
- package/types/masmarino-gabarit-auth-activate.d.ts +70 -0
- package/types/masmarino-gabarit-auth-login.d.ts +63 -0
- package/types/masmarino-gabarit-auth-register.d.ts +66 -0
- package/types/masmarino-gabarit-auth-reset-password.d.ts +53 -0
- package/types/masmarino-gabarit-auth.d.ts +543 -0
- package/types/masmarino-gabarit-autocomplete.d.ts +140 -0
- package/types/masmarino-gabarit-avatar-group.d.ts +60 -0
- package/types/masmarino-gabarit-avatar.d.ts +31 -0
- package/types/masmarino-gabarit-badge.d.ts +139 -0
- package/types/masmarino-gabarit-bar-chart.d.ts +111 -0
- package/types/masmarino-gabarit-breadcrumb.d.ts +12 -0
- package/types/masmarino-gabarit-button.d.ts +108 -0
- package/types/masmarino-gabarit-card.d.ts +102 -0
- package/types/masmarino-gabarit-chart.d.ts +361 -0
- package/types/masmarino-gabarit-checkbox-group.d.ts +113 -0
- package/types/masmarino-gabarit-checkbox.d.ts +49 -0
- package/types/masmarino-gabarit-confirm-danger-modal.d.ts +94 -0
- package/types/masmarino-gabarit-copy-button.d.ts +111 -0
- package/types/masmarino-gabarit-copy-field.d.ts +64 -0
- package/types/masmarino-gabarit-date-picker.d.ts +399 -0
- package/types/masmarino-gabarit-description-list.d.ts +40 -0
- package/types/masmarino-gabarit-dimension-card.d.ts +55 -0
- package/types/masmarino-gabarit-disclosure.d.ts +44 -0
- package/types/masmarino-gabarit-divider.d.ts +20 -0
- package/types/masmarino-gabarit-docs.d.ts +2 -1
- package/types/masmarino-gabarit-drawer.d.ts +51 -0
- package/types/masmarino-gabarit-empty-state.d.ts +67 -0
- package/types/masmarino-gabarit-file-upload.d.ts +95 -0
- package/types/masmarino-gabarit-floating-panel.d.ts +14 -0
- package/types/masmarino-gabarit-format.d.ts +54 -0
- package/types/masmarino-gabarit-funnel-chart.d.ts +78 -0
- package/types/masmarino-gabarit-gauge-bar.d.ts +54 -0
- package/types/masmarino-gabarit-git-field.d.ts +39 -0
- package/types/masmarino-gabarit-icon-marker.d.ts +47 -0
- package/types/masmarino-gabarit-icon.d.ts +23 -0
- package/types/masmarino-gabarit-input.d.ts +181 -0
- package/types/masmarino-gabarit-job-graph.d.ts +63 -0
- package/types/masmarino-gabarit-job-status.d.ts +20 -0
- package/types/masmarino-gabarit-line-chart.d.ts +153 -0
- package/types/masmarino-gabarit-list-card.d.ts +87 -0
- package/types/masmarino-gabarit-list-row.d.ts +14 -0
- package/types/masmarino-gabarit-list-toolbar.d.ts +100 -0
- package/types/masmarino-gabarit-menu.d.ts +93 -0
- package/types/masmarino-gabarit-mfa-enrollment.d.ts +142 -0
- package/types/masmarino-gabarit-mfa-settings.d.ts +89 -0
- package/types/masmarino-gabarit-modal.d.ts +60 -0
- package/types/masmarino-gabarit-nav-tabs.d.ts +65 -0
- package/types/masmarino-gabarit-notification-dot.d.ts +34 -0
- package/types/masmarino-gabarit-page-header.d.ts +20 -0
- package/types/masmarino-gabarit-page-layout.d.ts +46 -0
- package/types/masmarino-gabarit-pagination.d.ts +109 -0
- package/types/masmarino-gabarit-panel.d.ts +21 -0
- package/types/masmarino-gabarit-passkey-settings.d.ts +94 -0
- package/types/masmarino-gabarit-pie-chart.d.ts +94 -0
- package/types/masmarino-gabarit-popover.d.ts +33 -0
- package/types/masmarino-gabarit-radio-group.d.ts +66 -0
- package/types/masmarino-gabarit-save-status.d.ts +39 -0
- package/types/masmarino-gabarit-search-bar.d.ts +183 -0
- package/types/masmarino-gabarit-secret-reveal.d.ts +95 -0
- package/types/masmarino-gabarit-segmented-control.d.ts +76 -0
- package/types/masmarino-gabarit-select.d.ts +146 -0
- package/types/masmarino-gabarit-skeleton-list.d.ts +54 -0
- package/types/masmarino-gabarit-skeleton.d.ts +26 -0
- package/types/masmarino-gabarit-slider.d.ts +73 -0
- package/types/masmarino-gabarit-sparkline.d.ts +70 -0
- package/types/masmarino-gabarit-spinner.d.ts +20 -0
- package/types/masmarino-gabarit-stat-grid.d.ts +39 -0
- package/types/masmarino-gabarit-stat-tile.d.ts +79 -0
- package/types/masmarino-gabarit-stepper.d.ts +67 -0
- package/types/masmarino-gabarit-switch.d.ts +42 -0
- package/types/masmarino-gabarit-table.d.ts +60 -0
- package/types/masmarino-gabarit-tabs.d.ts +40 -0
- package/types/masmarino-gabarit-tag-input.d.ts +78 -0
- package/types/masmarino-gabarit-tag.d.ts +35 -0
- package/types/masmarino-gabarit-textarea.d.ts +109 -0
- package/types/masmarino-gabarit-timeline-chart.d.ts +145 -0
- package/types/masmarino-gabarit-toaster.d.ts +62 -0
- package/types/masmarino-gabarit-tooltip.d.ts +39 -0
- package/types/masmarino-gabarit-tree.d.ts +71 -0
- package/types/masmarino-gabarit-user-chip.d.ts +26 -0
- package/types/masmarino-gabarit.d.ts +83 -6599
- package/user-chip/README.md +42 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# ListCard
|
|
2
|
+
|
|
3
|
+
The card that holds a page's list: a header (state tabs, a count, filters)
|
|
4
|
+
above a box that shows one of four states of the list. It is the surface
|
|
5
|
+
around [`gbt-list-row`](../list-row/README.md)s and the place where
|
|
6
|
+
"loading", "failed" and "empty" look the same on every list page.
|
|
7
|
+
|
|
8
|
+
**Selector**: `gbt-list-card`
|
|
9
|
+
|
|
10
|
+
## States
|
|
11
|
+
|
|
12
|
+
| `state` | What shows |
|
|
13
|
+
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
14
|
+
| `ready` | The `[list-card-header]` above the box, then the projected content in the box (usually `ul > li > gbt-list-row`). The default. |
|
|
15
|
+
| `loading` | A skeleton of the same shape (a header placeholder above the box, `skeletonRows` rows in it), the rows are `aria-busy`, and a polite status (`loadingLabel`) announces it. |
|
|
16
|
+
| `failed` | An error block (`failedHeading`, `failedMessage`) in the box with a retry button, and the `[list-card-failed]` slot for more actions. No header. |
|
|
17
|
+
| `empty` | Nothing to list at all: `emptyIllustration` or `emptyIcon`, `emptyHeading`, `emptyMessage`, and `[list-card-empty]` for the call to action, in the box. No header. |
|
|
18
|
+
|
|
19
|
+
In every state but `ready` the projected content is not rendered.
|
|
20
|
+
|
|
21
|
+
## Inputs
|
|
22
|
+
|
|
23
|
+
| Input | Type | Default | Role |
|
|
24
|
+
| ------------------- | --------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `state` | `'loading' \| 'failed' \| 'empty' \| 'ready'` | `'ready'` | Which of the four states shows. |
|
|
26
|
+
| `ariaLabel` | `string \| null` | `null` | Names the card's `<section>` (for example "Issues"). |
|
|
27
|
+
| `loadingLabel` | `string` | `'Loading…'` | Visually hidden, read out politely while loading. |
|
|
28
|
+
| `skeletonRows` | `number` | `4` | Placeholder rows while loading. |
|
|
29
|
+
| `skeletonHeader` | `boolean` | `true` | Shows a placeholder of the header while loading (turn off for a card without a header). |
|
|
30
|
+
| `failedHeading` | `string` | `'The list could not be loaded'` | Heading of the failed block. |
|
|
31
|
+
| `failedMessage` | `string` | `''` | Optional detail under it. |
|
|
32
|
+
| `retryLabel` | `string \| null` | `'Retry'` | Label of the retry button. `null` hides it (bring your own action in `[list-card-failed]`). |
|
|
33
|
+
| `emptyHeading` | `string` | `'Nothing here yet'` | Heading of the empty block. |
|
|
34
|
+
| `emptyMessage` | `string` | `''` | Optional message under it. |
|
|
35
|
+
| `emptyIllustration` | `EmptyStateIllustration \| null` | `null` | The curated artwork of [`gbt-empty-state`](../empty-state/README.md) (full size). |
|
|
36
|
+
| `emptyIcon` | `string \| null` | `null` | A registered icon name on a soft disc (compact size), when there is no illustration. |
|
|
37
|
+
|
|
38
|
+
## Outputs
|
|
39
|
+
|
|
40
|
+
| Output | Payload | Role |
|
|
41
|
+
| ------- | ------- | ------------------------------------------------- |
|
|
42
|
+
| `retry` | `void` | The retry button of the failed block was pressed. |
|
|
43
|
+
|
|
44
|
+
## Slots
|
|
45
|
+
|
|
46
|
+
| Selector | State | Role |
|
|
47
|
+
| -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| `[list-card-header]` | `ready` | The header, above the box: state tabs (`gbt-segmented-control tinted`), a heading, a count, filters. No header when absent. |
|
|
49
|
+
| (default) | `ready` | The list. A direct child `ul`/`ol` loses its bullets and spacing; a direct child carrying the `list-card-message` attribute is the centred quiet message of a list that is empty under the current filter. |
|
|
50
|
+
| `[list-card-failed]` | `failed` | Extra actions after the retry button. |
|
|
51
|
+
| `[list-card-empty]` | `empty` | The call to action (a `gbt-button`). |
|
|
52
|
+
|
|
53
|
+
## Structure: a header over a box, in one section
|
|
54
|
+
|
|
55
|
+
The card renders one `<section>` (named by `ariaLabel`, carrying `data-state`) with two parts:
|
|
56
|
+
|
|
57
|
+
1. **The header** (`.gbt-list-card__header`, `ready` and `loading` only): a plain row **above** the box —
|
|
58
|
+
the projected `[list-card-header]`, or its skeleton while loading. It has no background, no border and
|
|
59
|
+
no padding of its own, like [`gbt-card`](../card/README.md)'s header; `0.5rem` separates it from the
|
|
60
|
+
box. It reserves the height of a `md` control (`--gbt-control-height-md`, 38px), so the loading
|
|
61
|
+
placeholder and the ready header are the same height and the box does not move when the list arrives.
|
|
62
|
+
A header that holds only a heading (no tabs, input or button) is shorter than that on its own: expect a
|
|
63
|
+
little whitespace above the box in that case, since there is no background to make the row visible.
|
|
64
|
+
2. **The box** (`.gbt-list-card__box`): the 1px edge (`--gbt-card-border`), the radius and the background.
|
|
65
|
+
It holds the list, the skeleton rows, the failed block or the empty block.
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
<gbt-list-card>
|
|
69
|
+
section.gbt-list-card the landmark and the size container: aria-label, data-state
|
|
70
|
+
span.sr-only[role=status] the loading announcement
|
|
71
|
+
div.gbt-list-card__header [list-card-header], or its skeleton (ready/loading only)
|
|
72
|
+
div.gbt-list-card__box the edge, the radius, the clipping
|
|
73
|
+
div.gbt-list-card__body ready: your projected list
|
|
74
|
+
(or, instead of the body: the loading skeleton rows, the failed block, or the empty block —
|
|
75
|
+
exactly one of these four renders in the box at a time, depending on `state`)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Unlike `gbt-card`, the header stays **inside** the section: the header's tabs and filters belong to the
|
|
79
|
+
named region ("Issues") exactly as the rows do. `.gbt-list-card` is the section, carrying `data-state`;
|
|
80
|
+
the edge is drawn by `.gbt-list-card__box`.
|
|
81
|
+
|
|
82
|
+
## Behaviour
|
|
83
|
+
|
|
84
|
+
- The card is a **size container** named `gbt-list-card`, on the section (header and box): the header's
|
|
85
|
+
items sit closer under 560 px of card width, and a page can add its own
|
|
86
|
+
`@container gbt-list-card (max-width: …)` rules (hide a column, shorten a label). Never make it a
|
|
87
|
+
shrink-to-fit flex or grid item; give it a width or let it fill a block.
|
|
88
|
+
- The box clips its corners (`overflow: hidden`), so the rows' hover follows the rounded edge. The header is
|
|
89
|
+
outside the box and is never clipped.
|
|
90
|
+
- The failed block is a live alert (`role="alert"`). The loading status is a **persistent** polite region
|
|
91
|
+
(`role="status"`, a direct child of the section, empty unless `loading`) and is never inside the
|
|
92
|
+
`aria-busy` skeleton, so the announcement is not held back. A card that is already `loading` on its very
|
|
93
|
+
first render inserts the region with its text, which some screen readers do not announce: start in
|
|
94
|
+
`ready` and switch, or accept it.
|
|
95
|
+
- **Focus after Retry**: pressing Retry usually flips the state to `loading`, which removes the focused
|
|
96
|
+
button and drops focus to the page. Move focus to a stable target yourself (the card's heading or the
|
|
97
|
+
list once it is ready), or keep the retry outside the card.
|
|
98
|
+
- The empty and failed buttons reach 44 px on touch screens.
|
|
99
|
+
|
|
100
|
+
## Example
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<gbt-list-card
|
|
104
|
+
ariaLabel="Issues"
|
|
105
|
+
[state]="state()"
|
|
106
|
+
loadingLabel="Loading issues…"
|
|
107
|
+
failedHeading="The issues could not be loaded"
|
|
108
|
+
emptyHeading="No issues yet"
|
|
109
|
+
emptyIcon="circle-dot"
|
|
110
|
+
(retry)="reload()"
|
|
111
|
+
>
|
|
112
|
+
<gbt-segmented-control
|
|
113
|
+
list-card-header
|
|
114
|
+
tinted
|
|
115
|
+
ariaLabel="Issue state"
|
|
116
|
+
[options]="tabs"
|
|
117
|
+
[(value)]="tab"
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
@if (rows().length === 0) {
|
|
121
|
+
<p list-card-message>No {{ tab() }} issues.</p>
|
|
122
|
+
} @else {
|
|
123
|
+
<ul>
|
|
124
|
+
@for (issue of rows(); track issue.id) {
|
|
125
|
+
<li>
|
|
126
|
+
<gbt-list-row>
|
|
127
|
+
<a [href]="issue.url" [title]="issue.title">{{ issue.title }}</a>
|
|
128
|
+
</gbt-list-row>
|
|
129
|
+
</li>
|
|
130
|
+
}
|
|
131
|
+
</ul>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
<gbt-button list-card-empty variant="secondary" iconName="plus" text="New issue" />
|
|
135
|
+
</gbt-list-card>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The `failed` and `empty` slots are only rendered in their own state, so the button above shows only when
|
|
139
|
+
`state` is `empty`.
|
|
140
|
+
|
|
141
|
+
## Accessibility
|
|
142
|
+
|
|
143
|
+
See [AUDIT.md](AUDIT.md). Note that Safari and VoiceOver drop the list semantics of a `ul` whose bullets are
|
|
144
|
+
removed with `list-style: none`: add `role="list"` on the `ul` if that matters to you.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# ListRow
|
|
2
|
+
|
|
3
|
+
One item of a list (issues, merge requests, pipelines, accounts…): a
|
|
4
|
+
leading status icon, the title area (the title link, then its badges and
|
|
5
|
+
tags), a muted meta line under it, and trailing actions or counters on the
|
|
6
|
+
right. Every column lines up on the title's first line.
|
|
7
|
+
|
|
8
|
+
The page provides the list semantics and the surface around it
|
|
9
|
+
(`ul > li > gbt-list-row`, inside a [`gbt-list-card`](../list-card/README.md)
|
|
10
|
+
or any bordered box). The row adds hairlines between consecutive rows, a
|
|
11
|
+
hover / focus-within background and title truncation.
|
|
12
|
+
|
|
13
|
+
**Selector**: `gbt-list-row`
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
|
|
17
|
+
| Input | Type | Default | Role |
|
|
18
|
+
| ------ | ---------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------- |
|
|
19
|
+
| `tone` | `'neutral' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'neutral'` | Tint of the `[row-leading]` icon: muted, or the status colour (`gbt-badge` variants). |
|
|
20
|
+
|
|
21
|
+
## Slots
|
|
22
|
+
|
|
23
|
+
| Selector | Role |
|
|
24
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
25
|
+
| `[row-leading]` | A 20 px column centred on the title line: a status icon (`gbt-icon` renders as a 16 px glyph). Tinted by `tone`. Collapses when empty. |
|
|
26
|
+
| (default) | The title area: the title **link as a direct child**, then badges, tags or a `gbt-user-chip`. |
|
|
27
|
+
| `[row-meta]` | A muted line under the title (one or several `span`s, wrapping together). `code` inside is a monospace chip. Collapses when empty. |
|
|
28
|
+
| `[row-trailing]` | Counters, an avatar, a menu, right aligned. Wraps under the main column, still right aligned, when there is no room. Collapses when empty. |
|
|
29
|
+
|
|
30
|
+
## Rules of use
|
|
31
|
+
|
|
32
|
+
- **The title link is a DIRECT child of the row** (`<a>` straight inside `gbt-list-row`, not wrapped in a
|
|
33
|
+
`span` or a `@if` block element): only a direct child gets the truncation (one line, ellipsis) and the
|
|
34
|
+
focus ring. Give it the full title in `title`.
|
|
35
|
+
- Children of the title area do not shrink (`flex: none`), except the link and a `gbt-user-chip`, which
|
|
36
|
+
truncate. Tags that do not fit beside the title wrap below it.
|
|
37
|
+
- Keep **the same trailing items in every row** of a list (an empty, fixed-width `span` for a missing
|
|
38
|
+
avatar) so the columns line up.
|
|
39
|
+
- The status is never told by colour alone: put an icon that differs per status and a text alternative
|
|
40
|
+
(`<span class="sr-only">Open</span>`) next to it.
|
|
41
|
+
- Do not nest interactive controls inside the title link.
|
|
42
|
+
|
|
43
|
+
## Separators
|
|
44
|
+
|
|
45
|
+
`li + li > gbt-list-row` and `gbt-list-row + gbt-list-row` get a `--gbt-hairline` border on top. Emulated
|
|
46
|
+
view encapsulation cannot express "a row after a row" from inside the component, so this is one rule
|
|
47
|
+
that is not scoped to the component, keyed on the element name.
|
|
48
|
+
|
|
49
|
+
## Example
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<ul>
|
|
53
|
+
@for (issue of issues; track issue.id) {
|
|
54
|
+
<li>
|
|
55
|
+
<gbt-list-row [tone]="issue.tone">
|
|
56
|
+
<span row-leading>
|
|
57
|
+
<gbt-icon [name]="issue.icon" />
|
|
58
|
+
<span class="sr-only">{{ issue.status }}</span>
|
|
59
|
+
</span>
|
|
60
|
+
<a [href]="issue.url" [title]="issue.title">{{ issue.title }}</a>
|
|
61
|
+
<gbt-tag [color]="'#dc2626'">bug</gbt-tag>
|
|
62
|
+
<span row-meta>#{{ issue.id }} opened {{ issue.age }} by {{ issue.author }}</span>
|
|
63
|
+
<span row-trailing><gbt-icon name="message-circle" />{{ issue.comments }}</span>
|
|
64
|
+
</gbt-list-row>
|
|
65
|
+
</li>
|
|
66
|
+
}
|
|
67
|
+
</ul>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Accessibility
|
|
71
|
+
|
|
72
|
+
See [AUDIT.md](AUDIT.md).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ListToolbar
|
|
2
|
+
|
|
3
|
+
Search + sort controls for a local list or grid — a text filter, a
|
|
4
|
+
sort-field selector, and a direction toggle. Purely a set of controls:
|
|
5
|
+
it never filters or sorts anything itself, and takes no `data` input.
|
|
6
|
+
|
|
7
|
+
**Selector**: `gbt-list-toolbar`
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
|
|
11
|
+
| Input | Type | Default | Role |
|
|
12
|
+
| -------------------- | --------------------------- | --------------------------- | ---------------------------------------- |
|
|
13
|
+
| `searchValue` | `string` | `''` | Current search text (controlled). |
|
|
14
|
+
| `searchLabel` | `string` | — | Required. Visible label for the search field. |
|
|
15
|
+
| `searchPlaceholder` | `string` | `''` | Placeholder for the search field. |
|
|
16
|
+
| `sortOptions` | `ListToolbarSortOption<T>[]`| — | Required. `{ value, label }[]`. |
|
|
17
|
+
| `sortValue` | `T` | — | Required. Currently selected sort field.|
|
|
18
|
+
| `sortDirection` | `'asc' \| 'desc'` | `'asc'` | Current sort direction. |
|
|
19
|
+
| `sortLabel` | `string` | `'Sort by'` | Visible label for the sort selector. |
|
|
20
|
+
| `directionLabel` | `string` | `'Reverse sort direction'` | Accessible name for the direction button.|
|
|
21
|
+
|
|
22
|
+
## Outputs
|
|
23
|
+
|
|
24
|
+
| Output | Payload | When |
|
|
25
|
+
| ------------------------ | ------------------- | ------------------------------------------ |
|
|
26
|
+
| `searchValueChange` | `string` | On every keystroke in the search field. |
|
|
27
|
+
| `sortValueChange` | `T` | When a different sort field is selected. |
|
|
28
|
+
| `sortDirectionChange` | `'asc' \| 'desc'` | When the direction button is clicked (flips the current direction). |
|
|
29
|
+
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<gbt-list-toolbar
|
|
34
|
+
searchLabel="Rechercher un dépôt"
|
|
35
|
+
[searchValue]="search()"
|
|
36
|
+
[sortOptions]="[{ value: 'name', label: 'Nom' }, { value: 'date', label: 'Date' }]"
|
|
37
|
+
[sortValue]="sort()"
|
|
38
|
+
[sortDirection]="direction()"
|
|
39
|
+
(searchValueChange)="search.set($event)"
|
|
40
|
+
(sortValueChange)="sort.set($event)"
|
|
41
|
+
(sortDirectionChange)="direction.set($event)"
|
|
42
|
+
/>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The consumer owns the actual filtering/sorting, typically as a
|
|
46
|
+
`computed()` over its own data driven by these three signals — the
|
|
47
|
+
same pattern used throughout consuming apps for local list filtering.
|
|
48
|
+
|
|
49
|
+
To avoid re-declaring the three signals and the filter/sort on every page,
|
|
50
|
+
use `createListToolbarState()` (see "List toolbar state" in the root README):
|
|
51
|
+
it exposes `search`, `sortValue`, `direction` and `sortOptions` ready to bind to
|
|
52
|
+
this component, plus `apply()` / `filtered()`.
|
package/menu/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Menu
|
|
2
|
+
|
|
3
|
+
Generic dropdown menu — trigger and action list, the WAI-ARIA "menu
|
|
4
|
+
button" pattern.
|
|
5
|
+
|
|
6
|
+
**Selector**: `gbt-menu`
|
|
7
|
+
|
|
8
|
+
Click or Enter/Space opens it while focusing the first item; the
|
|
9
|
+
up/down arrows on the closed trigger open it while focusing the first
|
|
10
|
+
or last item respectively; the up/down arrows inside the list cycle
|
|
11
|
+
through the items with wraparound (Home/End jump to the first/last);
|
|
12
|
+
Escape closes it and returns focus to the trigger; activating an item
|
|
13
|
+
closes the menu and **returns focus to the trigger** (the item that had
|
|
14
|
+
it is gone); a click outside the menu or a focus-out (tabbing) closes
|
|
15
|
+
it without stealing focus. Focus is never trapped: Tab leaves the menu.
|
|
16
|
+
|
|
17
|
+
## Inputs
|
|
18
|
+
|
|
19
|
+
| Input | Type | Default | Role |
|
|
20
|
+
| ------------------ | ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| `label` | `string` | required | Trigger text, and accessible name of the list. |
|
|
22
|
+
| `align` | `'start' \| 'end'` | `'start'` | Horizontal alignment of the list relative to the trigger. |
|
|
23
|
+
| `triggerIcon` | `string \| null` | `null` | When set, the trigger renders only this icon (no visible label, no chevron) and `label` becomes its `aria-label` instead — for an icon-only trigger such as a "⋮" kebab button. |
|
|
24
|
+
| `triggerAriaLabel` | `string \| null` | `null` | Accessible name of the trigger button when a custom trigger (below) has no text of its own, e.g. an avatar alone. Left out, the button is named by its content. |
|
|
25
|
+
| `chevron` | `boolean` | `true` | The chevron after the label or the custom trigger. Never shown with `triggerIcon`. |
|
|
26
|
+
|
|
27
|
+
## Outputs
|
|
28
|
+
|
|
29
|
+
| Output | Type | Role |
|
|
30
|
+
| -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `opened` | `void` | Emitted once per closed→open transition — never on close. Useful for lazily loading the menu's contents the first time it's opened. |
|
|
32
|
+
|
|
33
|
+
If you lazily populate the menu's contents on `opened`, project a placeholder item (e.g. `<a role="menuitem" class="gbt-menu__item">Chargement…</a>`) so the menu is never empty while the real content loads — otherwise a keyboard user's first ArrowDown-triggered open has nothing to focus, and other users briefly see an empty dropdown.
|
|
34
|
+
|
|
35
|
+
## Projected content
|
|
36
|
+
|
|
37
|
+
The list of menu items — each carries `role="menuitem"`. Write them with
|
|
38
|
+
[`gbtMenuItem`](menu-item/README.md) (`<button gbtMenuItem>` / `<a gbtMenuItem>`, with
|
|
39
|
+
`icon`, `variant="danger"` and `disabled`), or by hand with the `.gbt-menu__item` class (in
|
|
40
|
+
`_utilities.scss`, for styling; projected content escapes the component's encapsulation) — both
|
|
41
|
+
work, side by side. A real link or button, never a decorative element: it's what receives focus.
|
|
42
|
+
|
|
43
|
+
One more slot, the custom trigger: an element marked `gbtMenuTrigger`.
|
|
44
|
+
|
|
45
|
+
## Custom trigger (avatar + name…)
|
|
46
|
+
|
|
47
|
+
By default the trigger shows `label` and a chevron. To show anything else — a user's avatar and
|
|
48
|
+
name, say — project an element marked `gbtMenuTrigger` (import the `MenuTrigger` directive). Its
|
|
49
|
+
content goes **inside** the trigger button, replacing the label; the menu's behaviour, the chevron
|
|
50
|
+
and the list's name (`label`) are unchanged.
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<gbt-menu label="Account" align="end">
|
|
54
|
+
<span gbtMenuTrigger class="who">
|
|
55
|
+
<span aria-hidden="true"><gbt-avatar name="Ada Lovelace" size="sm" /></span>
|
|
56
|
+
<span>ada.lovelace</span>
|
|
57
|
+
</span>
|
|
58
|
+
<a gbtMenuItem icon="user" href="/account">My account</a>
|
|
59
|
+
<button gbtMenuItem icon="log-out" (click)="logout()">Sign out</button>
|
|
60
|
+
</gbt-menu>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- The slot lives in a `<button>`: phrasing content only, nothing interactive.
|
|
64
|
+
- The button is named by that content. Put a visible name next to a decorative avatar
|
|
65
|
+
(`aria-hidden` on the avatar avoids reading the name twice). For an avatar alone, set
|
|
66
|
+
`triggerAriaLabel`.
|
|
67
|
+
- A long name can ellipsise: give the slot `min-width: 0` and the name `overflow: hidden;
|
|
68
|
+
text-overflow: ellipsis; white-space: nowrap`; the trigger itself may shrink (`max-width: 100%`).
|
|
69
|
+
- The marked element is always projected into the trigger; without the `MenuTrigger` import it
|
|
70
|
+
only misses the layout refinements (shrinking, padding), so import it.
|
|
71
|
+
|
|
72
|
+
## Example
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<gbt-menu label="Mon compte" align="end">
|
|
76
|
+
<a role="menuitem" class="gbt-menu__item" href="/compte">Mon compte</a>
|
|
77
|
+
<button role="menuitem" class="gbt-menu__item" type="button" (click)="logout()">
|
|
78
|
+
Déconnexion
|
|
79
|
+
</button>
|
|
80
|
+
</gbt-menu>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Icon-only trigger (e.g. a kebab menu)
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<gbt-menu label="Actions" triggerIcon="ellipsis-vertical" align="end">
|
|
87
|
+
<a role="menuitem" class="gbt-menu__item" href="/x">Copier le chemin</a>
|
|
88
|
+
<button role="menuitem" class="gbt-menu__item" type="button" (click)="delete()">Supprimer</button>
|
|
89
|
+
</gbt-menu>
|
|
90
|
+
```
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# MfaEnrollment
|
|
2
|
+
|
|
3
|
+
The mandatory first enrolment of a second factor, shown inside the sign-in panel when the password
|
|
4
|
+
step (or a free registration) answered "setup required". The user picks a factor, sets it up, then
|
|
5
|
+
acknowledges the backup codes; only then does the component hand the new session to its host.
|
|
6
|
+
|
|
7
|
+
It is embedded by [`gbt-auth-login`](../auth-login/login.ts) and [`gbt-auth-register`](../auth-register/register.ts)
|
|
8
|
+
inside their [`gbt-auth-panel`](../auth/auth-panel/auth-panel.ts). Use it directly only to build your own
|
|
9
|
+
sign-in page. The panel (logo, page heading) belongs to the host page.
|
|
10
|
+
|
|
11
|
+
**Selector**: `gbt-mfa-enrollment`
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
The step counter reads "Step n of 3".
|
|
16
|
+
|
|
17
|
+
| Step | # | What shows |
|
|
18
|
+
| --------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
19
|
+
| `choice` | 1 | Where a passkey can be made (`passkeysAvailable` and a browser with WebAuthn): two option cards, "Passkey" (Recommended, the single primary button) and "Authenticator app". Otherwise: the introduction of the authenticator path, three steps and "Get started". |
|
|
20
|
+
| `scan` | 2 | The QR code of the `otpauth://` URL and the secret as text ([`gbt-totp-qr`](totp-qr/totp-qr.ts)), the one-time-code field and "Activate". |
|
|
21
|
+
| `passkey` | 2 | An optional key name (its default, "Passkey", is the placeholder), then "Create the passkey" opens the browser's prompt. |
|
|
22
|
+
| `codes` | 3 | The backup codes ([`gbt-backup-codes`](backup-codes/backup-codes.ts)), shown once, and "Continue", disabled until "I have saved my backup codes" is ticked. No way back from here. |
|
|
23
|
+
|
|
24
|
+
A note ("Two-factor authentication is required…") sits on the first step, as a plain information
|
|
25
|
+
note, not a live region.
|
|
26
|
+
|
|
27
|
+
## Inputs
|
|
28
|
+
|
|
29
|
+
| Input | Type | Default | Role |
|
|
30
|
+
| ------------------- | ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `mfaToken` | `string` | (required) | The short-lived token of the password step (`LoginResponse.mfaToken`): the credential of every enrolment call. |
|
|
32
|
+
| `passkeysAvailable` | `boolean` | `false` | The server can run passkey ceremonies (`AuthConfig.passkeysAvailable`). A passkey is offered only when the browser can too (read once, on creation). |
|
|
33
|
+
| `labels` | `Partial<MfaEnrollmentLabels>` | `{}` | Strings to change, over `provideAuthLabels({ mfaEnrollment })` and the English defaults (`DEFAULT_MFA_ENROLLMENT_LABELS`). |
|
|
34
|
+
|
|
35
|
+
## Outputs
|
|
36
|
+
|
|
37
|
+
| Output | Payload | Role |
|
|
38
|
+
| ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `completed` | `string` | The codes are acknowledged and "Continue" was pressed: the SESSION token. Hand it to `AuthPort.setToken` (the login and register pages do) and move on. The component never stores it. |
|
|
40
|
+
| `cancelled` | `void` | "Back" on the first step, or on the second when there is no choice to go back to: return to the credentials. |
|
|
41
|
+
| `expired` | `void` | The `mfaToken` is no longer usable (a 401 `invalid or expired token`, or a 400 `MFA is already set up`): return to the credentials. The component also shows why, in its alert, in case the host keeps it on screen. |
|
|
42
|
+
|
|
43
|
+
## Slots
|
|
44
|
+
|
|
45
|
+
None. The nested QR and backup codes take their strings from `provideAuthLabels({ totpQr, backupCodes })`
|
|
46
|
+
(or `DEFAULT_TOTP_QR_LABELS` / `DEFAULT_BACKUP_CODES_LABELS`); the component's `labels` input only covers
|
|
47
|
+
its own strings.
|
|
48
|
+
|
|
49
|
+
## Dependencies
|
|
50
|
+
|
|
51
|
+
| Token | Required | Used for |
|
|
52
|
+
| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
53
|
+
| `AUTH_PORT` | yes | `enrollTotp(mfaToken)`, `confirmTotp(mfaToken, code)`, `startPasskeySetup(mfaToken)`, `finishPasskeySetup(mfaToken, challengeId, credential, name)`. The two confirmations must NOT store the session. |
|
|
54
|
+
| `TOTP_QR_RENDERER` | no | Draws the QR (Gabarit ships no encoder). Without one, `gbt-totp-qr` shows its fallback and the secret to type by hand. |
|
|
55
|
+
| `AUTH_LABELS` | no | Application-wide strings (`provideAuthLabels`). |
|
|
56
|
+
|
|
57
|
+
## Behaviour
|
|
58
|
+
|
|
59
|
+
- **Nothing is asked before a choice.** The first call is made by "Get started" / "Use an app"
|
|
60
|
+
(`enrollTotp`) or "Create the passkey" (`startPasskeySetup`); choosing "Use a passkey" only opens the
|
|
61
|
+
name step.
|
|
62
|
+
- **The code** is sent stripped of spaces. An empty field is refused without a call ("Enter the 6-digit
|
|
63
|
+
code from your app").
|
|
64
|
+
- **The passkey name** is trimmed; empty or blank, the key is named `defaultPasskeyName`. More than 40
|
|
65
|
+
characters (code points) or control characters are refused before any call, with the focus on the field.
|
|
66
|
+
- **Busy states.** The primary button of the step shows its spinner while a call is in flight and a second
|
|
67
|
+
submit is ignored; the busy state is rolled back on any failure. On the passkey step a polite note
|
|
68
|
+
("Confirm on your device to create the key.") shows while the browser's prompt is open, and "Back" stays
|
|
69
|
+
enabled (the prompt can be lost behind a window).
|
|
70
|
+
- **Stale ceremonies are dropped.** Leaving the passkey step while the server or the browser has not answered
|
|
71
|
+
yet opens no prompt and sends no attestation.
|
|
72
|
+
- **Recreate this component per `mfaToken`** (e.g. with an `@if` / `*ngIf` keyed on the token, as
|
|
73
|
+
`gbt-auth-login` and `gbt-auth-register` already do): **it does not reset its internal state when the input
|
|
74
|
+
changes.** Its step, the held-back session and the acknowledgement would survive a new token, and a later
|
|
75
|
+
"Continue" could emit the previous flow's session.
|
|
76
|
+
- **Known race: "Back" while a passkey `finish` call is in flight.** Pressing "Back" once the browser has
|
|
77
|
+
answered and `finishPasskeySetup` is still on its way cancels the flow (`cancelled` fires) and its answer is
|
|
78
|
+
dropped: the passkey and its backup codes may then exist on the server without the user ever seeing the
|
|
79
|
+
codes: at the next sign-in the account already has that passkey, and regenerating the codes from the
|
|
80
|
+
account settings replaces the unseen ones.
|
|
81
|
+
- **No backup codes, no codes step.** A server that issues none with the chosen factor (an empty
|
|
82
|
+
`backupCodes`) ends the enrolment there: `completed` carries the session token at once.
|
|
83
|
+
- **The session is held back** until the codes are acknowledged; the secret, the `otpauth://` URL and the code
|
|
84
|
+
are dropped as soon as the factor is confirmed.
|
|
85
|
+
- **Focus.** Each step opens on its heading (`h2`, `tabindex="-1"`), so the change is announced and the QR stays
|
|
86
|
+
in view (no phone keyboard over it). After a failure the focus goes back where the user acts: the field, or
|
|
87
|
+
the button that was disabled while loading.
|
|
88
|
+
|
|
89
|
+
### Failures
|
|
90
|
+
|
|
91
|
+
The port's errors are read by status and by the `error` string of their body (see
|
|
92
|
+
[`AUTH_PORT_ERROR_BODIES`](../auth/shared/port-error.ts)). The backend's own wording is never shown.
|
|
93
|
+
|
|
94
|
+
| Answer | Where | Message (label) | `expired` |
|
|
95
|
+
| ---------------------------------------- | -------------------- | ------------------------------------------------------------------------ | --------- |
|
|
96
|
+
| 401 `invalid or expired token` | any call | `loginExpired` | yes |
|
|
97
|
+
| 400 `MFA is already set up` | any call | `alreadySetUp` | yes |
|
|
98
|
+
| 429 | any call | `tooManyAttempts` | no |
|
|
99
|
+
| 400 `invalid code` (or another 401) | `confirmTotp` | `wrongCode`, the field emptied | no |
|
|
100
|
+
| 409 | passkey calls | `alreadyRegistered` | no |
|
|
101
|
+
| 503 | passkey calls | `passkeysUnavailable` | no |
|
|
102
|
+
| anything else (network failure included) | the call | `startFailed`, `activationFailed` or `passkeyFailed` | no |
|
|
103
|
+
| `NotAllowedError` / `AbortError` | the browser's prompt | `cancelled`, as a quiet information note (`role="status"`), not an error | no |
|
|
104
|
+
| `InvalidStateError` | the browser's prompt | `alreadyRegistered` | no |
|
|
105
|
+
| `NotSupportedError` | the browser's prompt | `browserUnsupported` | no |
|
|
106
|
+
| any other browser error | the browser's prompt | `passkeyFailed` | no |
|
|
107
|
+
|
|
108
|
+
## Example
|
|
109
|
+
|
|
110
|
+
The application provides its backend once:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// app.config.ts
|
|
114
|
+
import { type ApplicationConfig } from '@angular/core'
|
|
115
|
+
import { provideHttpClient } from '@angular/common/http'
|
|
116
|
+
import { AUTH_PORT, TOTP_QR_RENDERER, provideAuthLabels } from '@masmarino/gabarit'
|
|
117
|
+
import { HttpAuthPort } from './auth/http-auth-port' // your AuthPort over HttpClient
|
|
118
|
+
|
|
119
|
+
export const appConfig: ApplicationConfig = {
|
|
120
|
+
providers: [
|
|
121
|
+
provideHttpClient(),
|
|
122
|
+
{ provide: AUTH_PORT, useClass: HttpAuthPort },
|
|
123
|
+
{
|
|
124
|
+
provide: TOTP_QR_RENDERER,
|
|
125
|
+
// Loaded only when the QR is drawn. It receives the raw TOTP secret: a LOCAL encoder only, never a
|
|
126
|
+
// hosted QR service, never logged (see ../totp-qr/README.md).
|
|
127
|
+
useValue: (text: string, options: object) =>
|
|
128
|
+
import('qrcode').then((m) => m.toDataURL(text, options)),
|
|
129
|
+
},
|
|
130
|
+
// Optional: localise the kit once (each part partial).
|
|
131
|
+
provideAuthLabels({
|
|
132
|
+
mfaEnrollment: { choiceHeading: 'Protégez votre compte', begin: 'Commencer' },
|
|
133
|
+
totpQr: { secretLabel: 'Ou saisissez cette clé dans votre application' },
|
|
134
|
+
backupCodes: { acknowledge: "J'ai enregistré mes codes de secours" },
|
|
135
|
+
}),
|
|
136
|
+
],
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
With `gbt-auth-login` (or `gbt-auth-register`) there is nothing more to wire: the page shows the enrolment,
|
|
141
|
+
hands the session to `AuthPort.setToken` and emits `loggedIn` (`registered`). Its footer link is projected
|
|
142
|
+
by the application, for example with the router:
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<gbt-auth-login (loggedIn)="router.navigateByUrl('/')">
|
|
146
|
+
<img auth-logo src="/logo.svg" alt="Acme" />
|
|
147
|
+
<a gbtButton variant="link" gbtAuthFooterLink routerLink="/register">Create an account</a>
|
|
148
|
+
</gbt-auth-login>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A page of your own embeds the component in the panel and handles its outputs:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
@Component({
|
|
155
|
+
selector: 'app-setup-mfa',
|
|
156
|
+
imports: [AuthPanel, MfaEnrollment],
|
|
157
|
+
template: `
|
|
158
|
+
<gbt-auth-panel wide heading="Two-factor authentication">
|
|
159
|
+
<img auth-logo src="/logo.svg" alt="Acme" />
|
|
160
|
+
<gbt-mfa-enrollment
|
|
161
|
+
[mfaToken]="mfaToken"
|
|
162
|
+
[passkeysAvailable]="passkeysAvailable"
|
|
163
|
+
(completed)="done($event)"
|
|
164
|
+
(cancelled)="router.navigateByUrl('/login')"
|
|
165
|
+
(expired)="router.navigateByUrl('/login')"
|
|
166
|
+
/>
|
|
167
|
+
</gbt-auth-panel>
|
|
168
|
+
`,
|
|
169
|
+
})
|
|
170
|
+
export class SetupMfaPage {
|
|
171
|
+
protected readonly router = inject(Router)
|
|
172
|
+
private readonly auth = inject(AUTH_PORT)
|
|
173
|
+
// From the password step (LoginResponse.mfaToken) and AuthConfig.passkeysAvailable.
|
|
174
|
+
mfaToken = '…'
|
|
175
|
+
passkeysAvailable = false
|
|
176
|
+
|
|
177
|
+
done(sessionToken: string): void {
|
|
178
|
+
this.auth.setToken(sessionToken)
|
|
179
|
+
this.router.navigateByUrl('/')
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The login and register pages do not forward a `labels` input to the enrolment: localise it there with
|
|
185
|
+
`provideAuthLabels({ mfaEnrollment })`.
|
|
186
|
+
|
|
187
|
+
## Accessibility
|
|
188
|
+
|
|
189
|
+
See [AUDIT.md](AUDIT.md).
|