@universal-material/web 3.8.0 → 3.9.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/.claude-plugin/plugin.json +12 -0
- package/README.md +12 -0
- package/app-bar/top-app-bar.d.ts +7 -6
- package/app-bar/top-app-bar.d.ts.map +1 -1
- package/app-bar/top-app-bar.js +71 -35
- package/app-bar/top-app-bar.js.map +1 -1
- package/app-bar/top-app-bar.styles.d.ts.map +1 -1
- package/app-bar/top-app-bar.styles.js +18 -1
- package/app-bar/top-app-bar.styles.js.map +1 -1
- package/badge/badge.d.ts +2 -2
- package/badge/badge.d.ts.map +1 -1
- package/badge/badge.js +6 -6
- package/badge/badge.js.map +1 -1
- package/badge/badge.styles.d.ts.map +1 -1
- package/badge/badge.styles.js +1 -0
- package/badge/badge.styles.js.map +1 -1
- package/bundle.min.js +4469 -1277
- package/button/button-base.d.ts +2 -2
- package/button/button-base.d.ts.map +1 -1
- package/button/button-base.js +5 -5
- package/button/button-base.js.map +1 -1
- package/button/button-set.d.ts +3 -3
- package/button/button-set.d.ts.map +1 -1
- package/button/button-set.js +7 -7
- package/button/button-set.js.map +1 -1
- package/button/button.d.ts +7 -7
- package/button/button.d.ts.map +1 -1
- package/button/button.js +14 -14
- package/button/button.js.map +1 -1
- package/button/fab-menu-color-context.d.ts +2 -2
- package/button/fab-menu-color-context.d.ts.map +1 -1
- package/button/fab-menu-color-context.js.map +1 -1
- package/button/fab-menu-item.d.ts +3 -3
- package/button/fab-menu-item.d.ts.map +1 -1
- package/button/fab-menu-item.js +11 -11
- package/button/fab-menu-item.js.map +1 -1
- package/button/fab-menu.d.ts +20 -5
- package/button/fab-menu.d.ts.map +1 -1
- package/button/fab-menu.js +48 -11
- package/button/fab-menu.js.map +1 -1
- package/button/fab.d.ts +22 -7
- package/button/fab.d.ts.map +1 -1
- package/button/fab.js +49 -11
- package/button/fab.js.map +1 -1
- package/button/icon-button.d.ts +7 -7
- package/button/icon-button.d.ts.map +1 -1
- package/button/icon-button.js +8 -8
- package/button/icon-button.js.map +1 -1
- package/button/toggle-button.d.ts +6 -6
- package/button/toggle-button.d.ts.map +1 -1
- package/button/toggle-button.js +10 -10
- package/button/toggle-button.js.map +1 -1
- package/button-field/button-field.d.ts +3 -3
- package/button-field/button-field.d.ts.map +1 -1
- package/button-field/button-field.js +9 -9
- package/button-field/button-field.js.map +1 -1
- package/calendar/calendar-base.d.ts +1 -1
- package/calendar/calendar-base.d.ts.map +1 -1
- package/calendar/calendar-base.js +10 -10
- package/calendar/calendar-base.js.map +1 -1
- package/calendar/calendar.d.ts +2 -2
- package/calendar/calendar.d.ts.map +1 -1
- package/calendar/calendar.js +8 -8
- package/calendar/calendar.js.map +1 -1
- package/calendar/range-calendar.d.ts +2 -2
- package/calendar/range-calendar.d.ts.map +1 -1
- package/calendar/range-calendar.js +9 -9
- package/calendar/range-calendar.js.map +1 -1
- package/card/card-content.d.ts +2 -2
- package/card/card-content.d.ts.map +1 -1
- package/card/card-content.js +5 -5
- package/card/card-content.js.map +1 -1
- package/card/card-media.d.ts +2 -2
- package/card/card-media.d.ts.map +1 -1
- package/card/card-media.js +5 -5
- package/card/card-media.js.map +1 -1
- package/card/card.d.ts +4 -4
- package/card/card.d.ts.map +1 -1
- package/card/card.js +5 -5
- package/card/card.js.map +1 -1
- package/checkbox/checkbox-list-item.d.ts +4 -4
- package/checkbox/checkbox-list-item.d.ts.map +1 -1
- package/checkbox/checkbox-list-item.js +5 -5
- package/checkbox/checkbox-list-item.js.map +1 -1
- package/checkbox/checkbox.d.ts +3 -3
- package/checkbox/checkbox.d.ts.map +1 -1
- package/checkbox/checkbox.js +7 -7
- package/checkbox/checkbox.js.map +1 -1
- package/chip/chip-set.d.ts +3 -3
- package/chip/chip-set.d.ts.map +1 -1
- package/chip/chip-set.js +6 -6
- package/chip/chip-set.js.map +1 -1
- package/chip/chip.d.ts +5 -5
- package/chip/chip.d.ts.map +1 -1
- package/chip/chip.js +20 -20
- package/chip/chip.js.map +1 -1
- package/chip-field/chip-field.d.ts +3 -3
- package/chip-field/chip-field.d.ts.map +1 -1
- package/chip-field/chip-field.js +9 -9
- package/chip-field/chip-field.js.map +1 -1
- package/collapse/collapse.d.ts +26 -0
- package/collapse/collapse.d.ts.map +1 -0
- package/collapse/collapse.js +62 -0
- package/collapse/collapse.js.map +1 -0
- package/collapse/collapse.styles.d.ts +2 -0
- package/collapse/collapse.styles.d.ts.map +1 -0
- package/collapse/collapse.styles.js +8 -0
- package/collapse/collapse.styles.js.map +1 -0
- package/config.js.map +1 -1
- package/css/universal-material.css +2 -1
- package/css/universal-material.min.css +2 -1
- package/custom-elements.json +16615 -12152
- package/datepicker/datepicker.d.ts +6 -4
- package/datepicker/datepicker.d.ts.map +1 -1
- package/datepicker/datepicker.js +33 -19
- package/datepicker/datepicker.js.map +1 -1
- package/datepicker/datepicker.styles.d.ts.map +1 -1
- package/datepicker/datepicker.styles.js +25 -0
- package/datepicker/datepicker.styles.js.map +1 -1
- package/datepicker/range-datepicker.d.ts +6 -4
- package/datepicker/range-datepicker.d.ts.map +1 -1
- package/datepicker/range-datepicker.js +33 -19
- package/datepicker/range-datepicker.js.map +1 -1
- package/dialog/confirm-dialog-builder.d.ts +3 -3
- package/dialog/confirm-dialog-builder.d.ts.map +1 -1
- package/dialog/confirm-dialog-builder.js.map +1 -1
- package/dialog/dialog-builder.d.ts +5 -4
- package/dialog/dialog-builder.d.ts.map +1 -1
- package/dialog/dialog-builder.js +10 -3
- package/dialog/dialog-builder.js.map +1 -1
- package/dialog/dialog-button-def.d.ts +3 -3
- package/dialog/dialog-button-def.d.ts.map +1 -1
- package/dialog/dialog-button-def.js.map +1 -1
- package/dialog/dialog.d.ts +2 -2
- package/dialog/dialog.d.ts.map +1 -1
- package/dialog/dialog.js +14 -14
- package/dialog/dialog.js.map +1 -1
- package/dialog/message-dialog-builder.d.ts +2 -2
- package/dialog/message-dialog-builder.d.ts.map +1 -1
- package/dialog/message-dialog-builder.js.map +1 -1
- package/elevation/elevation.d.ts +2 -2
- package/elevation/elevation.d.ts.map +1 -1
- package/elevation/elevation.js +4 -4
- package/elevation/elevation.js.map +1 -1
- package/expansion-panel/expansion-panel-container.d.ts +24 -0
- package/expansion-panel/expansion-panel-container.d.ts.map +1 -0
- package/expansion-panel/expansion-panel-container.js +54 -0
- package/expansion-panel/expansion-panel-container.js.map +1 -0
- package/expansion-panel/expansion-panel-container.styles.d.ts +2 -0
- package/expansion-panel/expansion-panel-container.styles.d.ts.map +1 -0
- package/expansion-panel/expansion-panel-container.styles.js +9 -0
- package/expansion-panel/expansion-panel-container.styles.js.map +1 -0
- package/expansion-panel/expansion-panel.d.ts +37 -0
- package/expansion-panel/expansion-panel.d.ts.map +1 -0
- package/expansion-panel/expansion-panel.js +89 -0
- package/expansion-panel/expansion-panel.js.map +1 -0
- package/expansion-panel/expansion-panel.styles.d.ts +2 -0
- package/expansion-panel/expansion-panel.styles.d.ts.map +1 -0
- package/expansion-panel/expansion-panel.styles.js +66 -0
- package/expansion-panel/expansion-panel.styles.js.map +1 -0
- package/field/field-base.d.ts +3 -3
- package/field/field-base.d.ts.map +1 -1
- package/field/field-base.js +20 -20
- package/field/field-base.js.map +1 -1
- package/field/field-defaults-context.d.ts +2 -2
- package/field/field-defaults-context.d.ts.map +1 -1
- package/field/field-defaults-context.js.map +1 -1
- package/field/field-defaults.d.ts +3 -3
- package/field/field-defaults.d.ts.map +1 -1
- package/field/field-defaults.js.map +1 -1
- package/field/field-variant.d.ts +1 -1
- package/field/field-variant.d.ts.map +1 -1
- package/field/field-variant.js.map +1 -1
- package/field/field.d.ts +3 -3
- package/field/field.d.ts.map +1 -1
- package/field/field.js +6 -6
- package/field/field.js.map +1 -1
- package/icon/icon.d.ts +2 -2
- package/icon/icon.d.ts.map +1 -1
- package/icon/icon.js +4 -4
- package/icon/icon.js.map +1 -1
- package/index.d.ts +24 -1
- package/index.d.ts.map +1 -1
- package/index.js +24 -1
- package/index.js.map +1 -1
- package/list/list-item.d.ts +16 -2
- package/list/list-item.d.ts.map +1 -1
- package/list/list-item.js +26 -6
- package/list/list-item.js.map +1 -1
- package/list/list-item.styles.d.ts.map +1 -1
- package/list/list-item.styles.js +13 -0
- package/list/list-item.styles.js.map +1 -1
- package/list/list.d.ts +2 -2
- package/list/list.d.ts.map +1 -1
- package/list/list.js +4 -4
- package/list/list.js.map +1 -1
- package/menu/menu-item.d.ts +3 -3
- package/menu/menu-item.d.ts.map +1 -1
- package/menu/menu-item.js +10 -10
- package/menu/menu-item.js.map +1 -1
- package/menu/menu.d.ts +2 -2
- package/menu/menu.d.ts.map +1 -1
- package/menu/menu.js +13 -13
- package/menu/menu.js.map +1 -1
- package/navigation/drawer-headline.d.ts +2 -2
- package/navigation/drawer-headline.d.ts.map +1 -1
- package/navigation/drawer-headline.js +6 -6
- package/navigation/drawer-headline.js.map +1 -1
- package/navigation/drawer-item.d.ts +3 -3
- package/navigation/drawer-item.d.ts.map +1 -1
- package/navigation/drawer-item.js +17 -12
- package/navigation/drawer-item.js.map +1 -1
- package/navigation/drawer.d.ts +2 -2
- package/navigation/drawer.d.ts.map +1 -1
- package/navigation/drawer.js +4 -4
- package/navigation/drawer.js.map +1 -1
- package/navigation/side-navigation.d.ts +9 -2
- package/navigation/side-navigation.d.ts.map +1 -1
- package/navigation/side-navigation.js +15 -8
- package/navigation/side-navigation.js.map +1 -1
- package/navigation-bar/navigation-bar-item.d.ts +40 -0
- package/navigation-bar/navigation-bar-item.d.ts.map +1 -0
- package/navigation-bar/navigation-bar-item.js +113 -0
- package/navigation-bar/navigation-bar-item.js.map +1 -0
- package/navigation-bar/navigation-bar-item.styles.d.ts +2 -0
- package/navigation-bar/navigation-bar-item.styles.d.ts.map +1 -0
- package/navigation-bar/navigation-bar-item.styles.js +101 -0
- package/navigation-bar/navigation-bar-item.styles.js.map +1 -0
- package/navigation-bar/navigation-bar.d.ts +40 -0
- package/navigation-bar/navigation-bar.d.ts.map +1 -0
- package/navigation-bar/navigation-bar.js +85 -0
- package/navigation-bar/navigation-bar.js.map +1 -0
- package/navigation-bar/navigation-bar.styles.d.ts +2 -0
- package/navigation-bar/navigation-bar.styles.d.ts.map +1 -0
- package/navigation-bar/navigation-bar.styles.js +44 -0
- package/navigation-bar/navigation-bar.styles.js.map +1 -0
- package/navigation-rail/navigation-rail-headline.d.ts +23 -0
- package/navigation-rail/navigation-rail-headline.d.ts.map +1 -0
- package/navigation-rail/navigation-rail-headline.js +28 -0
- package/navigation-rail/navigation-rail-headline.js.map +1 -0
- package/navigation-rail/navigation-rail-headline.styles.d.ts +2 -0
- package/navigation-rail/navigation-rail-headline.styles.d.ts.map +1 -0
- package/navigation-rail/navigation-rail-headline.styles.js +19 -0
- package/navigation-rail/navigation-rail-headline.styles.js.map +1 -0
- package/navigation-rail/navigation-rail-item.d.ts +58 -0
- package/navigation-rail/navigation-rail-item.d.ts.map +1 -0
- package/navigation-rail/navigation-rail-item.js +160 -0
- package/navigation-rail/navigation-rail-item.js.map +1 -0
- package/navigation-rail/navigation-rail-item.styles.d.ts +2 -0
- package/navigation-rail/navigation-rail-item.styles.d.ts.map +1 -0
- package/navigation-rail/navigation-rail-item.styles.js +182 -0
- package/navigation-rail/navigation-rail-item.styles.js.map +1 -0
- package/navigation-rail/navigation-rail.d.ts +66 -0
- package/navigation-rail/navigation-rail.d.ts.map +1 -0
- package/navigation-rail/navigation-rail.js +223 -0
- package/navigation-rail/navigation-rail.js.map +1 -0
- package/navigation-rail/navigation-rail.styles.d.ts +2 -0
- package/navigation-rail/navigation-rail.styles.d.ts.map +1 -0
- package/navigation-rail/navigation-rail.styles.js +220 -0
- package/navigation-rail/navigation-rail.styles.js.map +1 -0
- package/overflow-menu/overflow-menu.d.ts +8 -2
- package/overflow-menu/overflow-menu.d.ts.map +1 -1
- package/overflow-menu/overflow-menu.js +10 -1
- package/overflow-menu/overflow-menu.js.map +1 -1
- package/package.json +19 -3
- package/progress/circular-progress.d.ts +2 -2
- package/progress/circular-progress.d.ts.map +1 -1
- package/progress/circular-progress.js +6 -6
- package/progress/circular-progress.js.map +1 -1
- package/progress/progress-bar.d.ts +2 -2
- package/progress/progress-bar.d.ts.map +1 -1
- package/progress/progress-bar.js +6 -6
- package/progress/progress-bar.js.map +1 -1
- package/radio/radio-list-item.d.ts +4 -4
- package/radio/radio-list-item.d.ts.map +1 -1
- package/radio/radio-list-item.js +5 -5
- package/radio/radio-list-item.js.map +1 -1
- package/radio/radio.d.ts +3 -3
- package/radio/radio.d.ts.map +1 -1
- package/radio/radio.js +6 -6
- package/radio/radio.js.map +1 -1
- package/ripple/ripple.d.ts +2 -2
- package/ripple/ripple.d.ts.map +1 -1
- package/ripple/ripple.js +9 -9
- package/ripple/ripple.js.map +1 -1
- package/scaffold/pane.d.ts +127 -0
- package/scaffold/pane.d.ts.map +1 -0
- package/scaffold/pane.js +220 -0
- package/scaffold/pane.js.map +1 -0
- package/scaffold/pane.styles.d.ts +2 -0
- package/scaffold/pane.styles.d.ts.map +1 -0
- package/scaffold/pane.styles.js +1909 -0
- package/scaffold/pane.styles.js.map +1 -0
- package/scaffold/scaffold.d.ts +45 -0
- package/scaffold/scaffold.d.ts.map +1 -0
- package/scaffold/scaffold.js +170 -0
- package/scaffold/scaffold.js.map +1 -0
- package/scaffold/scaffold.styles.d.ts +2 -0
- package/scaffold/scaffold.styles.d.ts.map +1 -0
- package/scaffold/scaffold.styles.js +69 -0
- package/scaffold/scaffold.styles.js.map +1 -0
- package/scaffold/scroll-container-context.d.ts +4 -0
- package/scaffold/scroll-container-context.d.ts.map +1 -0
- package/scaffold/scroll-container-context.js +3 -0
- package/scaffold/scroll-container-context.js.map +1 -0
- package/scss/utilities/_divider.scss +4 -0
- package/search/search.d.ts +3 -3
- package/search/search.d.ts.map +1 -1
- package/search/search.js +7 -7
- package/search/search.js.map +1 -1
- package/search/search.styles.d.ts.map +1 -1
- package/search/search.styles.js +7 -2
- package/search/search.styles.js.map +1 -1
- package/select/option.d.ts +3 -3
- package/select/option.d.ts.map +1 -1
- package/select/option.js +8 -8
- package/select/option.js.map +1 -1
- package/select/select-navigation-controller.d.ts +4 -4
- package/select/select-navigation-controller.d.ts.map +1 -1
- package/select/select-navigation-controller.js.map +1 -1
- package/select/select.d.ts +18 -12
- package/select/select.d.ts.map +1 -1
- package/select/select.js +77 -31
- package/select/select.js.map +1 -1
- package/shared/button-wrapper.d.ts +1 -1
- package/shared/button-wrapper.d.ts.map +1 -1
- package/shared/button-wrapper.js +8 -8
- package/shared/button-wrapper.js.map +1 -1
- package/shared/char-count-text-field/native-text-field-wrapper.d.ts +2 -2
- package/shared/char-count-text-field/native-text-field-wrapper.d.ts.map +1 -1
- package/shared/char-count-text-field/native-text-field-wrapper.js +6 -6
- package/shared/char-count-text-field/native-text-field-wrapper.js.map +1 -1
- package/shared/menu-field/menu-field-navigation-controller.d.ts +3 -3
- package/shared/menu-field/menu-field-navigation-controller.d.ts.map +1 -1
- package/shared/menu-field/menu-field-navigation-controller.js.map +1 -1
- package/shared/menu-field/menu-field.d.ts +5 -5
- package/shared/menu-field/menu-field.d.ts.map +1 -1
- package/shared/menu-field/menu-field.js.map +1 -1
- package/shared/selection-control/selection-control-list-item.d.ts +2 -2
- package/shared/selection-control/selection-control-list-item.d.ts.map +1 -1
- package/shared/selection-control/selection-control-list-item.js +10 -1
- package/shared/selection-control/selection-control-list-item.js.map +1 -1
- package/shared/selection-control/selection-control.d.ts +1 -1
- package/shared/selection-control/selection-control.d.ts.map +1 -1
- package/shared/selection-control/selection-control.js +15 -7
- package/shared/selection-control/selection-control.js.map +1 -1
- package/shared/sets/set-base.d.ts +1 -1
- package/shared/sets/set-base.d.ts.map +1 -1
- package/shared/sets/set-base.js +2 -2
- package/shared/sets/set-base.js.map +1 -1
- package/shared/text-field-base/text-field-base.d.ts +34 -2
- package/shared/text-field-base/text-field-base.d.ts.map +1 -1
- package/shared/text-field-base/text-field-base.js +63 -4
- package/shared/text-field-base/text-field-base.js.map +1 -1
- package/skills/badge/SKILL.md +43 -0
- package/skills/buttons/SKILL.md +115 -0
- package/skills/card/SKILL.md +162 -0
- package/skills/chips/SKILL.md +95 -0
- package/skills/collapse/SKILL.md +37 -0
- package/skills/datepicker/SKILL.md +110 -0
- package/skills/dialog/SKILL.md +92 -0
- package/skills/drawer/SKILL.md +94 -0
- package/skills/expansion-panel/SKILL.md +65 -0
- package/skills/fab/SKILL.md +79 -0
- package/skills/list/SKILL.md +105 -0
- package/skills/menu/SKILL.md +120 -0
- package/skills/navigation-bar/SKILL.md +87 -0
- package/skills/navigation-rail/SKILL.md +127 -0
- package/skills/overview/SKILL.md +44 -0
- package/skills/progress/SKILL.md +63 -0
- package/skills/scaffold/SKILL.md +392 -0
- package/skills/search/SKILL.md +65 -0
- package/skills/select/SKILL.md +120 -0
- package/skills/selection-controls/SKILL.md +88 -0
- package/skills/setup/SKILL.md +58 -0
- package/skills/slider/SKILL.md +119 -0
- package/skills/snackbar/SKILL.md +70 -0
- package/skills/tab-bar/SKILL.md +55 -0
- package/skills/text-field/SKILL.md +114 -0
- package/skills/theming/SKILL.md +80 -0
- package/skills/top-app-bar/SKILL.md +64 -0
- package/skills/typeahead/SKILL.md +113 -0
- package/slider/slider.d.ts +73 -0
- package/slider/slider.d.ts.map +1 -0
- package/slider/slider.js +506 -0
- package/slider/slider.js.map +1 -0
- package/slider/slider.styles.d.ts +2 -0
- package/slider/slider.styles.d.ts.map +1 -0
- package/slider/slider.styles.js +292 -0
- package/slider/slider.styles.js.map +1 -0
- package/snackbar/snackbar.d.ts +4 -4
- package/snackbar/snackbar.d.ts.map +1 -1
- package/snackbar/snackbar.js +28 -28
- package/snackbar/snackbar.js.map +1 -1
- package/snackbar/snackbar.styles.js +1 -1
- package/snackbar/snackbar.styles.js.map +1 -1
- package/switch/switch-list-item.d.ts +4 -4
- package/switch/switch-list-item.d.ts.map +1 -1
- package/switch/switch-list-item.js +5 -5
- package/switch/switch-list-item.js.map +1 -1
- package/switch/switch.d.ts +3 -3
- package/switch/switch.d.ts.map +1 -1
- package/switch/switch.js +5 -5
- package/switch/switch.js.map +1 -1
- package/tab-bar/tab-bar.d.ts +6 -6
- package/tab-bar/tab-bar.d.ts.map +1 -1
- package/tab-bar/tab-bar.js +40 -23
- package/tab-bar/tab-bar.js.map +1 -1
- package/tab-bar/tab.d.ts +5 -5
- package/tab-bar/tab.d.ts.map +1 -1
- package/tab-bar/tab.js +9 -9
- package/tab-bar/tab.js.map +1 -1
- package/text-area/text-area.d.ts +3 -3
- package/text-area/text-area.d.ts.map +1 -1
- package/text-area/text-area.js +9 -9
- package/text-area/text-area.js.map +1 -1
- package/text-field/text-field.d.ts +15 -3
- package/text-field/text-field.d.ts.map +1 -1
- package/text-field/text-field.js +46 -13
- package/text-field/text-field.js.map +1 -1
- package/typeahead/highlight.d.ts +2 -2
- package/typeahead/highlight.d.ts.map +1 -1
- package/typeahead/highlight.js +7 -7
- package/typeahead/highlight.js.map +1 -1
- package/typeahead/typeahead.d.ts +7 -7
- package/typeahead/typeahead.d.ts.map +1 -1
- package/typeahead/typeahead.js +22 -18
- package/typeahead/typeahead.js.map +1 -1
- package/vscode.html-custom-data.json +870 -481
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Build a Material 3 responsive navigation rail — hidden on mobile, collapsed 96dp rail on medium with primary items, permanent expanded rail (220–360dp) on large with the full menu (including headlines). Two slots crossfade between the two states.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Navigation rail
|
|
6
|
+
|
|
7
|
+
`<u-navigation-rail>` is a responsive primary navigation surface with two independently configurable destination sets — one short list for the collapsed state, and an optional longer/grouped list for the expanded state. The two share the same on-screen area and the rail crossfades between them as it grows or shrinks.
|
|
8
|
+
|
|
9
|
+
## Responsive behavior
|
|
10
|
+
|
|
11
|
+
- **Mobile (< 840px)**: rail is hidden. Provide a different nav pattern (e.g. `<u-navigation-bar>`) for small screens.
|
|
12
|
+
- **Medium (840–1199px)**: rail is a permanent 96dp **collapsed** sidebar showing `slot="rail"` items.
|
|
13
|
+
- **Large (≥ 1200px)**: rail is permanently **expanded** (220–360dp, default 360dp), showing `slot="expanded"` items grouped by `<u-navigation-rail-headline>`s. `toggle-drawer` collapses it back to the 96dp width and crossfades to the `slot="rail"` list. If `slot="expanded"` is empty, the rail keeps showing `slot="rail"` items even when expanded.
|
|
14
|
+
|
|
15
|
+
## Basic usage
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<u-navigation-rail id="nav">
|
|
19
|
+
<!-- Collapsed: short list of primary destinations -->
|
|
20
|
+
<u-navigation-rail-item slot="rail" active>
|
|
21
|
+
<span class="material-symbols-outlined" slot="icon">home</span>
|
|
22
|
+
Home
|
|
23
|
+
</u-navigation-rail-item>
|
|
24
|
+
<u-navigation-rail-item slot="rail">
|
|
25
|
+
<span class="material-symbols-outlined" slot="icon">explore</span>
|
|
26
|
+
Browse
|
|
27
|
+
</u-navigation-rail-item>
|
|
28
|
+
<u-navigation-rail-item slot="rail">
|
|
29
|
+
<span class="material-symbols-outlined" slot="icon">library_music</span>
|
|
30
|
+
Library
|
|
31
|
+
</u-navigation-rail-item>
|
|
32
|
+
|
|
33
|
+
<!-- Expanded: full menu, grouped with headlines -->
|
|
34
|
+
<u-navigation-rail-headline slot="expanded">Library</u-navigation-rail-headline>
|
|
35
|
+
<u-navigation-rail-item slot="expanded" active>
|
|
36
|
+
<span class="material-symbols-outlined" slot="icon">home</span>
|
|
37
|
+
Home
|
|
38
|
+
</u-navigation-rail-item>
|
|
39
|
+
<u-navigation-rail-item slot="expanded">
|
|
40
|
+
<span class="material-symbols-outlined" slot="icon">explore</span>
|
|
41
|
+
Browse
|
|
42
|
+
</u-navigation-rail-item>
|
|
43
|
+
|
|
44
|
+
<u-navigation-rail-headline slot="expanded">Recents</u-navigation-rail-headline>
|
|
45
|
+
<u-navigation-rail-item slot="expanded">
|
|
46
|
+
<span class="material-symbols-outlined" slot="icon">favorite</span>
|
|
47
|
+
Favorites
|
|
48
|
+
</u-navigation-rail-item>
|
|
49
|
+
|
|
50
|
+
<u-scaffold>
|
|
51
|
+
<u-top-app-bar slot="top-bar" headline="Mail">
|
|
52
|
+
<u-icon-button slot="leading-icon"
|
|
53
|
+
onclick="const r = this.closest('u-navigation-rail'); r.toggleDrawer = !r.toggleDrawer;">
|
|
54
|
+
<span class="material-symbols-outlined">menu</span>
|
|
55
|
+
</u-icon-button>
|
|
56
|
+
</u-top-app-bar>
|
|
57
|
+
<div>Page content</div>
|
|
58
|
+
</u-scaffold>
|
|
59
|
+
</u-navigation-rail>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Slots on `<u-navigation-rail>`
|
|
63
|
+
|
|
64
|
+
| Slot | Content | Shown when |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| `rail` | Primary destinations (`<u-navigation-rail-item>`s). | Collapsed state (mobile is hidden; md is permanent; lg shows when `toggle-drawer` is set). |
|
|
67
|
+
| `expanded` | Full destination list — items + `<u-navigation-rail-headline>`s. | Expanded state (lg without `toggle-drawer`). Falls back to `rail` when empty. |
|
|
68
|
+
| `top` | Menu button, brand mark, or other top-pinned content. | Always (within visible rail). |
|
|
69
|
+
| `bottom` | FAB or secondary action pinned to the bottom. | Always (within visible rail). |
|
|
70
|
+
| default | Page content (typically `<u-scaffold>`). | Always. |
|
|
71
|
+
|
|
72
|
+
The rail crossfades (200ms) between the `rail` and `expanded` layers when its state changes.
|
|
73
|
+
|
|
74
|
+
## `<u-navigation-rail-item>`
|
|
75
|
+
|
|
76
|
+
The rail sets the item's `variant` automatically based on which slot it lives in:
|
|
77
|
+
|
|
78
|
+
- Inside `slot="rail"`: `variant="collapsed"` (vertical icon + label, 56×32dp pill around the icon, label-medium).
|
|
79
|
+
- Inside `slot="expanded"`: `variant="expanded"` (horizontal icon + label inside a content-sized 56dp pill aligned to the leading edge with 16dp inset; label-large).
|
|
80
|
+
|
|
81
|
+
Slots: default (label), `icon` (24dp icon), `badge` (optional `<u-badge>`).
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<u-navigation-rail-item slot="expanded" active>
|
|
85
|
+
<span class="material-symbols-outlined" slot="icon">inbox</span>
|
|
86
|
+
Inbox
|
|
87
|
+
<u-badge slot="badge">12</u-badge>
|
|
88
|
+
</u-navigation-rail-item>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## `<u-navigation-rail-headline>`
|
|
92
|
+
|
|
93
|
+
Section header for grouping destinations inside `slot="expanded"`. Renders title-small typography in `on-surface-variant`, with M3-correct padding. Only visible while the rail is expanded — headlines never appear in the narrow collapsed form, since the collapsed `slot="rail"` is a separate, headline-free list.
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<u-navigation-rail-headline slot="expanded">Recents</u-navigation-rail-headline>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Tokens applied (M3 expressive spec)
|
|
100
|
+
|
|
101
|
+
Sources: `md.comp.nav-rail`, `md.comp.nav-rail-collapsed`, `md.comp.nav-rail-expanded`, `md.comp.nav-rail-item`, `md.comp.nav-rail-item-vertical`, `md.comp.nav-rail-item-horizontal`.
|
|
102
|
+
|
|
103
|
+
- **Rail container**: 96dp width (collapsed default; 80dp narrow opt-in) / 220–360dp width (expanded); `surface` color; level-0 elevation; `corner-none`; 44dp top space; 20dp vertical-trailing space.
|
|
104
|
+
- **Item container**: 64dp height (default); 4dp gap between items (collapsed) / 0dp (expanded).
|
|
105
|
+
- **Active indicator**: `secondary-container` background; `corner-full` shape; 56×32dp pill (collapsed/vertical) / content-sized 56dp pill (expanded/horizontal) aligned to the 16dp leading edge.
|
|
106
|
+
- **Icon**: 24dp; `on-secondary-container` (active) / `on-surface-variant` (inactive).
|
|
107
|
+
- **Label**: label-medium 12sp (collapsed) / label-large 14sp (expanded). Active color is `secondary` (collapsed/vertical) or `on-secondary-container` (expanded/horizontal); inactive is `on-surface-variant`. Active items use `weight-prominent` (700 / bold).
|
|
108
|
+
- **State layer**: `on-secondary-container` color; hover 8%, focus 10%, pressed 10% — applied **only inside the active-indicator pill**. Click target remains the entire item, ripples confined to the pill via `u-ripple` inside the indicator.
|
|
109
|
+
|
|
110
|
+
## The two slots are the SAME navigation at two detail levels — not two different menus
|
|
111
|
+
|
|
112
|
+
`slot="rail"` (collapsed) and `slot="expanded"` are the **same destination set** shown at two levels of detail: collapsed = icons only; expanded = icons + labels grouped under headlines. They are *not* a place to put two different navigations.
|
|
113
|
+
|
|
114
|
+
A tempting-but-wrong idea: make the collapsed rail the product nav (Mail, Chat, Meet) and the expanded rail the current section's sub-navigation (mail folders — Inbox, Starred, Sent…). This breaks the user's mental model: toggling the rail should reveal *more detail about the same destinations*, not **swap** what the rail navigates. A user who expands the rail expecting fuller product labels instead finds an unrelated folder list. Don't do this.
|
|
115
|
+
|
|
116
|
+
Where section sub-navigation belongs instead:
|
|
117
|
+
- **A small set of peer views** (e.g. "All / Unread / Starred"): a `u-chip-set` or `u-tab-bar` in the list header.
|
|
118
|
+
- **A genuinely large folder tree** that warrants its own surface: a separate `<u-pane>` with a `<u-drawer>` — but first ask whether the screen actually needs it. A CRM "inbox" is a list of conversations; it usually does **not** need Gmail-style Sent/Drafts/Trash folders at all. Cut the layer rather than house it.
|
|
119
|
+
|
|
120
|
+
The rail carries **product-level** destinations only. If a sub-navigation doesn't fit as collapsed-icon + expanded-label of those same destinations, it doesn't belong in the rail.
|
|
121
|
+
|
|
122
|
+
## Caveats
|
|
123
|
+
|
|
124
|
+
- Don't slot a `<u-drawer>`: the expanded rail is a distinct M3 surface with its own pill-shaped items, not a drawer.
|
|
125
|
+
- Mobile (< 840px) hides the rail entirely — give small screens a different nav (`<u-navigation-bar>`, modal etc).
|
|
126
|
+
- `slot="top"` / `slot="bottom"` children are sized intrinsically (not stretched), and align horizontally based on the rail state — centered when collapsed, leading-aligned with 16dp inset when expanded.
|
|
127
|
+
- The expanded layer scrolls when its content overflows the rail height; the collapsed layer does too if you slot more than ~5 items.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Overview of @universal-material/web — what's in the library and which skills cover each area.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# @universal-material/web — overview
|
|
6
|
+
|
|
7
|
+
Material Design 3 web components built with Lit. Custom elements with the `u-` prefix; class names match the tag without the prefix (`<u-button>` → `class Button`).
|
|
8
|
+
|
|
9
|
+
## What's inside
|
|
10
|
+
|
|
11
|
+
| Area | Components | Skill |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Bootstrap | install, fonts, theme | `setup`, `theming` |
|
|
14
|
+
| Layout | `<u-scaffold>` | `scaffold` |
|
|
15
|
+
| App bars | `<u-top-app-bar>`, `<u-navigation-bar>` | `top-app-bar`, `navigation-bar` |
|
|
16
|
+
| Navigation | `<u-side-navigation>`, `<u-drawer>` | `drawer` |
|
|
17
|
+
| Common buttons | `<u-button>`, `<u-icon-button>`, `<u-button-set>` | `buttons` |
|
|
18
|
+
| FAB | `<u-fab>`, `<u-fab-menu>` | `fab` |
|
|
19
|
+
| Dialogs | `<u-dialog>` + `Dialog.message/.confirm` | `dialog` |
|
|
20
|
+
| Text input | `<u-text-field>`, `<u-text-area>` | `text-field` |
|
|
21
|
+
| Select | `<u-select>`, `<u-option>` | `select` |
|
|
22
|
+
| Chips | `<u-chip>`, `<u-chip-set>`, `<u-chip-field>` | `chips` |
|
|
23
|
+
| Selection controls | `<u-checkbox>`, `<u-radio>`, `<u-switch>` + list-items | `selection-controls` |
|
|
24
|
+
| Date | `<u-datepicker>`, `<u-range-datepicker>`, `<u-calendar>` | `datepicker` |
|
|
25
|
+
| Menus | `<u-menu>`, `<u-menu-item>`, `<u-overflow-menu>` | `menu` |
|
|
26
|
+
| Lists | `<u-list>`, `<u-list-item>` | `list` |
|
|
27
|
+
| Tabs | `<u-tab-bar>`, `<u-tab>` | `tab-bar` |
|
|
28
|
+
| Feedback | `Snackbar.show` | `snackbar` |
|
|
29
|
+
| Progress | `<u-progress-bar>`, `<u-circular-progress>` | `progress` |
|
|
30
|
+
| Badge | `<u-badge>` | `badge` |
|
|
31
|
+
| Cards | `<u-card>`, `<u-card-content>`, `<u-card-media>` | `card` |
|
|
32
|
+
| Autocomplete | `<u-typeahead>`, `<u-highlight>` | `typeahead` |
|
|
33
|
+
| Search | `<u-search>` (M3 search bar; pairs with `u-typeahead`) | `search` |
|
|
34
|
+
|
|
35
|
+
## Core conventions
|
|
36
|
+
|
|
37
|
+
- Custom elements have the `u-` prefix; class names are unprefixed PascalCase (`<u-top-app-bar>` → `class TopAppBar`).
|
|
38
|
+
- Every component exposes its main inner boxes via `part` attributes — style externally with `::part(container)` etc.
|
|
39
|
+
- Theming is driven by CSS variables (`--u-color-primary`, ...). Use `ThemeBuilder.create(seedHex).build()` once at boot.
|
|
40
|
+
- Slot names match what they hold (`leading-icon`, `trailing-icon`, `headline`, `actions`, ...).
|
|
41
|
+
- Form-associated components participate in `<form>` submission natively.
|
|
42
|
+
- Major version follows Material spec version (M3 → `3.x`).
|
|
43
|
+
|
|
44
|
+
When unsure which skill to use, this overview maps it out.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show determinate or indeterminate progress — u-progress-bar (linear) and u-circular-progress (ring).
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Progress indicators
|
|
6
|
+
|
|
7
|
+
Two components share the same value API: `<u-progress-bar>` (linear, M3 4dp track) and `<u-circular-progress>` (ring).
|
|
8
|
+
|
|
9
|
+
## Determinate
|
|
10
|
+
|
|
11
|
+
`value` is a number; `max` defaults to `1`. So `value` is a fraction `0–1`, or pass a `max`:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<u-progress-bar value="0.81"></u-progress-bar> <!-- 81% -->
|
|
15
|
+
<u-progress-bar value="81" max="100"></u-progress-bar> <!-- same -->
|
|
16
|
+
|
|
17
|
+
<u-circular-progress value="0.66"></u-circular-progress>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Indeterminate
|
|
21
|
+
|
|
22
|
+
Omit `value` (or set it to `undefined`) for the looping animation:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<u-progress-bar></u-progress-bar>
|
|
26
|
+
<u-circular-progress></u-circular-progress>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Setting value from JS
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const bar = document.querySelector('u-progress-bar')!;
|
|
33
|
+
bar.value = 0.4; // determinate
|
|
34
|
+
bar.value = undefined; // back to indeterminate
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Sizing & color (tokens)
|
|
38
|
+
|
|
39
|
+
- **`<u-progress-bar>` is not full-width by default** — give it `display: block` (or a width) to span its container:
|
|
40
|
+
```html
|
|
41
|
+
<u-progress-bar value="0.5" style="display: block"></u-progress-bar>
|
|
42
|
+
```
|
|
43
|
+
- **`<u-circular-progress>`** size via `--u-circular-progress-size` (default `3rem`):
|
|
44
|
+
```html
|
|
45
|
+
<u-circular-progress value="0.81" style="--u-circular-progress-size: 96px"></u-circular-progress>
|
|
46
|
+
```
|
|
47
|
+
Arc color `--u-circular-progress-color` (default `primary`); track `--u-circular-progress-track-color` (default `secondary-container`).
|
|
48
|
+
|
|
49
|
+
## Centered label in a ring (gauge)
|
|
50
|
+
|
|
51
|
+
Wrap the ring in a `position: relative` box and absolutely-center the label:
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<div style="position: relative; width: 96px; height: 96px">
|
|
55
|
+
<u-circular-progress value="0.81" style="--u-circular-progress-size: 96px"></u-circular-progress>
|
|
56
|
+
<div style="position: absolute; inset: 0; display: flex; align-items: center; justify-content: center">81%</div>
|
|
57
|
+
</div>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Caveats
|
|
61
|
+
|
|
62
|
+
- Determinate values are rounded (~2 decimals circular / 1 decimal bar) — sub-pixel differences won't render.
|
|
63
|
+
- Use indeterminate for unknown-duration waits; determinate when you can compute the fraction.
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Lay out an app screen with u-scaffold — top app bar, flex-row body that accepts plain content and any number of u-pane children, an optional navigation bar at the bottom, and a FAB anchored above the bar. Panes behave per a mode attribute (fixed / collapsible / sidebar / fullscreen) that can vary by Material 3 breakpoint.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Scaffold (page layout)
|
|
6
|
+
|
|
7
|
+
`<u-scaffold>` is a layout container that hosts a top app bar, a flex-row body, an optional navigation bar and a floating FAB. The body accepts plain content and any number of `<u-pane>` children as siblings — panes are flex items in DOM order; non-pane children flex to fill the remaining space.
|
|
8
|
+
|
|
9
|
+
## Basic usage
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<u-scaffold style="height: 100vh">
|
|
13
|
+
<u-top-app-bar slot="top-bar" size="large" headline="Inbox">
|
|
14
|
+
<u-icon-button slot="leading-icon">
|
|
15
|
+
<span class="material-symbols-outlined">menu</span>
|
|
16
|
+
</u-icon-button>
|
|
17
|
+
</u-top-app-bar>
|
|
18
|
+
|
|
19
|
+
<main style="padding: 16px 24px; overflow: auto">
|
|
20
|
+
<!-- scrollable page content -->
|
|
21
|
+
</main>
|
|
22
|
+
|
|
23
|
+
<u-navigation-bar slot="bottom-bar">
|
|
24
|
+
<u-navigation-bar-item active>
|
|
25
|
+
<span class="material-symbols-outlined" slot="icon">inbox</span>
|
|
26
|
+
Inbox
|
|
27
|
+
</u-navigation-bar-item>
|
|
28
|
+
</u-navigation-bar>
|
|
29
|
+
|
|
30
|
+
<u-fab slot="fab" color="primary">
|
|
31
|
+
<span class="material-symbols-outlined">edit</span>
|
|
32
|
+
</u-fab>
|
|
33
|
+
</u-scaffold>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The scaffold:
|
|
37
|
+
- arranges panes and non-pane content in a flex row with a 16dp gap,
|
|
38
|
+
- auto-sets `position="absolute"` on slotted `<u-top-app-bar>` / `<u-navigation-bar>` so they anchor against the scaffold (not the viewport),
|
|
39
|
+
- measures the navigation bar's height and offsets the FAB above it (16dp gap per M3),
|
|
40
|
+
- writes `data-align="start|end"` on each `<u-pane>` child based on whether it appears before or after the first non-pane child — used by sidebar/fullscreen modes to pick the slide direction,
|
|
41
|
+
- exposes itself as a named CSS container (`container-type: inline-size; container-name: u-scaffold`) so panes can opt into `query-context="container"`.
|
|
42
|
+
|
|
43
|
+
## Multi-pane layout via flex
|
|
44
|
+
|
|
45
|
+
Drop panes around the body content; DOM order is visual order. Panes default to `mode="fixed"` (always visible flex item) — they keep their content's intrinsic width unless you style them.
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<u-scaffold style="height: 100vh">
|
|
49
|
+
<u-pane variant="filled" style="width: 240px">
|
|
50
|
+
<!-- navigation pane -->
|
|
51
|
+
</u-pane>
|
|
52
|
+
|
|
53
|
+
<u-pane variant="transparent" style="flex: 1 1 0">
|
|
54
|
+
<!-- main body content; transparent = no background, scrolls inside .content -->
|
|
55
|
+
</u-pane>
|
|
56
|
+
|
|
57
|
+
<u-pane variant="filled" style="width: 320px">
|
|
58
|
+
<!-- supporting pane -->
|
|
59
|
+
</u-pane>
|
|
60
|
+
</u-scaffold>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The scaffold applies `flex: 1 1 0` to every non-pane child via `::slotted(:not(u-pane))`, so a plain `<main>` would also flex correctly — but see the recommendation below for why panes are preferred when other panes are present.
|
|
64
|
+
|
|
65
|
+
### Recommendation: be consistent — if you use panes, use them for every body sibling
|
|
66
|
+
|
|
67
|
+
When any body child of a scaffold is a `<u-pane>`, prefer making **all** body children panes (even the central content). Mixing `<u-pane>` with raw `<main>` / `<aside>` creates three forms of inconsistency:
|
|
68
|
+
|
|
69
|
+
1. **Surface**: panes pick up Material 3 surface tokens (filled = `surface-container-low` + 12dp corner; transparent = no bg). Raw `<main>` sits on `surface` without that shape. Side-by-side, the pane looks like a "real panel" and the main looks like loose content.
|
|
70
|
+
2. **Scrolling**: panes scroll inside their internal `.content` part — set the pane's height and overflow is handled automatically. Raw `<main>` needs `overflow: auto` set explicitly and `min-height: 0` to work right inside the flex row.
|
|
71
|
+
3. **Responsive modes**: panes accept `mode` / `mode-sm` / `mode-md` / `mode-lg` / `mode-xl` for breakpoint-driven layout changes. Raw `<main>` doesn't — you'd have to write `@media` rules to mirror the same behavior.
|
|
72
|
+
|
|
73
|
+
Use the `transparent` variant for the central content area when you want it to read as the page surface rather than a raised panel:
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<u-scaffold style="height: 100vh">
|
|
77
|
+
<u-pane mode="sidebar" mode-md="fixed" variant="filled" style="width: 240px">…sub-nav…</u-pane>
|
|
78
|
+
<u-pane variant="transparent" style="flex: 1 1 0">…page content…</u-pane>
|
|
79
|
+
</u-scaffold>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The rule is "be deliberate about the mix": use raw `<main>` when no panes exist in the layout at all (simpler), and use panes everywhere when any pane is present (consistent).
|
|
83
|
+
|
|
84
|
+
## Canonical patterns
|
|
85
|
+
|
|
86
|
+
Two layouts come up over and over in real apps. Use these as the starting point and tune from there.
|
|
87
|
+
|
|
88
|
+
### Pattern A — "Settings / section nav" (settings, profile, admin)
|
|
89
|
+
|
|
90
|
+
A settings screen **is a list-detail**: the section nav is the *list* and the section's content is the *detail*. Don't model it as "a modal sidebar + always-on content" — that hides the nav on mobile and forces a redundant mobile picker. Instead:
|
|
91
|
+
|
|
92
|
+
- **Section nav = the list**: `mode="fixed"` (always visible). On mobile it fills the viewport (it's the only thing shown until you pick a section); on md+ it's a fixed-width column. Drive the width with a class, not inline style, so a media query can switch it.
|
|
93
|
+
- **Section content = the detail**: `mode="fullscreen" mode-md="fixed"`. On mobile, picking a section opens the content as a fullscreen overlay with a back button; on md+ both sit side by side.
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<u-scaffold style="height: 100vh">
|
|
97
|
+
<u-top-app-bar slot="top-bar" headline="Settings">…</u-top-app-bar>
|
|
98
|
+
|
|
99
|
+
<!-- Nav (the list): fills the screen on mobile, fixed column on md+ -->
|
|
100
|
+
<u-pane id="nav" mode="fixed" variant="filled" class="settings-nav">
|
|
101
|
+
<u-drawer>
|
|
102
|
+
<u-drawer-item active data-section="general" keep-drawer-open>General</u-drawer-item>
|
|
103
|
+
<u-drawer-item data-section="security" keep-drawer-open>Security</u-drawer-item>
|
|
104
|
+
</u-drawer>
|
|
105
|
+
</u-pane>
|
|
106
|
+
|
|
107
|
+
<!-- Content (the detail): fullscreen overlay on mobile, fixed on md+ -->
|
|
108
|
+
<u-pane id="content" mode="fullscreen" mode-md="fixed" variant="transparent" style="flex: 1 1 0; min-width: 0;">
|
|
109
|
+
<div slot="header" class="settings-back"><!-- shown only on mobile -->
|
|
110
|
+
<u-icon-button onclick="document.getElementById('content').close()">
|
|
111
|
+
<span class="material-symbols-outlined">arrow_back</span>
|
|
112
|
+
</u-icon-button>
|
|
113
|
+
<span class="u-title-s">Settings</span>
|
|
114
|
+
</div>
|
|
115
|
+
<!-- sections, forms, etc. -->
|
|
116
|
+
</u-pane>
|
|
117
|
+
</u-scaffold>
|
|
118
|
+
|
|
119
|
+
<style>
|
|
120
|
+
.settings-nav { flex: 1 1 0; } /* mobile: nav fills viewport */
|
|
121
|
+
@media (min-width: 840px) { .settings-nav { flex: 0 0 320px; } } /* md+: fixed column */
|
|
122
|
+
.settings-back { display: none; align-items: center; gap: 8px; padding: 8px 12px; }
|
|
123
|
+
@media (max-width: 839.98px) { .settings-back { display: flex; } } /* back only on mobile */
|
|
124
|
+
</style>
|
|
125
|
+
|
|
126
|
+
<script>
|
|
127
|
+
// Picking a section reveals it AND opens the detail (no-op at md+ where it's fixed).
|
|
128
|
+
document.querySelectorAll('u-drawer-item[data-section]').forEach(it =>
|
|
129
|
+
it.addEventListener('click', () => {
|
|
130
|
+
showSection(it.dataset.section);
|
|
131
|
+
document.getElementById('content').show();
|
|
132
|
+
}));
|
|
133
|
+
</script>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Two cascade traps this pattern walks into (both bit real builds):
|
|
137
|
+
- **`display` on the mobile-only back header**: put it in a class with the media query, never `style="display:flex"` inline — inline beats the class's `display:none` and the header leaks onto desktop.
|
|
138
|
+
- **Pane width**: `mode="fixed"` panes set their own `flex` internally; override it from a document-scope **class** (not the component's `:host`) so a media query can flip `flex: 1 1 0` ↔ `flex: 0 0 320px`.
|
|
139
|
+
|
|
140
|
+
### Pattern B — "List-detail" (mail, messaging, file managers, CRM contacts)
|
|
141
|
+
|
|
142
|
+
Three panes: a small top-level nav, a list of items, and a detail view. The detail collapses to a fullscreen overlay on mobile so it can take over the viewport when the user picks an item, then slides back out when they hit back.
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<u-scaffold style="height: 100vh">
|
|
146
|
+
<u-top-app-bar slot="top-bar" headline="Mail">
|
|
147
|
+
<u-icon-button slot="leading-icon" onclick="document.getElementById('mailnav').toggle()">
|
|
148
|
+
<span class="material-symbols-outlined">menu</span>
|
|
149
|
+
</u-icon-button>
|
|
150
|
+
</u-top-app-bar>
|
|
151
|
+
|
|
152
|
+
<!-- Nav: modal on mobile/medium, fixed at lg -->
|
|
153
|
+
<u-pane id="mailnav" mode="sidebar" mode-lg="fixed" variant="filled" style="width: 240px;">
|
|
154
|
+
<u-drawer>
|
|
155
|
+
<u-drawer-item active>
|
|
156
|
+
<span slot="icon" class="material-symbols-outlined">inbox</span>
|
|
157
|
+
Inbox
|
|
158
|
+
<span slot="badge">12</span>
|
|
159
|
+
</u-drawer-item>
|
|
160
|
+
<u-drawer-item>
|
|
161
|
+
<span slot="icon" class="material-symbols-outlined">send</span>
|
|
162
|
+
Sent
|
|
163
|
+
</u-drawer-item>
|
|
164
|
+
</u-drawer>
|
|
165
|
+
</u-pane>
|
|
166
|
+
|
|
167
|
+
<!-- List: always in-flow. Filled gives it a distinct surface. -->
|
|
168
|
+
<u-pane variant="filled" mode="fixed" style="width: 360px;">
|
|
169
|
+
<div slot="header" style="padding: 8px 12px; display: flex; align-items: center; gap: 8px;">
|
|
170
|
+
<strong>Primary · 5 messages</strong>
|
|
171
|
+
</div>
|
|
172
|
+
<u-list>
|
|
173
|
+
<u-list-item selectable onclick="openMessage(...)">…</u-list-item>
|
|
174
|
+
<!-- ... -->
|
|
175
|
+
</u-list>
|
|
176
|
+
</u-pane>
|
|
177
|
+
|
|
178
|
+
<!-- Detail: fullscreen overlay on mobile, fixed in-flow on md+. THIS is the key. -->
|
|
179
|
+
<u-pane id="detail" mode="fullscreen" mode-md="fixed" variant="filled" style="flex: 1 1 0; min-width: 0;">
|
|
180
|
+
<div slot="header" style="display: flex; align-items: center; gap: 4px; padding: 8px 12px;">
|
|
181
|
+
<u-icon-button onclick="document.getElementById('detail').close()" aria-label="Back">
|
|
182
|
+
<span class="material-symbols-outlined">arrow_back</span>
|
|
183
|
+
</u-icon-button>
|
|
184
|
+
<span style="flex: 1"></span>
|
|
185
|
+
<u-icon-button aria-label="Archive"><span class="material-symbols-outlined">archive</span></u-icon-button>
|
|
186
|
+
</div>
|
|
187
|
+
<div style="padding: 0 24px 24px;">
|
|
188
|
+
<!-- subject, sender, body -->
|
|
189
|
+
</div>
|
|
190
|
+
</u-pane>
|
|
191
|
+
</u-scaffold>
|
|
192
|
+
|
|
193
|
+
<script>
|
|
194
|
+
function openMessage(id) {
|
|
195
|
+
// populate detail content...
|
|
196
|
+
document.getElementById('detail').show(); // opens fullscreen overlay on mobile, no-op at md+ where it's already fixed
|
|
197
|
+
}
|
|
198
|
+
</script>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
What makes this work:
|
|
202
|
+
|
|
203
|
+
- **Nav pane**: `mode="sidebar" mode-lg="fixed"` — closed-by-default modal until lg, where it docks in-flow. (`mode-md="collapsible"` is another option if you want it visible at md but collapsible.)
|
|
204
|
+
- **List pane**: `mode="fixed"` — always visible. `filled` so it has its own surface, distinct from the detail.
|
|
205
|
+
- **Detail pane**: `mode="fullscreen" mode-md="fixed"` — **this is the linchpin of the list-detail pattern**. At mobile sizes the detail slides in OVER the list when opened; the back button calls `pane.close()` to dismiss. At md+ it's a fixed flex item beside the list. The mode change is pure CSS; the `open` property persists across breakpoints (once you set it, it sticks).
|
|
206
|
+
- **Header slot**: each pane uses `slot="header"` for its toolbar so it stays pinned while the list/detail content scrolls.
|
|
207
|
+
|
|
208
|
+
There's a working version of this in the docs site under `docs/src/app/screens/scaffold-list-detail/` (a Mail clone) — read it whenever you need the full responsive choreography.
|
|
209
|
+
|
|
210
|
+
## Modes and breakpoints
|
|
211
|
+
|
|
212
|
+
Each pane picks a `mode` (default `fixed`). Optional `mode-sm` / `mode-md` / `mode-lg` / `mode-xl` attributes override the mode from that Material 3 breakpoint upward. The largest matching breakpoint wins — all resolution is CSS, no `matchMedia`.
|
|
213
|
+
|
|
214
|
+
| Mode | Behaviour |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `fixed` (default) | In-flow flex item, always visible. `open` is ignored. |
|
|
217
|
+
| `collapsible` | In-flow when `open=true`, removed from the row when `open=false` (animated). |
|
|
218
|
+
| `sidebar` | Modal drawer overlay against the scaffold when `open=true`. Slides from the leading or trailing edge per DOM order. Closed by default. |
|
|
219
|
+
| `fullscreen` | Like `sidebar` but full-width and sits above the top app bar (no scrim). Closed by default. |
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
<u-scaffold style="height: 100vh">
|
|
223
|
+
<!-- Navigation: sidebar on mobile, collapsible at md+, permanent at lg+ -->
|
|
224
|
+
<u-pane
|
|
225
|
+
id="nav"
|
|
226
|
+
mode="sidebar"
|
|
227
|
+
mode-md="collapsible"
|
|
228
|
+
mode-lg="fixed"
|
|
229
|
+
variant="filled"
|
|
230
|
+
style="width: 240px">
|
|
231
|
+
<!-- navigation -->
|
|
232
|
+
</u-pane>
|
|
233
|
+
|
|
234
|
+
<main style="overflow: auto">…</main>
|
|
235
|
+
|
|
236
|
+
<!-- Inspector: fullscreen on mobile, permanent at md+ -->
|
|
237
|
+
<u-pane
|
|
238
|
+
id="inspector"
|
|
239
|
+
mode="fullscreen"
|
|
240
|
+
mode-md="fixed"
|
|
241
|
+
variant="filled"
|
|
242
|
+
style="width: 320px">
|
|
243
|
+
<!-- details -->
|
|
244
|
+
</u-pane>
|
|
245
|
+
</u-scaffold>
|
|
246
|
+
|
|
247
|
+
<script>
|
|
248
|
+
document.getElementById('nav').show(); // open
|
|
249
|
+
document.getElementById('nav').toggle(); // flip
|
|
250
|
+
document.getElementById('inspector').close();
|
|
251
|
+
</script>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The default value of `open` follows the base `mode`: `true` for `fixed` / `collapsible`, `false` for `sidebar` / `fullscreen`. Once you write to `open` (attribute or property), your value sticks regardless of mode changes.
|
|
255
|
+
|
|
256
|
+
## Container queries
|
|
257
|
+
|
|
258
|
+
Switch a pane to react to the scaffold's own width rather than the viewport with `query-context="container"`. Useful when a scaffold is embedded in a narrower outer layout.
|
|
259
|
+
|
|
260
|
+
```html
|
|
261
|
+
<u-pane mode="sidebar" mode-md="fixed" query-context="container">
|
|
262
|
+
<!-- mode-md kicks in at md *of the scaffold's box*, not the viewport -->
|
|
263
|
+
</u-pane>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Hosting a drawer or rail
|
|
267
|
+
|
|
268
|
+
`<u-drawer>` and `<u-navigation-rail>` are first-class body siblings or pane contents — there's no special slot.
|
|
269
|
+
|
|
270
|
+
```html
|
|
271
|
+
<u-scaffold>
|
|
272
|
+
<u-top-app-bar slot="top-bar" headline="Mail">
|
|
273
|
+
<u-icon-button slot="leading-icon">
|
|
274
|
+
<span class="material-symbols-outlined">menu</span>
|
|
275
|
+
</u-icon-button>
|
|
276
|
+
</u-top-app-bar>
|
|
277
|
+
|
|
278
|
+
<!-- Permanent drawer below lg, modal sidebar above -->
|
|
279
|
+
<u-pane id="nav" mode="sidebar" mode-lg="fixed" style="width: 360px">
|
|
280
|
+
<u-drawer>
|
|
281
|
+
<u-drawer-headline>Mail</u-drawer-headline>
|
|
282
|
+
<u-drawer-item>Inbox</u-drawer-item>
|
|
283
|
+
<u-drawer-item>Sent</u-drawer-item>
|
|
284
|
+
</u-drawer>
|
|
285
|
+
</u-pane>
|
|
286
|
+
|
|
287
|
+
<main>…</main>
|
|
288
|
+
</u-scaffold>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
For a rail at all sizes, drop it directly in the body row:
|
|
292
|
+
|
|
293
|
+
```html
|
|
294
|
+
<u-scaffold>
|
|
295
|
+
<u-navigation-rail>…</u-navigation-rail>
|
|
296
|
+
<main>…</main>
|
|
297
|
+
</u-scaffold>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Scroll behaviour
|
|
301
|
+
|
|
302
|
+
The scaffold doesn't own the scroll. Non-pane body content needs its own `overflow: auto` (the scaffold applies `min-height: 0` so the rule works). Pane content scrolls inside the pane's own `.content` part.
|
|
303
|
+
|
|
304
|
+
The top app bar, FAB and FAB menu listen to `window` by default. Use the bar's `scrollContainer` attribute to point it at a specific element:
|
|
305
|
+
|
|
306
|
+
```html
|
|
307
|
+
<u-top-app-bar slot="top-bar" scrollContainer="body-scroll" headline="…"></u-top-app-bar>
|
|
308
|
+
|
|
309
|
+
<div id="body-scroll" style="overflow: auto">…</div>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## `<u-pane>` attribute reference
|
|
313
|
+
|
|
314
|
+
| Attribute | Values | Default | What it does |
|
|
315
|
+
| --- | --- | --- | --- |
|
|
316
|
+
| `variant` | `transparent` \| `filled` | `transparent` | `filled` gives a `surface-container-low` background + 12dp corner. Modal-mode overlays use `surface-container-low` (sidebar) or `surface-container-lowest` (fullscreen) automatically. |
|
|
317
|
+
| `mode` | `fixed` \| `collapsible` \| `sidebar` \| `fullscreen` | `fixed` | Base behaviour. |
|
|
318
|
+
| `mode-sm` / `mode-md` / `mode-lg` / `mode-xl` | same values | unset | Per-breakpoint override (≥ 600 / 840 / 1200 / 1600 px). Larger breakpoints win. |
|
|
319
|
+
| `query-context` | `media` \| `container` | `media` | Whether breakpoint overrides query the viewport or the scaffold's container. |
|
|
320
|
+
| `open` | boolean | `true` for `fixed`/`collapsible`, `false` for `sidebar`/`fullscreen` | Programmatic open state. Ignored in `fixed`. |
|
|
321
|
+
| `animation` | `none` \| `exit` \| `exit-start` \| `exit-end` \| `fade` | `exit` | How the pane transitions in/out. `exit` (default) slides off-screen and infers the edge from `data-align` (start → leading, end → trailing). `exit-start`/`exit-end` force a side. `fade` swaps the slide for an opacity transition. `none` disables the transition. |
|
|
322
|
+
| `animation-sm` / `animation-md` / `animation-lg` / `animation-xl` | same values | unset | Per-breakpoint override for `animation`. Larger breakpoints win. |
|
|
323
|
+
|
|
324
|
+
### `--u-pane-width` (collapsible slide distance)
|
|
325
|
+
|
|
326
|
+
In `collapsible` mode the pane slides off via a negative `margin-inline-start` (or `margin-inline-end` when `data-align=end`). The slide distance defaults to `100%` of the body row — for the math to match the pane's actual open width, set `--u-pane-width` to that width.
|
|
327
|
+
|
|
328
|
+
```html
|
|
329
|
+
<!-- Sets both the visible width and the slide distance -->
|
|
330
|
+
<u-pane mode-md="collapsible" style="width: 280px; --u-pane-width: 280px;">…</u-pane>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Programmatic control
|
|
334
|
+
|
|
335
|
+
Each pane exposes `show()`, `close()`, `toggle()`, plus the `open` boolean property. The pane fires `open` and `close` events when the consumer writes to `open`; `expand` / `collapse` events no longer exist (mode resolution is CSS-only).
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
const pane = document.getElementById('nav');
|
|
339
|
+
pane.show();
|
|
340
|
+
pane.close();
|
|
341
|
+
pane.toggle();
|
|
342
|
+
pane.open = true;
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Slots
|
|
346
|
+
|
|
347
|
+
`<u-scaffold>`:
|
|
348
|
+
|
|
349
|
+
| Slot | Goes into |
|
|
350
|
+
| --- | --- |
|
|
351
|
+
| `top-bar` | A `<u-top-app-bar>` (auto-positioned absolute). |
|
|
352
|
+
| default | Panes (`<u-pane>`) and the page content as siblings. Non-pane children flex to fill the remaining space. |
|
|
353
|
+
| `bottom-bar` | A `<u-navigation-bar>` (auto-positioned absolute). |
|
|
354
|
+
| `fab` | A `<u-fab>` or `<u-fab-menu>` — anchored above the bottom bar. |
|
|
355
|
+
|
|
356
|
+
`<u-pane>`:
|
|
357
|
+
|
|
358
|
+
| Slot | Goes into |
|
|
359
|
+
| --- | --- |
|
|
360
|
+
| `header` | Sticks to the top of the pane. |
|
|
361
|
+
| default | The pane content (scrolls inside the pane's `.content` part). |
|
|
362
|
+
|
|
363
|
+
## Parts
|
|
364
|
+
|
|
365
|
+
Scaffold: `top-bar`, `bottom-bar`, `fab`, `body-row`.
|
|
366
|
+
|
|
367
|
+
Pane: `container`, `header`, `content`, `scrim`.
|
|
368
|
+
|
|
369
|
+
## CSS custom properties
|
|
370
|
+
|
|
371
|
+
Scaffold:
|
|
372
|
+
- `--u-pane-gap` (default `16px`), `--u-pane-padding` (default `0` below md, `16px` at md+) — spacing in the body row.
|
|
373
|
+
- `--u-scaffold-fab-inline-offset`, `--u-scaffold-fab-block-offset` (defaults `16px`).
|
|
374
|
+
- `--u-scaffold-container-color` — scaffold background.
|
|
375
|
+
|
|
376
|
+
Pane (set on the pane host):
|
|
377
|
+
- `--u-pane-filled-bg-color`, `--u-pane-filled-shape-corner` — in-flow filled variant.
|
|
378
|
+
- `--u-pane-overlay-bg-color`, `--u-pane-overlay-corner-shape` — sidebar/fullscreen overlay.
|
|
379
|
+
- `--u-pane-mobile-width` (default `360px`) — sidebar drawer width.
|
|
380
|
+
- `--u-pane-scrim-color`, `--u-pane-scrim-opacity` (default `.4`).
|
|
381
|
+
- `--u-pane-z-index` (default `1030`, sidebar), `--u-pane-fullscreen-z-index` (default `1040`).
|
|
382
|
+
- `--u-pane-transition` (default `300ms` ease).
|
|
383
|
+
|
|
384
|
+
## Caveats
|
|
385
|
+
|
|
386
|
+
- The scaffold must have an explicit height (`100vh`, fixed px, or flex parent). Without it the body row collapses to 0.
|
|
387
|
+
- Non-pane body content does not get an automatic scroll container — apply `overflow: auto` yourself if you want the column to scroll independently of the page.
|
|
388
|
+
- The top app bar / FAB default to `window` scroll. To make them react to in-scaffold scroll, set `scrollContainer="..."` to the id of a scrolling element or pass an `HTMLElement` directly to the property.
|
|
389
|
+
- `<u-pane mode="sidebar">` slides from the leading edge by default. Place the pane *after* your body content in DOM order to slide from the trailing edge — the scaffold writes `data-align="end"` on it automatically.
|
|
390
|
+
- `query-context="container"` requires the scaffold ancestor (which sets `container-type`). If you use a pane outside a `<u-scaffold>`, container queries won't resolve.
|
|
391
|
+
- The standalone `u-side-navigation` is `@deprecated` — use a `<u-pane>` with a slotted `<u-drawer>` / `<u-navigation-rail>` instead.
|
|
392
|
+
- **A `<u-drawer>` slotted into a `filled` pane paints over the pane's own background.** The drawer renders its own surface, and at `lg`+ that surface becomes `surface`/body (the "standard drawer" treatment for a permanent side-nav) — identical to the scaffold background. So a filled nav pane shows its background at small/medium widths and then *loses it on desktop*, exactly where the drawer flips to body color. The pane background is there; the drawer is painting on top. Fix at the drawer, not the pane: neutralize the drawer's surface so the pane shows through — `--u-modal-drawer-bg-color: transparent; --u-standard-drawer-bg-color: transparent;` (set on the pane; they inherit to the drawer). See the drawer skill for detail.
|