@egose/shadcn-theme-ng 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/accordion/README.md +405 -2
- package/alert/README.md +372 -2
- package/alert-dialog/README.md +471 -5
- package/aspect-ratio/README.md +272 -5
- package/autocomplete/README.md +502 -2
- package/avatar/README.md +357 -5
- package/badge/README.md +318 -2
- package/basic-alert/README.md +353 -2
- package/breadcrumb/README.md +406 -5
- package/button/README.md +482 -2
- package/button/fesm2022/button.mjs +85 -107
- package/button/types/button.d.ts +5 -8
- package/button-group/README.md +318 -5
- package/calendar/README.md +357 -2
- package/card/README.md +331 -5
- package/carousel/README.md +333 -5
- package/carousel/fesm2022/carousel.mjs +4 -1
- package/checkbox/README.md +320 -2
- package/collapsible/README.md +332 -5
- package/combobox/README.md +507 -5
- package/combobox/fesm2022/combobox.mjs +4 -1
- package/command/README.md +435 -5
- package/confirmation-dialog/README.md +301 -2
- package/context-menu/README.md +366 -5
- package/date-picker/README.md +465 -2
- package/date-picker/fesm2022/date-picker.mjs +2 -2
- package/dialog/README.md +448 -2
- package/drawer/README.md +395 -5
- package/dropdown-menu/README.md +417 -5
- package/empty/README.md +329 -5
- package/field/README.md +385 -5
- package/form-checkbox/README.md +312 -2
- package/form-date-picker/README.md +322 -2
- package/form-field/README.md +356 -2
- package/form-field-simple/README.md +340 -2
- package/form-searchable-multiselect/README.md +361 -2
- package/form-select/README.md +350 -2
- package/form-text-input/README.md +371 -2
- package/form-textarea/README.md +347 -2
- package/hover-card/README.md +256 -5
- package/icon/README.md +239 -2
- package/input/README.md +269 -2
- package/input-group/README.md +335 -5
- package/input-group/fesm2022/input-group.mjs +3 -3
- package/input-otp/README.md +375 -5
- package/item/README.md +385 -5
- package/item/fesm2022/item.mjs +3 -3
- package/kbd/README.md +291 -5
- package/label/README.md +272 -2
- package/layout-simple/README.md +193 -2
- package/layout-simple/fesm2022/layout-simple.mjs +472 -236
- package/layout-simple/types/layout-simple.d.ts +174 -137
- package/menu/README.md +417 -2
- package/menubar/README.md +343 -5
- package/native-select/README.md +323 -5
- package/navigation-menu/README.md +369 -5
- package/package.json +1 -1
- package/pagination/README.md +388 -5
- package/popover/README.md +331 -2
- package/progress/README.md +311 -5
- package/radio-group/README.md +364 -2
- package/radio-group/fesm2022/radio-group.mjs +5 -1
- package/radio-group/types/radio-group.d.ts +1 -1
- package/resizable/README.md +269 -5
- package/scroll-area/README.md +233 -5
- package/searchable-multiselect/README.md +323 -2
- package/select/README.md +437 -2
- package/separator/README.md +222 -2
- package/sheet/README.md +311 -2
- package/sidebar/README.md +457 -5
- package/skeleton/README.md +217 -5
- package/slider/README.md +273 -5
- package/slider/fesm2022/slider.mjs +3 -3
- package/sonner/README.md +346 -2
- package/spinner/README.md +284 -2
- package/switch/README.md +310 -2
- package/table/README.md +423 -5
- package/tabs/README.md +411 -2
- package/tabs/fesm2022/tabs.mjs +2 -2
- package/textarea/README.md +282 -5
- package/toggle/README.md +270 -5
- package/toggle-group/README.md +340 -5
- package/tooltip/README.md +269 -2
- package/typography/README.md +271 -5
- package/typography/types/typography.d.ts +8 -8
- package/utils/README.md +303 -2
package/popover/README.md
CHANGED
|
@@ -1,3 +1,332 @@
|
|
|
1
|
-
# Popover
|
|
1
|
+
# Popover (`@egose/shadcn-theme-ng/popover`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Floating panel anchored to a button (shadcn/ui `popover` equivalent). Thin shadcn styling directives over the spartan-ng `BrnPopover` brain family: a stateful `hlm-popover` root, a `button[hlmPopoverTrigger]` opener, portal/content pieces for the floating panel, and header/title/description typography helpers.
|
|
4
|
+
|
|
5
|
+
Ships as `@egose/shadcn-theme-ng/popover` and `@egose/shadcn-theme-ng-tw/popover` (tw: variant). See the [package README](../../README.md) for installation, peer dependencies, Tailwind setup, and testing. Do not publish this project directory independently.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Plain Tailwind (no prefix):
|
|
11
|
+
npm install @egose/shadcn-theme-ng
|
|
12
|
+
|
|
13
|
+
# Or the tw:-prefixed variant:
|
|
14
|
+
npm install @egose/shadcn-theme-ng-tw
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Peer dependencies are inherited from the package root (see [package README](../../README.md)). This subpath itself declares `@angular/common`, `@angular/core` as peers plus a `tslib` runtime dependency; at runtime it uses `@spartan-ng/brain/popover` and `@spartan-ng/brain/core`. No extra install step is needed beyond the package install above.
|
|
18
|
+
|
|
19
|
+
## Imports
|
|
20
|
+
|
|
21
|
+
Real exported symbols (from `src/public-api.ts`):
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
HlmPopover, // directive: [hlmPopover],hlm-popover
|
|
26
|
+
HlmPopoverTrigger, // directive: button[hlmPopoverTrigger],button[hlmPopoverTriggerFor]
|
|
27
|
+
HlmPopoverPortal, // directive: [hlmPopoverPortal]
|
|
28
|
+
HlmPopoverContent, // directive: [hlmPopoverContent],hlm-popover-content
|
|
29
|
+
HlmPopoverHeader, // directive: [hlmPopoverHeader],hlm-popover-header
|
|
30
|
+
HlmPopoverTitle, // directive: [hlmPopoverTitle]
|
|
31
|
+
HlmPopoverDescription, // directive: [hlmPopoverDescription]
|
|
32
|
+
HlmPopoverImports, // all seven above
|
|
33
|
+
HlmPopoverModule, // NgModule wrapping HlmPopoverImports
|
|
34
|
+
} from '@egose/shadcn-theme-ng/popover';
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Standalone usage:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { Component } from '@angular/core';
|
|
41
|
+
import { HlmPopoverImports } from '@egose/shadcn-theme-ng/popover';
|
|
42
|
+
|
|
43
|
+
@Component({
|
|
44
|
+
selector: 'app-demo',
|
|
45
|
+
standalone: true,
|
|
46
|
+
imports: [...HlmPopoverImports],
|
|
47
|
+
template: `
|
|
48
|
+
<hlm-popover>
|
|
49
|
+
<button hlmPopoverTrigger type="button">Open</button>
|
|
50
|
+
<hlm-popover-content *hlmPopoverPortal>
|
|
51
|
+
<p>Hello</p>
|
|
52
|
+
</hlm-popover-content>
|
|
53
|
+
</hlm-popover>
|
|
54
|
+
`,
|
|
55
|
+
})
|
|
56
|
+
export class DemoComponent {}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
NgModule usage:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { NgModule } from '@angular/core';
|
|
63
|
+
import { HlmPopoverModule } from '@egose/shadcn-theme-ng/popover';
|
|
64
|
+
|
|
65
|
+
@NgModule({ imports: [HlmPopoverModule] })
|
|
66
|
+
export class DemoModule {}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For the `tw:` build, swap the specifier to `@egose/shadcn-theme-ng-tw/popover`. Symbol names are identical.
|
|
70
|
+
|
|
71
|
+
## Anatomy / Structure
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<hlm-popover>
|
|
75
|
+
<!-- opener (must be a <button>) -->
|
|
76
|
+
<button hlmPopoverTrigger type="button">Settings</button>
|
|
77
|
+
|
|
78
|
+
<!-- floating panel: content styling + portal projection -->
|
|
79
|
+
<hlm-popover-content *hlmPopoverPortal class="tw:w-80">
|
|
80
|
+
<div hlmPopoverHeader>
|
|
81
|
+
<h4 hlmPopoverTitle>Dimensions</h4>
|
|
82
|
+
<p hlmPopoverDescription>Set the size of the layer.</p>
|
|
83
|
+
</div>
|
|
84
|
+
<!-- panel body -->
|
|
85
|
+
</hlm-popover-content>
|
|
86
|
+
</hlm-popover>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The two-directive split on the panel is load-bearing: `hlmPopoverContent` paints the panel (fixed `w-72`, animations, `data-state` reflection), while `*hlmPopoverPortal` (a `BrnPopoverContent` host) projects it into the overlay at the anchored position. Always use them together as `hlm-popover-content *hlmPopoverPortal`.
|
|
90
|
+
|
|
91
|
+
| Class | Selector | Role |
|
|
92
|
+
| ----------------------- | -------------------------------------------------------- | ----------------------------------------------------- |
|
|
93
|
+
| `HlmPopover` | `[hlmPopover],hlm-popover` | Stateful root (`BrnPopover` host) |
|
|
94
|
+
| `HlmPopoverTrigger` | `button[hlmPopoverTrigger],button[hlmPopoverTriggerFor]` | Opener (`BrnPopoverTrigger` host; must be `<button>`) |
|
|
95
|
+
| `HlmPopoverPortal` | `[hlmPopoverPortal]` | Overlay projection (`BrnPopoverContent` host) |
|
|
96
|
+
| `HlmPopoverContent` | `[hlmPopoverContent],hlm-popover-content` | Panel styling + `data-state` mirror |
|
|
97
|
+
| `HlmPopoverHeader` | `[hlmPopoverHeader],hlm-popover-header` | Header stack (`flex flex-col gap-1 text-sm`) |
|
|
98
|
+
| `HlmPopoverTitle` | `[hlmPopoverTitle]` | Title (`font-medium`) |
|
|
99
|
+
| `HlmPopoverDescription` | `[hlmPopoverDescription]` | Subtitle (`text-muted-foreground`) |
|
|
100
|
+
|
|
101
|
+
## API reference
|
|
102
|
+
|
|
103
|
+
| Selector | Inputs (incl. host passthroughs) | Outputs | Notes |
|
|
104
|
+
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `[hlmPopover],hlm-popover` (`HlmPopover`) | `align`, `attachTo`, `autoFocus`, `closeOnOutsidePointerEvents`, `offsetX`, `sideOffset`, `state` (all via `BrnPopover`) | `stateChanged`, `closed` (via `BrnPopover`) | No shadcn inputs of its own; `data-slot="popover"` |
|
|
106
|
+
| `button[hlmPopoverTrigger]` (`HlmPopoverTrigger`) | `id`, `hlmPopoverTriggerFor` (→ `brnPopoverTriggerFor`), `type` (all via `BrnPopoverTrigger`) | — | `data-slot="popover-trigger"`. `hlmPopoverTriggerFor` targets an explicit popover when the trigger lives outside the root |
|
|
107
|
+
| `[hlmPopoverPortal]` (`HlmPopoverPortal`) | `context`, `class` (via `BrnPopoverContent`) | — | Structural directive (`*hlmPopoverPortal`); `class` merges onto the overlay panel |
|
|
108
|
+
| `[hlmPopoverContent]` (`HlmPopoverContent`) | — | — | Exposes `state` (brain state signal, default `signal('closed')`); mirrors it to `data-state` via `Renderer2` for the `data-open:`/`data-closed:` animations |
|
|
109
|
+
| `[hlmPopoverHeader]` / `[hlmPopoverTitle]` / `[hlmPopoverDescription]` | — | — | Typography only |
|
|
110
|
+
|
|
111
|
+
Panel styling (`HlmPopoverContent`): `bg-popover text-popover-foreground rounded-md p-4 text-sm shadow-md ring-1 ring-foreground/10 relative flex w-72 flex-col gap-4 outline-none` plus open/close zoom+fade animations keyed off `data-state`/`data-side`.
|
|
112
|
+
|
|
113
|
+
## Examples
|
|
114
|
+
|
|
115
|
+
### 1. Basic popover with header
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { Component } from '@angular/core';
|
|
119
|
+
import { HlmPopoverImports } from '@egose/shadcn-theme-ng/popover';
|
|
120
|
+
import { HlmButton } from '@egose/shadcn-theme-ng/button';
|
|
121
|
+
|
|
122
|
+
@Component({
|
|
123
|
+
selector: 'app-popover-basic',
|
|
124
|
+
standalone: true,
|
|
125
|
+
imports: [...HlmPopoverImports, HlmButton],
|
|
126
|
+
template: `
|
|
127
|
+
<hlm-popover>
|
|
128
|
+
<button hlmPopoverTrigger hlmButton variant="secondary" appearance="outline" type="button">Open settings</button>
|
|
129
|
+
<hlm-popover-content *hlmPopoverPortal>
|
|
130
|
+
<div hlmPopoverHeader>
|
|
131
|
+
<h4 hlmPopoverTitle>Dimensions</h4>
|
|
132
|
+
<p hlmPopoverDescription>Set the size of the layer.</p>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="tw:grid tw:gap-2 tw:text-sm">
|
|
135
|
+
<label class="tw:grid tw:gap-1"
|
|
136
|
+
>Width <input class="tw:border tw:rounded tw:px-2 tw:py-1" value="100%"
|
|
137
|
+
/></label>
|
|
138
|
+
<label class="tw:grid tw:gap-1"
|
|
139
|
+
>Height <input class="tw:border tw:rounded tw:px-2 tw:py-1" value="24px"
|
|
140
|
+
/></label>
|
|
141
|
+
</div>
|
|
142
|
+
</hlm-popover-content>
|
|
143
|
+
</hlm-popover>
|
|
144
|
+
`,
|
|
145
|
+
})
|
|
146
|
+
export class PopoverBasicComponent {}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 2. Placement, offsets, and state events
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { Component, signal } from '@angular/core';
|
|
153
|
+
import { HlmPopoverImports } from '@egose/shadcn-theme-ng/popover';
|
|
154
|
+
|
|
155
|
+
@Component({
|
|
156
|
+
selector: 'app-popover-placement',
|
|
157
|
+
standalone: true,
|
|
158
|
+
imports: [...HlmPopoverImports],
|
|
159
|
+
template: `
|
|
160
|
+
<hlm-popover
|
|
161
|
+
align="start"
|
|
162
|
+
[sideOffset]="8"
|
|
163
|
+
[closeOnOutsidePointerEvents]="true"
|
|
164
|
+
(stateChanged)="open.set($event === 'open')"
|
|
165
|
+
(closed)="open.set(false)"
|
|
166
|
+
>
|
|
167
|
+
<button hlmPopoverTrigger type="button">Aligned start, offset 8</button>
|
|
168
|
+
<hlm-popover-content *hlmPopoverPortal>
|
|
169
|
+
<p class="tw:text-sm">Panel state: {{ open() ? 'open' : 'closed' }}.</p>
|
|
170
|
+
</hlm-popover-content>
|
|
171
|
+
</hlm-popover>
|
|
172
|
+
`,
|
|
173
|
+
})
|
|
174
|
+
export class PopoverPlacementComponent {
|
|
175
|
+
readonly open = signal(false);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`align`, `sideOffset`, `closeOnOutsidePointerEvents` are brain inputs on `HlmPopover`; `stateChanged`/`closed` are its outputs.
|
|
180
|
+
|
|
181
|
+
### 3. Form inside a popover (template-driven)
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { Component } from '@angular/core';
|
|
185
|
+
import { FormsModule } from '@angular/forms';
|
|
186
|
+
import { HlmPopoverImports } from '@egose/shadcn-theme-ng/popover';
|
|
187
|
+
import { HlmInput } from '@egose/shadcn-theme-ng/input';
|
|
188
|
+
import { HlmLabelImports } from '@egose/shadcn-theme-ng/label';
|
|
189
|
+
|
|
190
|
+
@Component({
|
|
191
|
+
selector: 'app-popover-form',
|
|
192
|
+
standalone: true,
|
|
193
|
+
imports: [FormsModule, ...HlmPopoverImports, HlmInput, ...HlmLabelImports],
|
|
194
|
+
template: `
|
|
195
|
+
<hlm-popover>
|
|
196
|
+
<button hlmPopoverTrigger type="button">Invite member</button>
|
|
197
|
+
<hlm-popover-content *hlmPopoverPortal class="tw:w-80">
|
|
198
|
+
<div hlmPopoverHeader>
|
|
199
|
+
<h4 hlmPopoverTitle>Invite</h4>
|
|
200
|
+
<p hlmPopoverDescription>They receive an email invitation.</p>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="tw:grid tw:gap-1.5">
|
|
203
|
+
<label hlmLabel for="invite-email">Email</label>
|
|
204
|
+
<input hlmInput id="invite-email" [(ngModel)]="email" type="email" placeholder="ada@example.com" />
|
|
205
|
+
<button type="button" (click)="invite()">Send invite</button>
|
|
206
|
+
</div>
|
|
207
|
+
</hlm-popover-content>
|
|
208
|
+
</hlm-popover>
|
|
209
|
+
`,
|
|
210
|
+
})
|
|
211
|
+
export class PopoverFormComponent {
|
|
212
|
+
email = '';
|
|
213
|
+
|
|
214
|
+
invite(): void {
|
|
215
|
+
console.log('invite', this.email);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 4. Custom panel width via portal `class`
|
|
221
|
+
|
|
222
|
+
The portal's `class` input lands on the overlay host; the content directive keeps its own `w-72` unless you override it on the element as well.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { Component } from '@angular/core';
|
|
226
|
+
import { HlmPopoverImports } from '@egose/shadcn-theme-ng/popover';
|
|
227
|
+
|
|
228
|
+
@Component({
|
|
229
|
+
selector: 'app-popover-wide',
|
|
230
|
+
standalone: true,
|
|
231
|
+
imports: [...HlmPopoverImports],
|
|
232
|
+
template: `
|
|
233
|
+
<hlm-popover>
|
|
234
|
+
<button hlmPopoverTrigger type="button">Wide panel</button>
|
|
235
|
+
<hlm-popover-content *hlmPopoverPortal class="tw:w-96">
|
|
236
|
+
<div hlmPopoverHeader>
|
|
237
|
+
<h4 hlmPopoverTitle>Preview</h4>
|
|
238
|
+
<p hlmPopoverDescription>A wider panel for rich content.</p>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="tw:grid tw:grid-cols-2 tw:gap-2 tw:text-sm">
|
|
241
|
+
<div class="tw:rounded tw:bg-muted tw:p-2">Column A</div>
|
|
242
|
+
<div class="tw:rounded tw:bg-muted tw:p-2">Column B</div>
|
|
243
|
+
</div>
|
|
244
|
+
</hlm-popover-content>
|
|
245
|
+
</hlm-popover>
|
|
246
|
+
`,
|
|
247
|
+
})
|
|
248
|
+
export class PopoverWideComponent {}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 5. External trigger (`hlmPopoverTriggerFor`)
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { Component } from '@angular/core';
|
|
255
|
+
import { HlmPopoverImports } from '@egose/shadcn-theme-ng/popover';
|
|
256
|
+
|
|
257
|
+
@Component({
|
|
258
|
+
selector: 'app-popover-external',
|
|
259
|
+
standalone: true,
|
|
260
|
+
imports: [...HlmPopoverImports],
|
|
261
|
+
template: `
|
|
262
|
+
<hlm-popover #pop="hlmPopover">
|
|
263
|
+
<hlm-popover-content *hlmPopoverPortal>
|
|
264
|
+
<p class="tw:text-sm">Triggered from outside the root.</p>
|
|
265
|
+
</hlm-popover-content>
|
|
266
|
+
</hlm-popover>
|
|
267
|
+
|
|
268
|
+
<button hlmPopoverTriggerFor [hlmPopoverTriggerFor]="pop" type="button">Open from here</button>
|
|
269
|
+
`,
|
|
270
|
+
})
|
|
271
|
+
export class PopoverExternalComponent {}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
> The exact template-ref export name follows the brain `BrnPopover` directive (`#pop="…"`) — verify against your installed `@spartan-ng/brain` version if the alias differs.
|
|
275
|
+
|
|
276
|
+
### 6. Async content + loading state
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { Component, signal } from '@angular/core';
|
|
280
|
+
import { HlmPopoverImports } from '@egose/shadcn-theme-ng/popover';
|
|
281
|
+
|
|
282
|
+
@Component({
|
|
283
|
+
selector: 'app-popover-async',
|
|
284
|
+
standalone: true,
|
|
285
|
+
imports: [...HlmPopoverImports],
|
|
286
|
+
template: `
|
|
287
|
+
<hlm-popover (stateChanged)="onState($event)">
|
|
288
|
+
<button hlmPopoverTrigger type="button">Recent activity</button>
|
|
289
|
+
<hlm-popover-content *hlmPopoverPortal>
|
|
290
|
+
<div hlmPopoverHeader>
|
|
291
|
+
<h4 hlmPopoverTitle>Activity</h4>
|
|
292
|
+
<p hlmPopoverDescription>{{ loading() ? 'Loading…' : 'Last 3 events.' }}</p>
|
|
293
|
+
</div>
|
|
294
|
+
@for (e of events(); track e) {
|
|
295
|
+
<p class="tw:text-sm">{{ e }}</p>
|
|
296
|
+
} @empty {
|
|
297
|
+
<p class="tw:text-sm tw:text-muted-foreground">Nothing yet.</p>
|
|
298
|
+
}
|
|
299
|
+
</hlm-popover-content>
|
|
300
|
+
</hlm-popover>
|
|
301
|
+
`,
|
|
302
|
+
})
|
|
303
|
+
export class PopoverAsyncComponent {
|
|
304
|
+
readonly events = signal<string[]>([]);
|
|
305
|
+
readonly loading = signal(false);
|
|
306
|
+
|
|
307
|
+
async onState(state: string): Promise<void> {
|
|
308
|
+
if (state !== 'open' || this.events().length) return;
|
|
309
|
+
this.loading.set(true);
|
|
310
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
311
|
+
this.events.set(['Deploy succeeded', 'Review requested', 'Comment added']);
|
|
312
|
+
this.loading.set(false);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Accessibility notes
|
|
318
|
+
|
|
319
|
+
- Triggers must be `<button>` (both selector variants require it) with a text label; the brain layer wires `aria-expanded`, `aria-controls`, and focus return.
|
|
320
|
+
- Keep a heading (`hlmPopoverTitle`) as the first element when the panel conveys a topic — screen-reader users land inside the panel on open.
|
|
321
|
+
- `autoFocus` (brain input) controls initial focus; form panels should focus the first field, read-only panels should keep focus on the trigger side and let users tab in.
|
|
322
|
+
- `Escape`/outside-pointer close comes from the brain defaults (`closeOnOutsidePointerEvents` is configurable); don't trap focus manually.
|
|
323
|
+
|
|
324
|
+
## Theming / CSS variables
|
|
325
|
+
|
|
326
|
+
No theming inputs. The panel uses `bg-popover text-popover-foreground ring-foreground/10`; header/title/description use text tokens. Widen via portal `class` (example 4); state-driven animations read the mirrored `data-state`.
|
|
327
|
+
|
|
328
|
+
## Related subpaths
|
|
329
|
+
|
|
330
|
+
- `@egose/shadcn-theme-ng/tooltip` — hover hint vs this component's click-anchored panel.
|
|
331
|
+
- `@egose/shadcn-theme-ng/dropdown-menu`, `@egose/shadcn-theme-ng/select` — menu/select overlays built on the same brain overlay concepts.
|
|
332
|
+
- `@egose/shadcn-theme-ng/input`, `@egose/shadcn-theme-ng/label` — form controls commonly hosted inside popover panels.
|
package/progress/README.md
CHANGED
|
@@ -1,11 +1,317 @@
|
|
|
1
|
-
# Progress
|
|
1
|
+
# Progress (`@egose/shadcn-theme-ng/progress`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Determinate/indeterminate progress bar (shadcn/ui `progress` equivalent). Thin shadcn styling directives over the spartan-ng `BrnProgress` brain family: `hlm-progress` owns the track + value semantics (`value`, `max`, `getValueLabel`), and the inner `hlmProgressIndicator` bar positions itself from the brain value with RTL awareness and an indeterminate animation state.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Ships as `@egose/shadcn-theme-ng/progress` and `@egose/shadcn-theme-ng-tw/progress` (tw: variant). See the [package README](../../README.md) for installation, peer dependencies, Tailwind setup, and testing. Do not publish this project directory independently.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
# Plain Tailwind (no prefix):
|
|
11
|
+
npm install @egose/shadcn-theme-ng
|
|
12
|
+
|
|
13
|
+
# Or the tw:-prefixed variant:
|
|
14
|
+
npm install @egose/shadcn-theme-ng-tw
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Peer dependencies are inherited from the package root (see [package README](../../README.md)). This subpath itself declares `@angular/common`, `@angular/core`, `@spartan-ng/brain` as peers plus a `tslib` runtime dependency; at runtime the indicator also reads Angular CDK `Directionality` for RTL. No extra install step is needed beyond the package install above.
|
|
18
|
+
|
|
19
|
+
## Imports
|
|
20
|
+
|
|
21
|
+
Real exported symbols (from `src/public-api.ts`):
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
HlmProgress, // directive: hlm-progress,[hlmProgress]
|
|
26
|
+
HlmProgressIndicator, // directive: [hlmProgressIndicator],hlm-progress-indicator
|
|
27
|
+
HlmProgressImports, // readonly [HlmProgress, HlmProgressIndicator]
|
|
28
|
+
HlmProgressModule, // NgModule wrapping HlmProgressImports
|
|
29
|
+
} from '@egose/shadcn-theme-ng/progress';
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Standalone usage:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { Component } from '@angular/core';
|
|
36
|
+
import { HlmProgressImports } from '@egose/shadcn-theme-ng/progress';
|
|
37
|
+
|
|
38
|
+
@Component({
|
|
39
|
+
selector: 'app-demo',
|
|
40
|
+
standalone: true,
|
|
41
|
+
imports: [...HlmProgressImports],
|
|
42
|
+
template: `
|
|
43
|
+
<hlm-progress [value]="40" [max]="100">
|
|
44
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
45
|
+
</hlm-progress>
|
|
46
|
+
`,
|
|
47
|
+
})
|
|
48
|
+
export class DemoComponent {}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
NgModule usage:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { NgModule } from '@angular/core';
|
|
55
|
+
import { HlmProgressModule } from '@egose/shadcn-theme-ng/progress';
|
|
56
|
+
|
|
57
|
+
@NgModule({ imports: [HlmProgressModule] })
|
|
58
|
+
export class DemoModule {}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For the `tw:` build, swap the specifier to `@egose/shadcn-theme-ng-tw/progress`. Symbol names are identical.
|
|
62
|
+
|
|
63
|
+
## Anatomy / Structure
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<!-- determinate -->
|
|
67
|
+
<hlm-progress [value]="value()" [max]="100">
|
|
68
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
69
|
+
</hlm-progress>
|
|
70
|
+
|
|
71
|
+
<!-- attribute-selector form -->
|
|
72
|
+
<div hlmProgress [value]="40" [max]="100">
|
|
73
|
+
<div hlmProgressIndicator></div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- indeterminate (no value) -->
|
|
77
|
+
<hlm-progress>
|
|
78
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
79
|
+
</hlm-progress>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Class | Selector | Role |
|
|
83
|
+
| ---------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
84
|
+
| `HlmProgress` | `hlm-progress,[hlmProgress]` | Track (`BrnProgress` host: `value`, `max`, `getValueLabel`); `data-slot="progress"` |
|
|
85
|
+
| `HlmProgressIndicator` | `[hlmProgressIndicator],hlm-progress-indicator` | Fill bar (`BrnProgressIndicator` host); `data-slot="progress-indicator"` |
|
|
86
|
+
|
|
87
|
+
The indicator computes `translateX(-offset%)` from `100 - value` (defaulting `null`/`undefined` value to `100` for the offset math) and flips the sign in RTL via CDK `Directionality`. When the brain value is `null`/`undefined` it adds the `animate-indeterminate` class instead of a static fill.
|
|
88
|
+
|
|
89
|
+
## API reference
|
|
90
|
+
|
|
91
|
+
### `HlmProgress` (`hlm-progress,[hlmProgress]`)
|
|
92
|
+
|
|
93
|
+
| Member | Kind | Type | Notes |
|
|
94
|
+
| --------------- | ------------------------- | ----------------------------- | ------------------------------------------------------------ |
|
|
95
|
+
| `value` | input (via `BrnProgress`) | `number \| null \| undefined` | Current value; `null`/`undefined` → indeterminate |
|
|
96
|
+
| `max` | input (via `BrnProgress`) | `number` | Scale maximum (commonly `100`) |
|
|
97
|
+
| `getValueLabel` | input (via `BrnProgress`) | `(value, max) => string` | Accessible value-text factory (see brain docs for signature) |
|
|
98
|
+
|
|
99
|
+
No shadcn inputs of its own. Track styling: `bg-muted h-1.5 rounded-full relative inline-flex w-full overflow-hidden`.
|
|
100
|
+
|
|
101
|
+
### `HlmProgressIndicator` (`[hlmProgressIndicator],hlm-progress-indicator`)
|
|
102
|
+
|
|
103
|
+
No public inputs/outputs/methods. Internals (protected, for understanding only): `_transform` (computed `translateX()`), `_indeterminate` (computed `value == null`). Fill styling: `bg-primary h-full w-full flex-1 transition-all`; indeterminate state toggles `animate-indeterminate`.
|
|
104
|
+
|
|
105
|
+
## Examples
|
|
106
|
+
|
|
107
|
+
### 1. Basic determinate bar
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { Component, signal } from '@angular/core';
|
|
111
|
+
import { HlmProgressImports } from '@egose/shadcn-theme-ng/progress';
|
|
112
|
+
|
|
113
|
+
@Component({
|
|
114
|
+
selector: 'app-progress-basic',
|
|
115
|
+
standalone: true,
|
|
116
|
+
imports: [...HlmProgressImports],
|
|
117
|
+
template: `
|
|
118
|
+
<hlm-progress [value]="value()" [max]="100">
|
|
119
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
120
|
+
</hlm-progress>
|
|
121
|
+
<p class="tw:text-sm tw:text-muted-foreground">{{ value() }}%</p>
|
|
122
|
+
<button type="button" (click)="value.set(Math.min(100, value() + 10))">+10</button>
|
|
123
|
+
`,
|
|
124
|
+
})
|
|
125
|
+
export class ProgressBasicComponent {
|
|
126
|
+
readonly value = signal(40);
|
|
127
|
+
protected readonly Math = Math;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 2. Timer / simulated upload
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { Component, signal, OnDestroy } from '@angular/core';
|
|
135
|
+
import { HlmProgressImports } from '@egose/shadcn-theme-ng/progress';
|
|
136
|
+
|
|
137
|
+
@Component({
|
|
138
|
+
selector: 'app-progress-timer',
|
|
139
|
+
standalone: true,
|
|
140
|
+
imports: [...HlmProgressImports],
|
|
141
|
+
template: `
|
|
142
|
+
<hlm-progress [value]="progress()" [max]="100">
|
|
143
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
144
|
+
</hlm-progress>
|
|
145
|
+
<div class="tw:flex tw:gap-2">
|
|
146
|
+
<button type="button" (click)="start()">Start</button>
|
|
147
|
+
<button type="button" (click)="reset()">Reset</button>
|
|
148
|
+
</div>
|
|
149
|
+
`,
|
|
150
|
+
})
|
|
151
|
+
export class ProgressTimerComponent implements OnDestroy {
|
|
152
|
+
readonly progress = signal(0);
|
|
153
|
+
private timer: ReturnType<typeof setInterval> | undefined;
|
|
154
|
+
|
|
155
|
+
start(): void {
|
|
156
|
+
this.stop();
|
|
157
|
+
this.timer = setInterval(() => {
|
|
158
|
+
this.progress.update((v) => (v >= 100 ? 100 : v + 2));
|
|
159
|
+
if (this.progress() >= 100) this.stop();
|
|
160
|
+
}, 100);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
reset(): void {
|
|
164
|
+
this.stop();
|
|
165
|
+
this.progress.set(0);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
ngOnDestroy(): void {
|
|
169
|
+
this.stop();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private stop(): void {
|
|
173
|
+
if (this.timer) clearInterval(this.timer);
|
|
174
|
+
this.timer = undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 3. Indeterminate (unknown duration)
|
|
180
|
+
|
|
181
|
+
Omit `value` entirely — the indicator switches to the `animate-indeterminate` treatment.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { Component, signal } from '@angular/core';
|
|
185
|
+
import { HlmProgressImports } from '@egose/shadcn-theme-ng/progress';
|
|
186
|
+
|
|
187
|
+
@Component({
|
|
188
|
+
selector: 'app-progress-indeterminate',
|
|
189
|
+
standalone: true,
|
|
190
|
+
imports: [...HlmProgressImports],
|
|
191
|
+
template: `
|
|
192
|
+
<button type="button" (click)="load()">Fetch report</button>
|
|
193
|
+
@if (loading()) {
|
|
194
|
+
<hlm-progress aria-label="Loading report">
|
|
195
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
196
|
+
</hlm-progress>
|
|
197
|
+
}
|
|
198
|
+
`,
|
|
199
|
+
})
|
|
200
|
+
export class ProgressIndeterminateComponent {
|
|
201
|
+
readonly loading = signal(false);
|
|
202
|
+
|
|
203
|
+
async load(): Promise<void> {
|
|
204
|
+
this.loading.set(true);
|
|
205
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
206
|
+
this.loading.set(false);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
11
209
|
```
|
|
210
|
+
|
|
211
|
+
### 4. Custom scale (`max !== 100`) + accessible label
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { Component, signal } from '@angular/core';
|
|
215
|
+
import { HlmProgressImports } from '@egose/shadcn-theme-ng/progress';
|
|
216
|
+
|
|
217
|
+
@Component({
|
|
218
|
+
selector: 'app-progress-scale',
|
|
219
|
+
standalone: true,
|
|
220
|
+
imports: [...HlmProgressImports],
|
|
221
|
+
template: `
|
|
222
|
+
<hlm-progress [value]="done()" [max]="total()" [getValueLabel]="label" aria-label="Migration progress">
|
|
223
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
224
|
+
</hlm-progress>
|
|
225
|
+
<p class="tw:text-sm">{{ done() }} of {{ total() }} rows migrated</p>
|
|
226
|
+
`,
|
|
227
|
+
})
|
|
228
|
+
export class ProgressScaleComponent {
|
|
229
|
+
readonly done = signal(37);
|
|
230
|
+
readonly total = signal(200);
|
|
231
|
+
|
|
232
|
+
readonly label = (value: number | null | undefined, max: number): string => `${value ?? 0} of ${max} rows`;
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
> `getValueLabel` is the brain hook for the `aria-valuetext`; keep the visible text in sync (as above) so sighted and SR users agree.
|
|
237
|
+
|
|
238
|
+
### 5. Multi-step wizard
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { Component, signal, computed } from '@angular/core';
|
|
242
|
+
import { HlmProgressImports } from '@egose/shadcn-theme-ng/progress';
|
|
243
|
+
|
|
244
|
+
@Component({
|
|
245
|
+
selector: 'app-progress-steps',
|
|
246
|
+
standalone: true,
|
|
247
|
+
imports: [...HlmProgressImports],
|
|
248
|
+
template: `
|
|
249
|
+
<hlm-progress [value]="step()" [max]="steps.length">
|
|
250
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
251
|
+
</hlm-progress>
|
|
252
|
+
<p class="tw:text-sm">Step {{ step() }} of {{ steps.length }}: {{ steps[step() - 1] }}</p>
|
|
253
|
+
<div class="tw:flex tw:gap-2">
|
|
254
|
+
<button type="button" (click)="prev()" [disabled]="step() <= 1">Back</button>
|
|
255
|
+
<button type="button" (click)="next()" [disabled]="step() >= steps.length">Next</button>
|
|
256
|
+
</div>
|
|
257
|
+
`,
|
|
258
|
+
})
|
|
259
|
+
export class ProgressStepsComponent {
|
|
260
|
+
readonly steps = ['Account', 'Profile', 'Confirm'];
|
|
261
|
+
readonly step = signal(1);
|
|
262
|
+
|
|
263
|
+
prev(): void {
|
|
264
|
+
this.step.update((s) => Math.max(1, s - 1));
|
|
265
|
+
}
|
|
266
|
+
next(): void {
|
|
267
|
+
this.step.update((s) => Math.min(this.steps.length, s + 1));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 6. Error / complete states and NgModule form
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { NgModule, Component, signal, computed } from '@angular/core';
|
|
276
|
+
import { HlmProgressImports, HlmProgressModule } from '@egose/shadcn-theme-ng/progress';
|
|
277
|
+
|
|
278
|
+
@Component({
|
|
279
|
+
selector: 'app-progress-states',
|
|
280
|
+
standalone: true,
|
|
281
|
+
imports: [...HlmProgressImports],
|
|
282
|
+
template: `
|
|
283
|
+
<hlm-progress [value]="value()" [max]="100" [class]="barClass()">
|
|
284
|
+
<hlm-progress-indicator hlmProgressIndicator />
|
|
285
|
+
</hlm-progress>
|
|
286
|
+
<p class="tw:text-sm" [class.tw:text-destructive]="failed()">
|
|
287
|
+
{{ failed() ? 'Upload failed — retrying…' : value() >= 100 ? 'Complete' : 'Uploading…' }}
|
|
288
|
+
</p>
|
|
289
|
+
`,
|
|
290
|
+
})
|
|
291
|
+
export class ProgressStatesComponent {
|
|
292
|
+
readonly value = signal(75);
|
|
293
|
+
readonly failed = signal(false);
|
|
294
|
+
readonly barClass = computed(() => (this.failed() ? 'tw:[&>[data-slot=progress-indicator]]:tw:bg-destructive' : ''));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@NgModule({ imports: [HlmProgressModule] })
|
|
298
|
+
export class ProgressLegacyModule {}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
> There is no `state`/`variant` input — success/error tints are done with plain `class` overrides targeting the indicator (as above), since the indicator carries `data-slot="progress-indicator"`.
|
|
302
|
+
|
|
303
|
+
## Accessibility notes
|
|
304
|
+
|
|
305
|
+
- The brain `BrnProgress` exposes `role="progressbar"` with `aria-valuemin`/`max`/`now` (plus `aria-valuetext` via `getValueLabel`) — always provide `aria-label`/`aria-labelledby` unless surrounding text already names the bar.
|
|
306
|
+
- Indeterminate bars (no `value`) must still be labelled ("Loading report") so SR users know what is pending; pair with status text or an `aria-live` region for completion.
|
|
307
|
+
- Don't use progress as the _only_ conveyor of state — mirror percent/steps in text (examples 1/4/5).
|
|
308
|
+
- Color overrides (example 6) are decorative; keep the text label as the source of truth for error/complete states.
|
|
309
|
+
|
|
310
|
+
## Theming / CSS variables
|
|
311
|
+
|
|
312
|
+
No theming inputs. Track is `bg-muted`, fill is `bg-primary`; both follow shadcn tokens automatically. Tint the fill per-instance with a `class` override on the track targeting `[data-slot=progress-indicator]`.
|
|
313
|
+
|
|
314
|
+
## Related subpaths
|
|
315
|
+
|
|
316
|
+
- `@egose/shadcn-theme-ng/skeleton`, `@egose/shadcn-theme-ng/spinner` — alternative loading indicators (skeleton screens, spinners) vs this determinate bar.
|
|
317
|
+
- `@egose/shadcn-theme-ng/sonner` — toast completion notices to pair with a finished upload.
|