@luz-ds/app 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024–2026 Fernando Muñoz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,91 @@
1
- # Temporary Holding Version
1
+ # @luz-ds/app
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The header and the dashboard of a web application: the bar at the top of the main area (the menu button, the product's name on a phone, the search and the account) and the view's grid of panels. Each is a class on a native element, laid out in the markup by `@luz-ds/core`'s objects (`.o-shell`, `.o-main`, `.o-sticky`, `.o-row`, `.o-actions`, `.o-grid`) and its sidebar, dropdown, avatar and card; app adds only its skin and its rhythm. `@luz-ds/site` is optional: its search goes in the header's actions.
4
+
5
+ > **Beta, version 0.x.** App has a version of its own, apart from tokens, core and site, and grows in minor versions while it is 0.x: any of them may change a class. Pin an exact version and read the changelog before you upgrade.
6
+
7
+ ## When to use it
8
+
9
+ - You build a tool that people sign in to, with a sidebar of sections and a view for each.
10
+ - You want a quiet bar for the global tools, and a dashboard of panels for the view's state.
11
+ - Not for a landing page (`@luz-ds/landing`), a blog (`@luz-ds/cms`) or documentation.
12
+
13
+ There is no sidebar of its own: core's `.luz-sidebar` is a column from 769 px and a drawer at 768 px and below, opened by the menu button with no script. An icon rail and a collapsible sidebar are not in 0.1. The app page, an app with no sidebar and an empty dashboard need no CSS of their own: they are markup to copy, on the [App templates](https://nandovejer.github.io/luz/docs/app/templates/) page of the docs.
14
+
15
+ ## Prerequisites
16
+
17
+ Requires `@luz-ds/tokens` and `@luz-ds/core`, version `^0.1.0`, declared as peer dependencies, loaded in that order before this package. `@luz-ds/site` (`^0.1.0`) is an optional peer: load it before this package if you use its search; the patterns work without it.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @luz-ds/tokens @luz-ds/core @luz-ds/site @luz-ds/app
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Via npm/bundler
28
+
29
+ ```js
30
+ import '@luz-ds/tokens/css'
31
+ import '@luz-ds/core/css'
32
+ import '@luz-ds/site/css'
33
+ import '@luz-ds/app/css'
34
+ ```
35
+
36
+ ### Via CDN
37
+
38
+ ```html
39
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/tokens@0.1.0/dist/luz-tokens.min.css">
40
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/core@0.1.0/dist/luz.min.css">
41
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/site@0.1.0/dist/site.min.css">
42
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/app@0.1.0/dist/app.min.css">
43
+ ```
44
+
45
+ There is no script: the drawer and the account list are native popovers. Core's script is optional: with it, the sidebar marks the current link and closes the drawer when a link is followed.
46
+
47
+ The view's `h1` and its main action are not in the bar: they are the first row of the dashboard (`__head`), so the bar holds only what is the same in every view.
48
+
49
+ ## Package exports
50
+
51
+ - **`./css`** → `app.css` — the patterns, in the cascade layer `luz.app`, after `luz.cms` (`./css/min` for the minified twin)
52
+ - **`./components/*`** → `dist/components/` — one stylesheet per pattern, `<name>.css` and `<name>.min.css`, each in `luz.app`, for a page that loads only what it uses
53
+
54
+ ## Classes included
55
+
56
+ - `.luz-app-header` (`__toggle`, `__product`, `__actions`, `__account`, `__account-name`) — The bar at the top of `.o-main`, one row of core's `--luz-bar-h`, kept at the top with `.o-sticky`: the menu button, the product's name while the sidebar is a drawer, the search and the account. [Docs](https://nandovejer.github.io/luz/docs/app/header/)
57
+ - `.luz-app-dashboard` (`__head`, `__title`, `__panel`, `__panel--wide`) — The view: its title row, then a grid of panels (core's `.o-grid--auto` and `.luz-card`) as many columns as fit, none narrower than 18 rem; a wide panel takes two columns when the view is 40 rem wide. [Docs](https://nandovejer.github.io/luz/docs/app/dashboard/)
58
+
59
+ <!-- luz:ai-agents:start -->
60
+ ## For AI agents
61
+
62
+ `@luz-ds/app` 0.1.0 is in beta: any release may break anything. Read the breaking changes below before you update, and use only what the files under "Read first" list.
63
+
64
+ ### Breaking changes
65
+
66
+ First release of `@luz-ds/app`. Nothing breaks inside @luz-ds. Coming from `@cai-ds/*`: see the migration note in `CHANGELOG.md`.
67
+
68
+ Older versions: https://github.com/nandovejer/luz/blob/main/packages/app/CHANGELOG.md
69
+
70
+ ### Read first
71
+
72
+ 1. `dist/classes.json` in this package: the classes and custom properties its CSS defines. Write only those.
73
+ 2. https://nandovejer.github.io/luz/llms.txt: the index of the documentation, short enough for a small context.
74
+ 3. https://nandovejer.github.io/luz/llms-full.txt: the same pages in full.
75
+ 4. https://nandovejer.github.io/luz/examples/: one standalone page of markup for each component and pattern.
76
+
77
+ ### Load order
78
+
79
+ This package needs `@luz-ds/tokens` and `@luz-ds/core` loaded first.
80
+
81
+ `@luz-ds/site` is optional: the CSS of this package works with the packages above alone.
82
+
83
+ ### Check what you write
84
+
85
+ Run `npx @luz-ds/check <files>` on every HTML and CSS file you write, and fix what it reports. To copy the accessibility reviewer into your project: `npx @luz-ds/check agent > .claude/agents/luz-accessibility-reviewer.md`.
86
+
87
+ <!-- luz:ai-agents:end -->
88
+
89
+ ## License
90
+
91
+ MIT
package/dist/app.css ADDED
@@ -0,0 +1,174 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.site, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.app {
3
+ /* ==========================================================================
4
+ LUZ APP
5
+ The header and the dashboard of a web application. Each is a class on a
6
+ native element, laid out in the markup by core's objects (.o-shell and
7
+ .o-main with the sidebar, .o-sticky, .o-row, .o-actions, .o-grid) and
8
+ core's components (sidebar, nav toggle, dropdown, avatar, card, button).
9
+ App adds only its skin and its rhythm. No JavaScript.
10
+ Requires: @luz-ds/tokens and @luz-ds/core loaded first; @luz-ds/site
11
+ optional, loaded before this file.
12
+ ========================================================================== */
13
+
14
+ /* ==========================================================================
15
+ LUZ APP: header.
16
+ The bar at the top of the main area: header.luz-app-header.o-sticky >
17
+ div.o-row, inside .o-main beside the sidebar. It holds core's
18
+ .luz-nav-toggle (it opens the sidebar drawer), the __product name, and
19
+ div.o-row.__actions: site's <luz-search> if it is loaded, then a
20
+ .luz-dropdown whose button is the __account (initials in a .luz-avatar,
21
+ then the __account-name). It is the chrome only: the view's h1 and its
22
+ primary action are in the dashboard's title row.
23
+ Core gives the stickiness, the row and the drawer; app adds the surface,
24
+ the divider and the rhythm.
25
+ ========================================================================== */
26
+
27
+ /* A one-row bar of 44px controls is core's --luz-bar-h tall: two 2xs
28
+ paddings, the row and the divider. With enlarged text the row wraps
29
+ and the bar grows. */
30
+ .luz-app-header {
31
+ padding-block: var(--luz-space-2xs);
32
+ border-block-end: 1px solid var(--luz-divider);
33
+ background: var(--luz-surface-page);
34
+ }
35
+
36
+ /* The gutters are the content's (core's .o-container: md, xl from 641px),
37
+ so the toggle starts where the view starts */
38
+ .luz-app-header > .o-row {
39
+ gap: var(--luz-space-xs) var(--luz-space-md);
40
+ padding-inline: var(--luz-space-xl);
41
+ }
42
+
43
+ /* The global tools sit together at the inline end */
44
+ .luz-app-header__actions {
45
+ gap: var(--luz-space-xs);
46
+ margin-inline-start: auto;
47
+ }
48
+
49
+ /* The product's name, only while the sidebar is a drawer and its brand is
50
+ out of sight (so it is never twice in the tree). Without the popover API
51
+ the sidebar stays in the flow with its brand, and so does this stay out. */
52
+ .luz-app-header__product {
53
+ display: none;
54
+ margin: 0;
55
+ color: var(--luz-text-primary);
56
+ font-size: var(--luz-text-md);
57
+ font-weight: 600;
58
+ }
59
+
60
+ /* The account's button: the initials, then the name, in a 44px target */
61
+ .luz-app-header__account {
62
+ min-inline-size: var(--luz-control-lg);
63
+ padding-inline: var(--luz-space-xs);
64
+ }
65
+
66
+ /* The page is the scroller, so the sticky bar sticks to the window; if an
67
+ .o-main of yours scrolls on its own, what it focuses stays clear of the bar */
68
+ :root:has(.luz-app-header) .o-main {
69
+ scroll-padding-block-start: var(--luz-bar-h);
70
+ }
71
+
72
+ /* The account list opens at the end of its button, so it stays in the
73
+ window at the inline end of the bar (core's panel opens at the start) */
74
+ @supports selector(:popover-open) and (inset-block-start: anchor(end)) {
75
+ .luz-app-header .luz-dropdown__menu:popover-open {
76
+ inset-inline: auto anchor(end);
77
+ }
78
+ }
79
+
80
+ @supports selector(:popover-open) {
81
+ @media (max-width: 768px) {
82
+ .luz-app-header__product {
83
+ display: block;
84
+ }
85
+ }
86
+ }
87
+
88
+ /* At 640px and below the name is only in the button's name: clipped to
89
+ nothing, never display: none, as the search label is */
90
+ @media (max-width: 640px) {
91
+ .luz-app-header > .o-row {
92
+ padding-inline: var(--luz-space-md);
93
+ }
94
+
95
+ .luz-app-header__account-name {
96
+ position: absolute;
97
+ inline-size: 1px;
98
+ block-size: 1px;
99
+ overflow: hidden;
100
+ clip-path: inset(50%);
101
+ white-space: nowrap;
102
+ }
103
+ }
104
+
105
+ /* ==========================================================================
106
+ LUZ APP: dashboard.
107
+ main.luz-app-dashboard: the view. It holds a __head (a div with the
108
+ view's h1, the __title, and core's .o-actions) and a ul.o-grid.o-grid--auto
109
+ of li.luz-card.__panel, each starting with its h2. Core gives the grid
110
+ and the card; app sets the room, the padding, the column width and the
111
+ wide panel. The panels keep the order of the markup: no dense packing,
112
+ no order.
113
+ ========================================================================== */
114
+
115
+ /* The container of its own size query: a panel answers to the room the
116
+ view has, with or without a sidebar, not to the window's. 18rem is the
117
+ narrowest column of the grid. */
118
+ .luz-app-dashboard {
119
+ --luz-grid-min: 18rem;
120
+
121
+ display: grid;
122
+ align-content: start;
123
+ gap: var(--luz-space-lg);
124
+ padding: var(--luz-space-xl);
125
+ container-type: inline-size;
126
+ }
127
+
128
+ /* The view's name and what to do first, in one row that wraps */
129
+ .luz-app-dashboard__head {
130
+ display: flex;
131
+ flex-wrap: wrap;
132
+ align-items: center;
133
+ justify-content: space-between;
134
+ gap: var(--luz-space-sm) var(--luz-space-md);
135
+ }
136
+
137
+ .luz-app-dashboard__title {
138
+ margin: 0;
139
+ color: var(--luz-text-primary);
140
+ font-size: var(--luz-text-2xl);
141
+ font-weight: 600;
142
+ }
143
+
144
+ /* A wide child never widens its column: it scrolls inside the panel */
145
+ .luz-app-dashboard__panel {
146
+ min-inline-size: 0;
147
+ }
148
+
149
+ /* Two columns always exist from 40rem (2 x 18rem and a gap), so it never
150
+ asks for a column that is not there */
151
+ @container (min-width: 40rem) {
152
+ .luz-app-dashboard__panel--wide {
153
+ grid-column: span 2;
154
+ }
155
+ }
156
+
157
+ @media (max-width: 640px) {
158
+ .luz-app-dashboard {
159
+ padding: var(--luz-space-md);
160
+ }
161
+
162
+ .luz-app-dashboard__title {
163
+ font-size: var(--luz-text-xl);
164
+ }
165
+
166
+ /* Core's actions are a column here: below the title, not beside it */
167
+ .luz-app-dashboard__head {
168
+ flex-flow: column nowrap;
169
+ align-items: stretch;
170
+ }
171
+ }
172
+
173
+
174
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.site,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.app{.luz-app-header{padding-block:var(--luz-space-2xs);border-block-end:1px solid var(--luz-divider);background:var(--luz-surface-page)}.luz-app-header>.o-row{gap:var(--luz-space-xs) var(--luz-space-md);padding-inline:var(--luz-space-xl)}.luz-app-header__actions{gap:var(--luz-space-xs);margin-inline-start:auto}.luz-app-header__product{display:none;margin:0;color:var(--luz-text-primary);font-size:var(--luz-text-md);font-weight:600}.luz-app-header__account{min-inline-size:var(--luz-control-lg);padding-inline:var(--luz-space-xs)}:root:has(.luz-app-header) .o-main{scroll-padding-block-start:var(--luz-bar-h)}@supports selector(:popover-open) and (inset-block-start: anchor(end)){.luz-app-header .luz-dropdown__menu:popover-open{inset-inline:auto anchor(end)}}@supports selector(:popover-open){@media(max-width:768px){.luz-app-header__product{display:block}}}@media(max-width:640px){.luz-app-header>.o-row{padding-inline:var(--luz-space-md)}.luz-app-header__account-name{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}.luz-app-dashboard{--luz-grid-min: 18rem;display:grid;align-content:start;gap:var(--luz-space-lg);padding:var(--luz-space-xl);container-type:inline-size}.luz-app-dashboard__head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--luz-space-sm) var(--luz-space-md)}.luz-app-dashboard__title{margin:0;color:var(--luz-text-primary);font-size:var(--luz-text-2xl);font-weight:600}.luz-app-dashboard__panel{min-inline-size:0}@container (min-width: 40rem){.luz-app-dashboard__panel--wide{grid-column:span 2}}@media(max-width:640px){.luz-app-dashboard{padding:var(--luz-space-md)}.luz-app-dashboard__title{font-size:var(--luz-text-xl)}.luz-app-dashboard__head{flex-flow:column nowrap;align-items:stretch}}}
@@ -0,0 +1,23 @@
1
+ {
2
+ "schema": 1,
3
+ "package": "@luz-ds/app",
4
+ "version": "0.1.0",
5
+ "classes": [
6
+ { "name": "luz-app-dashboard", "kind": "pattern", "layer": "luz.app", "files": ["components/dashboard.css"], "defined": true, "block": "luz-app-dashboard" },
7
+ { "name": "luz-app-dashboard__head", "kind": "pattern", "layer": "luz.app", "files": ["components/dashboard.css"], "defined": true, "block": "luz-app-dashboard", "part": "head" },
8
+ { "name": "luz-app-dashboard__panel", "kind": "pattern", "layer": "luz.app", "files": ["components/dashboard.css"], "defined": true, "block": "luz-app-dashboard", "part": "panel" },
9
+ { "name": "luz-app-dashboard__panel--wide", "kind": "pattern", "layer": "luz.app", "files": ["components/dashboard.css"], "defined": true, "block": "luz-app-dashboard", "modifier": "wide", "part": "panel" },
10
+ { "name": "luz-app-dashboard__title", "kind": "pattern", "layer": "luz.app", "files": ["components/dashboard.css"], "defined": true, "block": "luz-app-dashboard", "part": "title" },
11
+ { "name": "luz-app-header", "kind": "pattern", "layer": "luz.app", "files": ["components/header.css"], "defined": true, "block": "luz-app-header" },
12
+ { "name": "luz-app-header__account", "kind": "pattern", "layer": "luz.app", "files": ["components/header.css"], "defined": true, "block": "luz-app-header", "part": "account" },
13
+ { "name": "luz-app-header__account-name", "kind": "pattern", "layer": "luz.app", "files": ["components/header.css"], "defined": true, "block": "luz-app-header", "part": "account-name" },
14
+ { "name": "luz-app-header__actions", "kind": "pattern", "layer": "luz.app", "files": ["components/header.css"], "defined": true, "block": "luz-app-header", "part": "actions" },
15
+ { "name": "luz-app-header__product", "kind": "pattern", "layer": "luz.app", "files": ["components/header.css"], "defined": true, "block": "luz-app-header", "part": "product" },
16
+ { "name": "luz-dropdown__menu", "kind": "component", "layer": "luz.app", "files": ["components/header.css"], "defined": false, "block": "luz-dropdown", "part": "menu" },
17
+ { "name": "o-main", "kind": "object", "layer": "luz.app", "files": ["components/header.css"], "defined": false, "block": "o-main" },
18
+ { "name": "o-row", "kind": "object", "layer": "luz.app", "files": ["components/header.css"], "defined": false, "block": "o-row" }
19
+ ],
20
+ "properties": [
21
+ { "name": "--luz-grid-min", "scope": "component", "layer": "luz.app", "files": ["components/dashboard.css"] }
22
+ ]
23
+ }
@@ -0,0 +1,71 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.site, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.app {
3
+ /* ==========================================================================
4
+ LUZ APP: dashboard.
5
+ main.luz-app-dashboard: the view. It holds a __head (a div with the
6
+ view's h1, the __title, and core's .o-actions) and a ul.o-grid.o-grid--auto
7
+ of li.luz-card.__panel, each starting with its h2. Core gives the grid
8
+ and the card; app sets the room, the padding, the column width and the
9
+ wide panel. The panels keep the order of the markup: no dense packing,
10
+ no order.
11
+ ========================================================================== */
12
+
13
+ /* The container of its own size query: a panel answers to the room the
14
+ view has, with or without a sidebar, not to the window's. 18rem is the
15
+ narrowest column of the grid. */
16
+ .luz-app-dashboard {
17
+ --luz-grid-min: 18rem;
18
+
19
+ display: grid;
20
+ align-content: start;
21
+ gap: var(--luz-space-lg);
22
+ padding: var(--luz-space-xl);
23
+ container-type: inline-size;
24
+ }
25
+
26
+ /* The view's name and what to do first, in one row that wraps */
27
+ .luz-app-dashboard__head {
28
+ display: flex;
29
+ flex-wrap: wrap;
30
+ align-items: center;
31
+ justify-content: space-between;
32
+ gap: var(--luz-space-sm) var(--luz-space-md);
33
+ }
34
+
35
+ .luz-app-dashboard__title {
36
+ margin: 0;
37
+ color: var(--luz-text-primary);
38
+ font-size: var(--luz-text-2xl);
39
+ font-weight: 600;
40
+ }
41
+
42
+ /* A wide child never widens its column: it scrolls inside the panel */
43
+ .luz-app-dashboard__panel {
44
+ min-inline-size: 0;
45
+ }
46
+
47
+ /* Two columns always exist from 40rem (2 x 18rem and a gap), so it never
48
+ asks for a column that is not there */
49
+ @container (min-width: 40rem) {
50
+ .luz-app-dashboard__panel--wide {
51
+ grid-column: span 2;
52
+ }
53
+ }
54
+
55
+ @media (max-width: 640px) {
56
+ .luz-app-dashboard {
57
+ padding: var(--luz-space-md);
58
+ }
59
+
60
+ .luz-app-dashboard__title {
61
+ font-size: var(--luz-text-xl);
62
+ }
63
+
64
+ /* Core's actions are a column here: below the title, not beside it */
65
+ .luz-app-dashboard__head {
66
+ flex-flow: column nowrap;
67
+ align-items: stretch;
68
+ }
69
+ }
70
+
71
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.site,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.app{.luz-app-dashboard{--luz-grid-min: 18rem;display:grid;align-content:start;gap:var(--luz-space-lg);padding:var(--luz-space-xl);container-type:inline-size}.luz-app-dashboard__head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--luz-space-sm) var(--luz-space-md)}.luz-app-dashboard__title{margin:0;color:var(--luz-text-primary);font-size:var(--luz-text-2xl);font-weight:600}.luz-app-dashboard__panel{min-inline-size:0}@container (min-width: 40rem){.luz-app-dashboard__panel--wide{grid-column:span 2}}@media(max-width:640px){.luz-app-dashboard{padding:var(--luz-space-md)}.luz-app-dashboard__title{font-size:var(--luz-text-xl)}.luz-app-dashboard__head{flex-flow:column nowrap;align-items:stretch}}}
@@ -0,0 +1,94 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.site, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.app {
3
+ /* ==========================================================================
4
+ LUZ APP: header.
5
+ The bar at the top of the main area: header.luz-app-header.o-sticky >
6
+ div.o-row, inside .o-main beside the sidebar. It holds core's
7
+ .luz-nav-toggle (it opens the sidebar drawer), the __product name, and
8
+ div.o-row.__actions: site's <luz-search> if it is loaded, then a
9
+ .luz-dropdown whose button is the __account (initials in a .luz-avatar,
10
+ then the __account-name). It is the chrome only: the view's h1 and its
11
+ primary action are in the dashboard's title row.
12
+ Core gives the stickiness, the row and the drawer; app adds the surface,
13
+ the divider and the rhythm.
14
+ ========================================================================== */
15
+
16
+ /* A one-row bar of 44px controls is core's --luz-bar-h tall: two 2xs
17
+ paddings, the row and the divider. With enlarged text the row wraps
18
+ and the bar grows. */
19
+ .luz-app-header {
20
+ padding-block: var(--luz-space-2xs);
21
+ border-block-end: 1px solid var(--luz-divider);
22
+ background: var(--luz-surface-page);
23
+ }
24
+
25
+ /* The gutters are the content's (core's .o-container: md, xl from 641px),
26
+ so the toggle starts where the view starts */
27
+ .luz-app-header > .o-row {
28
+ gap: var(--luz-space-xs) var(--luz-space-md);
29
+ padding-inline: var(--luz-space-xl);
30
+ }
31
+
32
+ /* The global tools sit together at the inline end */
33
+ .luz-app-header__actions {
34
+ gap: var(--luz-space-xs);
35
+ margin-inline-start: auto;
36
+ }
37
+
38
+ /* The product's name, only while the sidebar is a drawer and its brand is
39
+ out of sight (so it is never twice in the tree). Without the popover API
40
+ the sidebar stays in the flow with its brand, and so does this stay out. */
41
+ .luz-app-header__product {
42
+ display: none;
43
+ margin: 0;
44
+ color: var(--luz-text-primary);
45
+ font-size: var(--luz-text-md);
46
+ font-weight: 600;
47
+ }
48
+
49
+ /* The account's button: the initials, then the name, in a 44px target */
50
+ .luz-app-header__account {
51
+ min-inline-size: var(--luz-control-lg);
52
+ padding-inline: var(--luz-space-xs);
53
+ }
54
+
55
+ /* The page is the scroller, so the sticky bar sticks to the window; if an
56
+ .o-main of yours scrolls on its own, what it focuses stays clear of the bar */
57
+ :root:has(.luz-app-header) .o-main {
58
+ scroll-padding-block-start: var(--luz-bar-h);
59
+ }
60
+
61
+ /* The account list opens at the end of its button, so it stays in the
62
+ window at the inline end of the bar (core's panel opens at the start) */
63
+ @supports selector(:popover-open) and (inset-block-start: anchor(end)) {
64
+ .luz-app-header .luz-dropdown__menu:popover-open {
65
+ inset-inline: auto anchor(end);
66
+ }
67
+ }
68
+
69
+ @supports selector(:popover-open) {
70
+ @media (max-width: 768px) {
71
+ .luz-app-header__product {
72
+ display: block;
73
+ }
74
+ }
75
+ }
76
+
77
+ /* At 640px and below the name is only in the button's name: clipped to
78
+ nothing, never display: none, as the search label is */
79
+ @media (max-width: 640px) {
80
+ .luz-app-header > .o-row {
81
+ padding-inline: var(--luz-space-md);
82
+ }
83
+
84
+ .luz-app-header__account-name {
85
+ position: absolute;
86
+ inline-size: 1px;
87
+ block-size: 1px;
88
+ overflow: hidden;
89
+ clip-path: inset(50%);
90
+ white-space: nowrap;
91
+ }
92
+ }
93
+
94
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.site,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.app{.luz-app-header{padding-block:var(--luz-space-2xs);border-block-end:1px solid var(--luz-divider);background:var(--luz-surface-page)}.luz-app-header>.o-row{gap:var(--luz-space-xs) var(--luz-space-md);padding-inline:var(--luz-space-xl)}.luz-app-header__actions{gap:var(--luz-space-xs);margin-inline-start:auto}.luz-app-header__product{display:none;margin:0;color:var(--luz-text-primary);font-size:var(--luz-text-md);font-weight:600}.luz-app-header__account{min-inline-size:var(--luz-control-lg);padding-inline:var(--luz-space-xs)}:root:has(.luz-app-header) .o-main{scroll-padding-block-start:var(--luz-bar-h)}@supports selector(:popover-open) and (inset-block-start: anchor(end)){.luz-app-header .luz-dropdown__menu:popover-open{inset-inline:auto anchor(end)}}@supports selector(:popover-open){@media(max-width:768px){.luz-app-header__product{display:block}}}@media(max-width:640px){.luz-app-header>.o-row{padding-inline:var(--luz-space-md)}.luz-app-header__account-name{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}}
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@luz-ds/app",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "LUZ Design System — the header and the dashboard of a web application (a bar with search and the account, a grid of panels), built on @luz-ds/core",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ "./css": "./dist/app.css",
9
+ "./css/min": "./dist/app.min.css",
10
+ "./components/*": "./dist/components/*",
11
+ "./classes.json": "./dist/classes.json",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "LICENSE"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/nandovejer/luz.git",
21
+ "directory": "packages/app"
22
+ },
23
+ "homepage": "https://github.com/nandovejer/luz#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/nandovejer/luz/issues"
26
+ },
27
+ "keywords": [
28
+ "design-system",
29
+ "css",
30
+ "app",
31
+ "dashboard",
32
+ "patterns",
33
+ "vanilla"
34
+ ],
35
+ "sideEffects": [
36
+ "*.css"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "peerDependencies": {
42
+ "@luz-ds/core": "^0.1.0",
43
+ "@luz-ds/site": "^0.1.0",
44
+ "@luz-ds/tokens": "^0.1.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "@luz-ds/site": {
48
+ "optional": true
49
+ }
50
+ },
51
+ "devDependencies": {
52
+ "@luz-ds/core": "0.1.0",
53
+ "@luz-ds/tokens": "0.1.0",
54
+ "@luz-ds/site": "0.1.0"
55
+ }
6
56
  }