@ojiepermana/angular-component 22.0.27
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 +82 -0
- package/accordion/README.md +193 -0
- package/alert/README.md +180 -0
- package/alert-dialog/README.md +239 -0
- package/aspect-ratio/README.md +112 -0
- package/avatar/README.md +176 -0
- package/badge/README.md +133 -0
- package/breadcrumb/README.md +216 -0
- package/button/README.md +139 -0
- package/button-group/README.md +208 -0
- package/calendar/README.md +135 -0
- package/card/README.md +220 -0
- package/carousel/README.md +276 -0
- package/checkbox/README.md +149 -0
- package/collapsible/README.md +195 -0
- package/combobox/README.md +198 -0
- package/command/README.md +275 -0
- package/composer/README.md +235 -0
- package/context-menu/README.md +267 -0
- package/date-picker/README.md +179 -0
- package/dialog/README.md +235 -0
- package/drawer/README.md +145 -0
- package/dropdown-menu/README.md +311 -0
- package/editor/README.md +136 -0
- package/empty/README.md +183 -0
- package/fesm2022/ojiepermana-angular-component-accordion.mjs +186 -0
- package/fesm2022/ojiepermana-angular-component-accordion.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-alert-dialog.mjs +276 -0
- package/fesm2022/ojiepermana-angular-component-alert-dialog.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-alert.mjs +99 -0
- package/fesm2022/ojiepermana-angular-component-alert.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-aspect-ratio.mjs +37 -0
- package/fesm2022/ojiepermana-angular-component-aspect-ratio.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-avatar.mjs +139 -0
- package/fesm2022/ojiepermana-angular-component-avatar.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-badge.mjs +50 -0
- package/fesm2022/ojiepermana-angular-component-badge.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-breadcrumb.mjs +200 -0
- package/fesm2022/ojiepermana-angular-component-breadcrumb.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-button-group.mjs +103 -0
- package/fesm2022/ojiepermana-angular-component-button-group.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-button.mjs +68 -0
- package/fesm2022/ojiepermana-angular-component-button.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-calendar.mjs +103 -0
- package/fesm2022/ojiepermana-angular-component-calendar.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-card.mjs +152 -0
- package/fesm2022/ojiepermana-angular-component-card.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-carousel.mjs +334 -0
- package/fesm2022/ojiepermana-angular-component-carousel.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-checkbox.mjs +165 -0
- package/fesm2022/ojiepermana-angular-component-checkbox.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-collapsible.mjs +121 -0
- package/fesm2022/ojiepermana-angular-component-collapsible.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-combobox.mjs +274 -0
- package/fesm2022/ojiepermana-angular-component-combobox.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-command.mjs +297 -0
- package/fesm2022/ojiepermana-angular-component-command.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-composer.mjs +352 -0
- package/fesm2022/ojiepermana-angular-component-composer.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-context-menu.mjs +108 -0
- package/fesm2022/ojiepermana-angular-component-context-menu.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-date-picker.mjs +186 -0
- package/fesm2022/ojiepermana-angular-component-date-picker.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-dialog.mjs +283 -0
- package/fesm2022/ojiepermana-angular-component-dialog.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-drawer.mjs +6 -0
- package/fesm2022/ojiepermana-angular-component-drawer.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-dropdown-menu.mjs +494 -0
- package/fesm2022/ojiepermana-angular-component-dropdown-menu.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-editor.mjs +680 -0
- package/fesm2022/ojiepermana-angular-component-editor.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-empty.mjs +145 -0
- package/fesm2022/ojiepermana-angular-component-empty.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-form.mjs +364 -0
- package/fesm2022/ojiepermana-angular-component-form.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-hover-card.mjs +275 -0
- package/fesm2022/ojiepermana-angular-component-hover-card.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-icon.mjs +86 -0
- package/fesm2022/ojiepermana-angular-component-icon.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-input-group.mjs +179 -0
- package/fesm2022/ojiepermana-angular-component-input-group.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-input-otp.mjs +517 -0
- package/fesm2022/ojiepermana-angular-component-input-otp.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-input.mjs +45 -0
- package/fesm2022/ojiepermana-angular-component-input.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-item.mjs +264 -0
- package/fesm2022/ojiepermana-angular-component-item.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-kanban.mjs +314 -0
- package/fesm2022/ojiepermana-angular-component-kanban.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-kbd.mjs +55 -0
- package/fesm2022/ojiepermana-angular-component-kbd.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-label.mjs +33 -0
- package/fesm2022/ojiepermana-angular-component-label.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-menubar.mjs +311 -0
- package/fesm2022/ojiepermana-angular-component-menubar.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-native-select.mjs +67 -0
- package/fesm2022/ojiepermana-angular-component-native-select.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-navigation-menu.mjs +408 -0
- package/fesm2022/ojiepermana-angular-component-navigation-menu.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-pagination.mjs +226 -0
- package/fesm2022/ojiepermana-angular-component-pagination.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-pillbox.mjs +810 -0
- package/fesm2022/ojiepermana-angular-component-pillbox.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-popover.mjs +145 -0
- package/fesm2022/ojiepermana-angular-component-popover.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-progress.mjs +60 -0
- package/fesm2022/ojiepermana-angular-component-progress.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-radio.mjs +173 -0
- package/fesm2022/ojiepermana-angular-component-radio.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-resizable.mjs +478 -0
- package/fesm2022/ojiepermana-angular-component-resizable.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-scroll-area.mjs +54 -0
- package/fesm2022/ojiepermana-angular-component-scroll-area.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-select.mjs +297 -0
- package/fesm2022/ojiepermana-angular-component-select.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-separator.mjs +37 -0
- package/fesm2022/ojiepermana-angular-component-separator.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-sheet.mjs +297 -0
- package/fesm2022/ojiepermana-angular-component-sheet.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-skeleton.mjs +31 -0
- package/fesm2022/ojiepermana-angular-component-skeleton.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-slider.mjs +423 -0
- package/fesm2022/ojiepermana-angular-component-slider.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-spinner.mjs +60 -0
- package/fesm2022/ojiepermana-angular-component-spinner.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-switch.mjs +140 -0
- package/fesm2022/ojiepermana-angular-component-switch.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-table.mjs +155 -0
- package/fesm2022/ojiepermana-angular-component-table.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-tabs.mjs +271 -0
- package/fesm2022/ojiepermana-angular-component-tabs.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-textarea.mjs +39 -0
- package/fesm2022/ojiepermana-angular-component-textarea.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-timeline.mjs +237 -0
- package/fesm2022/ojiepermana-angular-component-timeline.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-toast.mjs +161 -0
- package/fesm2022/ojiepermana-angular-component-toast.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-toggle-group.mjs +289 -0
- package/fesm2022/ojiepermana-angular-component-toggle-group.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-toggle.mjs +82 -0
- package/fesm2022/ojiepermana-angular-component-toggle.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-tooltip.mjs +410 -0
- package/fesm2022/ojiepermana-angular-component-tooltip.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component-utils.mjs +81 -0
- package/fesm2022/ojiepermana-angular-component-utils.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-component.mjs +11 -0
- package/fesm2022/ojiepermana-angular-component.mjs.map +1 -0
- package/form/README.md +210 -0
- package/hover-card/README.md +142 -0
- package/icon/README.md +25 -0
- package/input/README.md +159 -0
- package/input-group/README.md +237 -0
- package/input-otp/README.md +278 -0
- package/item/README.md +247 -0
- package/kanban/README.md +81 -0
- package/kbd/README.md +139 -0
- package/label/README.md +135 -0
- package/menubar/README.md +269 -0
- package/native-select/README.md +176 -0
- package/navigation-menu/README.md +160 -0
- package/package.json +291 -0
- package/pagination/README.md +144 -0
- package/pillbox/README.md +67 -0
- package/popover/README.md +43 -0
- package/progress/README.md +160 -0
- package/radio/README.md +209 -0
- package/resizable/README.md +168 -0
- package/scroll-area/README.md +143 -0
- package/select/README.md +174 -0
- package/separator/README.md +170 -0
- package/sheet/README.md +183 -0
- package/skeleton/README.md +158 -0
- package/slider/README.md +207 -0
- package/spinner/README.md +160 -0
- package/switch/README.md +166 -0
- package/table/README.md +291 -0
- package/tabs/README.md +214 -0
- package/textarea/README.md +153 -0
- package/timeline/README.md +94 -0
- package/toast/README.md +321 -0
- package/toggle/README.md +131 -0
- package/toggle-group/README.md +206 -0
- package/tooltip/README.md +207 -0
- package/types/ojiepermana-angular-component-accordion.d.ts +51 -0
- package/types/ojiepermana-angular-component-alert-dialog.d.ts +93 -0
- package/types/ojiepermana-angular-component-alert.d.ts +37 -0
- package/types/ojiepermana-angular-component-aspect-ratio.d.ts +12 -0
- package/types/ojiepermana-angular-component-avatar.d.ts +51 -0
- package/types/ojiepermana-angular-component-badge.d.ts +19 -0
- package/types/ojiepermana-angular-component-breadcrumb.d.ts +46 -0
- package/types/ojiepermana-angular-component-button-group.d.ts +26 -0
- package/types/ojiepermana-angular-component-button.d.ts +22 -0
- package/types/ojiepermana-angular-component-calendar.d.ts +39 -0
- package/types/ojiepermana-angular-component-card.d.ts +60 -0
- package/types/ojiepermana-angular-component-carousel.d.ts +86 -0
- package/types/ojiepermana-angular-component-checkbox.d.ts +42 -0
- package/types/ojiepermana-angular-component-collapsible.d.ts +42 -0
- package/types/ojiepermana-angular-component-combobox.d.ts +53 -0
- package/types/ojiepermana-angular-component-command.d.ts +102 -0
- package/types/ojiepermana-angular-component-composer.d.ts +90 -0
- package/types/ojiepermana-angular-component-context-menu.d.ts +36 -0
- package/types/ojiepermana-angular-component-date-picker.d.ts +48 -0
- package/types/ojiepermana-angular-component-dialog.d.ts +87 -0
- package/types/ojiepermana-angular-component-drawer.d.ts +1 -0
- package/types/ojiepermana-angular-component-dropdown-menu.d.ts +140 -0
- package/types/ojiepermana-angular-component-editor.d.ts +126 -0
- package/types/ojiepermana-angular-component-empty.d.ts +50 -0
- package/types/ojiepermana-angular-component-form.d.ts +140 -0
- package/types/ojiepermana-angular-component-hover-card.d.ts +75 -0
- package/types/ojiepermana-angular-component-icon.d.ts +31 -0
- package/types/ojiepermana-angular-component-input-group.d.ts +51 -0
- package/types/ojiepermana-angular-component-input-otp.d.ts +142 -0
- package/types/ojiepermana-angular-component-input.d.ts +16 -0
- package/types/ojiepermana-angular-component-item.d.ts +88 -0
- package/types/ojiepermana-angular-component-kanban.d.ts +70 -0
- package/types/ojiepermana-angular-component-kbd.d.ts +16 -0
- package/types/ojiepermana-angular-component-label.d.ts +11 -0
- package/types/ojiepermana-angular-component-menubar.d.ts +69 -0
- package/types/ojiepermana-angular-component-native-select.d.ts +26 -0
- package/types/ojiepermana-angular-component-navigation-menu.d.ts +98 -0
- package/types/ojiepermana-angular-component-pagination.d.ts +33 -0
- package/types/ojiepermana-angular-component-pillbox.d.ts +156 -0
- package/types/ojiepermana-angular-component-popover.d.ts +50 -0
- package/types/ojiepermana-angular-component-progress.d.ts +17 -0
- package/types/ojiepermana-angular-component-radio.d.ts +57 -0
- package/types/ojiepermana-angular-component-resizable.d.ts +99 -0
- package/types/ojiepermana-angular-component-scroll-area.d.ts +19 -0
- package/types/ojiepermana-angular-component-select.d.ts +56 -0
- package/types/ojiepermana-angular-component-separator.d.ts +14 -0
- package/types/ojiepermana-angular-component-sheet.d.ts +78 -0
- package/types/ojiepermana-angular-component-skeleton.d.ts +10 -0
- package/types/ojiepermana-angular-component-slider.d.ts +74 -0
- package/types/ojiepermana-angular-component-spinner.d.ts +13 -0
- package/types/ojiepermana-angular-component-switch.d.ts +44 -0
- package/types/ojiepermana-angular-component-table.d.ts +52 -0
- package/types/ojiepermana-angular-component-tabs.d.ts +92 -0
- package/types/ojiepermana-angular-component-textarea.d.ts +12 -0
- package/types/ojiepermana-angular-component-timeline.d.ts +63 -0
- package/types/ojiepermana-angular-component-toast.d.ts +51 -0
- package/types/ojiepermana-angular-component-toggle-group.d.ts +89 -0
- package/types/ojiepermana-angular-component-toggle.d.ts +25 -0
- package/types/ojiepermana-angular-component-tooltip.d.ts +101 -0
- package/types/ojiepermana-angular-component-utils.d.ts +30 -0
- package/types/ojiepermana-angular-component.d.ts +2 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Progress
|
|
2
|
+
|
|
3
|
+
Displays an indicator showing the completion progress of a task, typically as a
|
|
4
|
+
slim passive progress bar.
|
|
5
|
+
|
|
6
|
+
Use Progress for uploads, background jobs, onboarding steps, and other cases
|
|
7
|
+
where the user should understand status without directly interacting with the
|
|
8
|
+
bar itself.
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { ProgressComponent } from '@ojiepermana/angular-component/progress';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
For the visible label and slider-driven examples below, also import the existing
|
|
17
|
+
label and slider entrypoints:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { LabelComponent } from '@ojiepermana/angular-component/label';
|
|
21
|
+
import { SliderComponent } from '@ojiepermana/angular-component/slider';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Basic usage
|
|
25
|
+
|
|
26
|
+
Provide an accessible name directly when the bar stands on its own.
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<ProgressBar [value]="33" aria-label="Upload progress" class="w-full max-w-sm" />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Common patterns
|
|
33
|
+
|
|
34
|
+
### Visible label row
|
|
35
|
+
|
|
36
|
+
The current shadcn Radix docs place Progress inside a Field layout with a
|
|
37
|
+
visible label and percentage. In Angular, keep the visible copy outside the
|
|
38
|
+
bar and connect it with `aria-labelledby`.
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<label id="progress-upload-label" class="mb-2 flex items-center gap-3">
|
|
42
|
+
<span>Upload progress</span>
|
|
43
|
+
<span class="ms-auto text-muted-foreground">66%</span>
|
|
44
|
+
</label>
|
|
45
|
+
|
|
46
|
+
<ProgressBar [value]="66" aria-labelledby="progress-upload-label" class="w-full max-w-sm" />
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use `aria-labelledby` here instead of `label[for]`, because `ProgressBar` is a
|
|
50
|
+
custom element with `role="progressbar"`, not a native labelable form control.
|
|
51
|
+
|
|
52
|
+
### Controlled progress
|
|
53
|
+
|
|
54
|
+
Keep the progress bar passive and let surrounding controls own the interaction.
|
|
55
|
+
The built-in range slider directive works well for the same pattern shown in the
|
|
56
|
+
upstream docs.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const controlledValue = signal(50);
|
|
60
|
+
|
|
61
|
+
<Label id="progress-controlled-label" class="mb-2 flex items-center gap-3">
|
|
62
|
+
<span>Upload progress</span>
|
|
63
|
+
<span class="ms-auto text-muted-foreground">{{ controlledValue() }}%</span>
|
|
64
|
+
</Label>
|
|
65
|
+
|
|
66
|
+
<ProgressBar [value]="controlledValue()" aria-labelledby="progress-controlled-label" class="w-full" />
|
|
67
|
+
|
|
68
|
+
<Label for="progress-controlled-slider" class="mb-2 block">Adjust progress</Label>
|
|
69
|
+
<input
|
|
70
|
+
id="progress-controlled-slider"
|
|
71
|
+
type="range"
|
|
72
|
+
Slider
|
|
73
|
+
min="0"
|
|
74
|
+
max="100"
|
|
75
|
+
step="1"
|
|
76
|
+
[value]="controlledValue()"
|
|
77
|
+
(input)="onControlledInput($event)" />
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Indeterminate loading
|
|
81
|
+
|
|
82
|
+
Switch to `indeterminate` when work is running but no stable percentage is
|
|
83
|
+
available yet.
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<ProgressBar [indeterminate]="true" aria-label="Loading deployment" class="w-full max-w-sm" />
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### RTL
|
|
90
|
+
|
|
91
|
+
Follow the upstream guidance by setting `dir="rtl"` on the surrounding
|
|
92
|
+
container and flipping the bar with `rtl:rotate-180`.
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<section dir="rtl" lang="ar" class="w-full max-w-sm text-right">
|
|
96
|
+
<label id="progress-rtl-label" class="mb-2 flex items-center gap-3">
|
|
97
|
+
<span>تقدم الرفع</span>
|
|
98
|
+
<span class="ms-auto text-muted-foreground">٦٦%</span>
|
|
99
|
+
</label>
|
|
100
|
+
|
|
101
|
+
<ProgressBar [value]="66" aria-labelledby="progress-rtl-label" class="w-full rtl:rotate-180" />
|
|
102
|
+
</section>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API reference
|
|
106
|
+
|
|
107
|
+
| Input | Type | Default |
|
|
108
|
+
| ----------------- | ---------------- | ------- |
|
|
109
|
+
| `value` | `number \| null` | `0` |
|
|
110
|
+
| `max` | `number` | `100` |
|
|
111
|
+
| `indeterminate` | `boolean` | `false` |
|
|
112
|
+
| `aria-label` | `string \| null` | `null` |
|
|
113
|
+
| `aria-labelledby` | `string \| null` | `null` |
|
|
114
|
+
| `class` | `string` | `''` |
|
|
115
|
+
|
|
116
|
+
Native host attributes such as `id`, `dir`, and `data-*` still pass through to
|
|
117
|
+
the rendered custom element and can be used for surrounding composition.
|
|
118
|
+
|
|
119
|
+
## Styling and theming
|
|
120
|
+
|
|
121
|
+
Tokens consumed:
|
|
122
|
+
|
|
123
|
+
- `--secondary` for the track.
|
|
124
|
+
- `--primary` for the indicator.
|
|
125
|
+
|
|
126
|
+
Use the `class` input for width and layout utilities such as `w-full`,
|
|
127
|
+
`max-w-sm`, or `rtl:rotate-180`. Indeterminate state is animated via a CSS
|
|
128
|
+
keyframe that respects `prefers-reduced-motion`.
|
|
129
|
+
|
|
130
|
+
## Accessibility
|
|
131
|
+
|
|
132
|
+
- Host is `role="progressbar"`.
|
|
133
|
+
- Determinate mode sets `aria-valuemin="0"`, `aria-valuemax="max"`, and
|
|
134
|
+
`aria-valuenow="clamped value"`.
|
|
135
|
+
- Indeterminate mode omits `aria-valuenow` and adds `data-state="indeterminate"`.
|
|
136
|
+
- Always provide an accessible name via `aria-label` or `aria-labelledby`.
|
|
137
|
+
- If visible label text already exists on the page, connect it with
|
|
138
|
+
`aria-labelledby` instead of relying on native label semantics.
|
|
139
|
+
|
|
140
|
+
## Keyboard interactions
|
|
141
|
+
|
|
142
|
+
Progress is passive and not focusable by default. Any keyboard interaction
|
|
143
|
+
belongs to surrounding controls such as the slider used in the controlled
|
|
144
|
+
example.
|
|
145
|
+
|
|
146
|
+
## Angular notes
|
|
147
|
+
|
|
148
|
+
- Keep Progress focused on status display. Do not nest buttons or other
|
|
149
|
+
interactive controls inside the bar.
|
|
150
|
+
- Drive determinate state from signals, reactive forms, or any other existing
|
|
151
|
+
Angular state source.
|
|
152
|
+
- For visible label rows, `Label` is a good lightweight companion, but plain
|
|
153
|
+
text with the right ARIA wiring works too.
|
|
154
|
+
|
|
155
|
+
## Source parity
|
|
156
|
+
|
|
157
|
+
This Angular implementation follows the shadcn Radix progress examples while
|
|
158
|
+
translating `Field` and `FieldLabel` to ordinary Angular markup plus explicit
|
|
159
|
+
ARIA wiring. The controlled example reuses the existing range slider directive,
|
|
160
|
+
and the RTL pattern matches the upstream `rtl:rotate-180` approach.
|
package/radio/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Radio Group
|
|
2
|
+
|
|
3
|
+
Accessible exclusive-choice control composed from `RadioGroup` and `Radio`.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import {
|
|
9
|
+
FormContentComponent,
|
|
10
|
+
FormDescriptionComponent,
|
|
11
|
+
FormFieldComponent,
|
|
12
|
+
FormFieldsetComponent,
|
|
13
|
+
FormLegendComponent,
|
|
14
|
+
FormTitleComponent,
|
|
15
|
+
} from '@ojiepermana/angular-component/form';
|
|
16
|
+
import { LabelComponent } from '@ojiepermana/angular-component/label';
|
|
17
|
+
import { RadioComponent, RadioGroupComponent } from '@ojiepermana/angular-component/radio';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Composition
|
|
21
|
+
|
|
22
|
+
The upstream shadcn `RadioGroup` examples map to the existing Angular radio and form primitives.
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
RadioGroup
|
|
26
|
+
├── label[Label]
|
|
27
|
+
│ ├── Radio
|
|
28
|
+
│ └── FormContent
|
|
29
|
+
│ ├── FormTitle
|
|
30
|
+
│ └── p[FormDescription]
|
|
31
|
+
└── label[Label]
|
|
32
|
+
├── Radio
|
|
33
|
+
└── copy or FormContent
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Basic Usage
|
|
37
|
+
|
|
38
|
+
Use `RadioGroup` as the value owner and wrap each option in `label[Label]` when the whole row should toggle the radio.
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<RadioGroup name="density" [(ngModel)]="density" class="w-fit">
|
|
42
|
+
<label Label class="flex items-center gap-3">
|
|
43
|
+
<Radio value="default" />
|
|
44
|
+
<span>Default</span>
|
|
45
|
+
</label>
|
|
46
|
+
<label Label class="flex items-center gap-3">
|
|
47
|
+
<Radio value="comfortable" />
|
|
48
|
+
<span>Comfortable</span>
|
|
49
|
+
</label>
|
|
50
|
+
<label Label class="flex items-center gap-3">
|
|
51
|
+
<Radio value="compact" />
|
|
52
|
+
<span>Compact</span>
|
|
53
|
+
</label>
|
|
54
|
+
</RadioGroup>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Reactive forms also work because the group implements `ControlValueAccessor`.
|
|
58
|
+
|
|
59
|
+
## Common Patterns
|
|
60
|
+
|
|
61
|
+
### Descriptive rows
|
|
62
|
+
|
|
63
|
+
Map the shadcn `Field` and `FieldContent` helpers to `FormField` and `FormContent`.
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<RadioGroup name="density" [(ngModel)]="density" class="w-full max-w-md">
|
|
67
|
+
<label Label class="block rounded-xl border border-border bg-card/40 p-4">
|
|
68
|
+
<FormField orientation="horizontal" class="gap-4">
|
|
69
|
+
<Radio value="default" />
|
|
70
|
+
<FormContent>
|
|
71
|
+
<FormTitle>Default</FormTitle>
|
|
72
|
+
<p FormDescription>Standard spacing for most use cases.</p>
|
|
73
|
+
</FormContent>
|
|
74
|
+
</FormField>
|
|
75
|
+
</label>
|
|
76
|
+
</RadioGroup>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Choice cards
|
|
80
|
+
|
|
81
|
+
Wrap each plan card in `label[Label]` so the full surface toggles the choice.
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<RadioGroup name="plan" [(ngModel)]="selectedPlan" class="w-full max-w-md">
|
|
85
|
+
<label Label class="block rounded-xl border border-border bg-card/40 p-4">
|
|
86
|
+
<FormField orientation="horizontal" class="items-start gap-4">
|
|
87
|
+
<FormContent>
|
|
88
|
+
<FormTitle>Pro</FormTitle>
|
|
89
|
+
<p FormDescription>For growing businesses.</p>
|
|
90
|
+
</FormContent>
|
|
91
|
+
<Radio value="pro" />
|
|
92
|
+
</FormField>
|
|
93
|
+
</label>
|
|
94
|
+
</RadioGroup>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Semantic fieldsets
|
|
98
|
+
|
|
99
|
+
Use a real fieldset and legend when the radio list answers one grouped question.
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<fieldset FormFieldset class="max-w-md rounded-xl border border-border bg-card/40 p-5">
|
|
103
|
+
<legend FormLegend variant="label">Subscription plan</legend>
|
|
104
|
+
<p class="text-sm leading-6 text-muted-foreground">Yearly and lifetime plans offer significant savings.</p>
|
|
105
|
+
|
|
106
|
+
<RadioGroup name="subscription-plan" [(ngModel)]="subscriptionPlan" class="mt-4">
|
|
107
|
+
<label Label class="block rounded-lg border border-border/70 bg-background/80 p-3">
|
|
108
|
+
<FormField orientation="horizontal" class="gap-4">
|
|
109
|
+
<Radio value="monthly" />
|
|
110
|
+
<FormContent>
|
|
111
|
+
<FormTitle>Monthly ($9.99/month)</FormTitle>
|
|
112
|
+
</FormContent>
|
|
113
|
+
</FormField>
|
|
114
|
+
</label>
|
|
115
|
+
</RadioGroup>
|
|
116
|
+
</fieldset>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Validation and helper text
|
|
120
|
+
|
|
121
|
+
Forward helper and error relationships from the group when the descriptive copy sits outside the individual items.
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<FormField invalid class="max-w-md rounded-xl border border-destructive/40 bg-destructive/5 p-5">
|
|
125
|
+
<FormTitle>Notification Preferences</FormTitle>
|
|
126
|
+
<p id="notification-help" class="text-sm leading-6 text-muted-foreground">
|
|
127
|
+
Choose how you want to receive notifications.
|
|
128
|
+
</p>
|
|
129
|
+
|
|
130
|
+
<RadioGroup
|
|
131
|
+
name="notification-preferences"
|
|
132
|
+
[(ngModel)]="notificationPreference"
|
|
133
|
+
aria-describedby="notification-help notification-error"
|
|
134
|
+
aria-invalid="true">
|
|
135
|
+
<label Label class="block rounded-lg border border-destructive/40 bg-background/80 p-3">
|
|
136
|
+
<FormField orientation="horizontal" class="gap-4">
|
|
137
|
+
<Radio value="email" />
|
|
138
|
+
<FormContent>
|
|
139
|
+
<FormTitle>Email only</FormTitle>
|
|
140
|
+
</FormContent>
|
|
141
|
+
</FormField>
|
|
142
|
+
</label>
|
|
143
|
+
</RadioGroup>
|
|
144
|
+
|
|
145
|
+
<p id="notification-error" class="text-sm font-medium text-destructive">
|
|
146
|
+
Select one delivery channel before saving notification settings.
|
|
147
|
+
</p>
|
|
148
|
+
</FormField>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## API Reference
|
|
152
|
+
|
|
153
|
+
### `RadioGroupComponent`
|
|
154
|
+
|
|
155
|
+
| Input | Type | Default | Notes |
|
|
156
|
+
| ------------------ | ---------------------------- | ------------ | ------------------------------------------------------------ |
|
|
157
|
+
| `name` | `string` | `''` | Forwarded to the underlying Material radio group. |
|
|
158
|
+
| `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | Switches between stacked rows and inline columns. |
|
|
159
|
+
| `aria-label` | `string \| null` | `null` | Use when the group needs an explicit accessible name. |
|
|
160
|
+
| `aria-labelledby` | `string \| null` | `null` | Alternative to `aria-label` for external group headings. |
|
|
161
|
+
| `aria-describedby` | `string \| null` | `null` | Connects helper or error text outside the group. |
|
|
162
|
+
| `aria-invalid` | `boolean \| string \| null` | `null` | Marks the group invalid for validation-driven presentations. |
|
|
163
|
+
| `class` | `string` | `''` | Adds utility classes to the wrapped group host. |
|
|
164
|
+
|
|
165
|
+
| Output | Payload |
|
|
166
|
+
| ------------- | -------- |
|
|
167
|
+
| `valueChange` | `string` |
|
|
168
|
+
|
|
169
|
+
Public method: `focus()`.
|
|
170
|
+
|
|
171
|
+
### `RadioComponent`
|
|
172
|
+
|
|
173
|
+
| Input | Type | Default |
|
|
174
|
+
| ---------- | --------- | ------- |
|
|
175
|
+
| `value` | `string` | — |
|
|
176
|
+
| `disabled` | `boolean` | `false` |
|
|
177
|
+
| `class` | `string` | `''` |
|
|
178
|
+
|
|
179
|
+
## Styling and Theming
|
|
180
|
+
|
|
181
|
+
The radio primitives wrap Angular Material's structural radio markup and restyle it through the shared theme tokens.
|
|
182
|
+
|
|
183
|
+
- The selected dot uses the `primary` token.
|
|
184
|
+
- Unselected outlines use the `input` token rather than current text color.
|
|
185
|
+
- The group uses CSS grid; horizontal orientation flips to `grid-flow-col auto-cols-max`.
|
|
186
|
+
- Card-style layouts and invalid treatments are best handled by surrounding `label[Label]` and `FormField` composition.
|
|
187
|
+
|
|
188
|
+
## Accessibility
|
|
189
|
+
|
|
190
|
+
- Use a real `fieldset` and `legend` when the radio list answers one grouped question.
|
|
191
|
+
- Wrap each option in `label[Label]` when the whole row or card should toggle the control.
|
|
192
|
+
- Use `aria-describedby` on `RadioGroup` to connect helper or error text rendered outside the option rows.
|
|
193
|
+
- Use `aria-invalid` on `RadioGroup` when validation fails and the group should be announced as invalid.
|
|
194
|
+
|
|
195
|
+
## Keyboard Interactions
|
|
196
|
+
|
|
197
|
+
- `Tab` moves focus to the selected item or first enabled item in the group.
|
|
198
|
+
- Arrow keys move between options using Angular Material's radio-group behavior.
|
|
199
|
+
- `Space` selects the focused option.
|
|
200
|
+
|
|
201
|
+
## Angular Notes
|
|
202
|
+
|
|
203
|
+
- `RadioGroup` owns the selected value and works with both `ngModel` and reactive forms.
|
|
204
|
+
- `Radio` represents only the selectable item; visible copy usually lives beside it in a wrapper label or `FormContent` block.
|
|
205
|
+
- The shadcn `Field` family maps to the existing `form` entrypoint rather than a separate radio-specific helper package.
|
|
206
|
+
|
|
207
|
+
## Source Parity
|
|
208
|
+
|
|
209
|
+
This Angular implementation follows the same high-value shadcn radio-group examples: descriptive rows, choice cards, semantic fieldsets, disabled states, invalid messaging, and RTL. The deliberate difference is the use of the existing `form` and `label` primitives instead of introducing a separate runtime `Field` API.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Resizable
|
|
2
|
+
|
|
3
|
+
Accessible resizable panel groups and layouts with pointer and keyboard support.
|
|
4
|
+
|
|
5
|
+
Use Resizable for split views, workspace layouts, drill-in dashboards, inspector panes, and settings screens where users should control how much space each pane receives.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
ResizableHandleComponent,
|
|
12
|
+
ResizablePanelComponent,
|
|
13
|
+
ResizablePanelGroupComponent,
|
|
14
|
+
} from '@ojiepermana/angular-component/resizable';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Composition
|
|
18
|
+
|
|
19
|
+
The Angular structure mirrors the shadcn composition while using Angular selectors and standalone imports.
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
ResizablePanelGroup
|
|
23
|
+
├── ResizablePanel
|
|
24
|
+
├── ResizableHandle
|
|
25
|
+
└── ResizablePanel
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Basic usage
|
|
29
|
+
|
|
30
|
+
Use `ResizablePanelGroup` as the root, place `ResizablePanel` elements around each separator, and insert `ResizableHandle` between adjacent panels.
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<ResizablePanelGroup orientation="horizontal" class="h-[200px] max-w-lg rounded-lg border border-border">
|
|
34
|
+
<ResizablePanel defaultSize="35%">
|
|
35
|
+
<div class="flex h-full items-center justify-center p-6">Navigation</div>
|
|
36
|
+
</ResizablePanel>
|
|
37
|
+
|
|
38
|
+
<ResizableHandle aria-label="Resize navigation"></ResizableHandle>
|
|
39
|
+
|
|
40
|
+
<ResizablePanel defaultSize="65%">
|
|
41
|
+
<div class="flex h-full items-center justify-center p-6">Content</div>
|
|
42
|
+
</ResizablePanel>
|
|
43
|
+
</ResizablePanelGroup>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Common patterns
|
|
47
|
+
|
|
48
|
+
### Nested workspace layout
|
|
49
|
+
|
|
50
|
+
Nested groups work well for mail, support, or analytics layouts where a secondary area also needs its own split.
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<ResizablePanelGroup orientation="horizontal" class="h-[220px] rounded-lg border border-border">
|
|
54
|
+
<ResizablePanel defaultSize="50%">
|
|
55
|
+
<div class="flex h-full items-center justify-center p-6">Threads</div>
|
|
56
|
+
</ResizablePanel>
|
|
57
|
+
|
|
58
|
+
<ResizableHandle withHandle aria-label="Resize thread list"></ResizableHandle>
|
|
59
|
+
|
|
60
|
+
<ResizablePanel defaultSize="50%">
|
|
61
|
+
<ResizablePanelGroup orientation="vertical" class="h-full">
|
|
62
|
+
<ResizablePanel defaultSize="28%">
|
|
63
|
+
<div class="flex h-full items-center justify-center p-6">Summary</div>
|
|
64
|
+
</ResizablePanel>
|
|
65
|
+
<ResizableHandle withHandle aria-label="Resize summary"></ResizableHandle>
|
|
66
|
+
<ResizablePanel defaultSize="72%">
|
|
67
|
+
<div class="flex h-full items-center justify-center p-6">Details</div>
|
|
68
|
+
</ResizablePanel>
|
|
69
|
+
</ResizablePanelGroup>
|
|
70
|
+
</ResizablePanel>
|
|
71
|
+
</ResizablePanelGroup>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Vertical resizing
|
|
75
|
+
|
|
76
|
+
Use `orientation="vertical"` when panes stack top-to-bottom.
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<ResizablePanelGroup orientation="vertical" class="h-[220px] max-w-sm rounded-lg border border-border">
|
|
80
|
+
<ResizablePanel defaultSize="25%">...</ResizablePanel>
|
|
81
|
+
<ResizableHandle aria-label="Resize header"></ResizableHandle>
|
|
82
|
+
<ResizablePanel defaultSize="75%">...</ResizablePanel>
|
|
83
|
+
</ResizablePanelGroup>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Visible handle
|
|
87
|
+
|
|
88
|
+
Add `withHandle` when the splitter should show a grab affordance.
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<ResizableHandle withHandle aria-label="Resize sidebar"></ResizableHandle>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Size constraints
|
|
95
|
+
|
|
96
|
+
Use `minSize` and `maxSize` to keep navigation, preview, or inspector panes within useful bounds.
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<ResizablePanel defaultSize="30%" minSize="20%" maxSize="40%">...</ResizablePanel>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### RTL
|
|
103
|
+
|
|
104
|
+
Set `dir="rtl"` on `ResizablePanelGroup` or an ancestor when the layout should follow right-to-left direction. Pointer and arrow-key resizing adapt to the computed direction.
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<ResizablePanelGroup dir="rtl" orientation="horizontal" class="h-[200px] rounded-lg border border-border">
|
|
108
|
+
<ResizablePanel defaultSize="50%">...</ResizablePanel>
|
|
109
|
+
<ResizableHandle withHandle aria-label="تغيير حجم القائمة"></ResizableHandle>
|
|
110
|
+
<ResizablePanel defaultSize="50%">...</ResizablePanel>
|
|
111
|
+
</ResizablePanelGroup>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## API reference
|
|
115
|
+
|
|
116
|
+
### `ResizablePanelGroupComponent`
|
|
117
|
+
|
|
118
|
+
| Input | Type | Default |
|
|
119
|
+
| ------------- | ---------------------------- | -------------- |
|
|
120
|
+
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` |
|
|
121
|
+
| `class` | `string` | `''` |
|
|
122
|
+
|
|
123
|
+
### `ResizablePanelComponent`
|
|
124
|
+
|
|
125
|
+
| Input | Type | Default |
|
|
126
|
+
| ------------- | -------------------------- | -------------- |
|
|
127
|
+
| `defaultSize` | `string \| number \| null` | `null` |
|
|
128
|
+
| `minSize` | `string \| number` | `'10%'` |
|
|
129
|
+
| `maxSize` | `string \| number` | `'90%'` |
|
|
130
|
+
| `id` | `string \| null` | auto-generated |
|
|
131
|
+
| `class` | `string` | `''` |
|
|
132
|
+
|
|
133
|
+
### `ResizableHandleComponent`
|
|
134
|
+
|
|
135
|
+
| Input | Type | Default |
|
|
136
|
+
| ------------ | ---------------- | ---------------- |
|
|
137
|
+
| `withHandle` | `boolean` | `false` |
|
|
138
|
+
| `aria-label` | `string \| null` | `'Resize panel'` |
|
|
139
|
+
| `class` | `string` | `''` |
|
|
140
|
+
|
|
141
|
+
## Styling and theming
|
|
142
|
+
|
|
143
|
+
Pass `class` to the group, panel, or handle to tune height, width, backgrounds, borders, and embedded layouts.
|
|
144
|
+
|
|
145
|
+
The primitives use the shared theme tokens, so utilities such as `border-border`, `bg-card`, `bg-muted/40`, `text-foreground`, and `focus-visible:ring-ring` work as expected.
|
|
146
|
+
|
|
147
|
+
## Accessibility
|
|
148
|
+
|
|
149
|
+
- Each handle is a focusable `separator` with `aria-controls`, `aria-valuenow`, `aria-valuemin`, and `aria-valuemax`.
|
|
150
|
+
- Provide a clear `aria-label` for each handle when the surrounding pane label is not already obvious from context.
|
|
151
|
+
- Panels stay in normal page flow, which keeps nested forms and content accessible without extra portals or overlays.
|
|
152
|
+
|
|
153
|
+
## Keyboard interactions
|
|
154
|
+
|
|
155
|
+
- Arrow keys resize the adjacent panes.
|
|
156
|
+
- `Home` moves the primary pane to its minimum allowed size.
|
|
157
|
+
- `End` moves the primary pane to its maximum allowed size.
|
|
158
|
+
- `Enter` collapses the primary pane to its minimum and restores the previous size on the next press.
|
|
159
|
+
|
|
160
|
+
## Angular notes
|
|
161
|
+
|
|
162
|
+
- The API follows the shadcn `orientation` plus percent-size model, but does not require React or `react-resizable-panels`.
|
|
163
|
+
- `defaultSize`, `minSize`, and `maxSize` accept either percentage strings such as `'25%'` or numeric percentages such as `25`.
|
|
164
|
+
- Custom element hosts are explicitly block-level flex items so nested groups and dashboard panes size correctly inside Angular templates.
|
|
165
|
+
|
|
166
|
+
## Source parity
|
|
167
|
+
|
|
168
|
+
This Angular implementation follows the shadcn Resizable composition and examples while translating them to standalone component imports, Angular selectors, and a signal-driven internal layout model.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Scroll Area
|
|
2
|
+
|
|
3
|
+
Native scroll viewport with token-styled scrollbars for constrained lists,
|
|
4
|
+
horizontal galleries, and RTL content. It maps the shadcn Scroll Area pattern to
|
|
5
|
+
Angular while keeping scrolling browser-native.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { ScrollAreaComponent } from '@ojiepermana/angular-component/scroll-area';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Import composed primitives separately when the content needs them:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { ScrollAreaComponent } from '@ojiepermana/angular-component/scroll-area';
|
|
17
|
+
import { SeparatorComponent } from '@ojiepermana/angular-component/separator';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Give the host a stable height or width, then place padding on inner content when
|
|
23
|
+
the scrollbar should stay flush with the border.
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<ScrollArea class="h-72 w-48 rounded-md border border-border" viewportAriaLabel="Release tags">
|
|
27
|
+
<div class="p-4">
|
|
28
|
+
<h4 class="mb-4 text-sm font-medium leading-none">Tags</h4>
|
|
29
|
+
@for (tag of tags; track tag) {
|
|
30
|
+
<div class="text-sm">{{ tag }}</div>
|
|
31
|
+
<Separator class="my-2" />
|
|
32
|
+
}
|
|
33
|
+
</div>
|
|
34
|
+
</ScrollArea>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Common Patterns
|
|
38
|
+
|
|
39
|
+
### Basic List
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<ScrollArea class="h-64 w-56 rounded-md border border-border" viewportAriaLabel="Activity log">
|
|
43
|
+
<ol class="space-y-3 p-4 text-sm">
|
|
44
|
+
@for (event of events; track event.id) {
|
|
45
|
+
<li>{{ event.label }}</li>
|
|
46
|
+
}
|
|
47
|
+
</ol>
|
|
48
|
+
</ScrollArea>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Horizontal Content
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<ScrollArea class="w-full max-w-md rounded-md border border-border" viewportAriaLabel="Artwork gallery">
|
|
55
|
+
<div class="flex w-max gap-4 p-4">
|
|
56
|
+
@for (artwork of artworks; track artwork.id; let index = $index) {
|
|
57
|
+
<figure class="shrink-0">
|
|
58
|
+
<img
|
|
59
|
+
[ngSrc]="artwork.image"
|
|
60
|
+
[alt]="artwork.alt"
|
|
61
|
+
[priority]="index === 0"
|
|
62
|
+
width="300"
|
|
63
|
+
height="400"
|
|
64
|
+
class="aspect-3/4 h-64 w-48 rounded-md object-cover" />
|
|
65
|
+
<figcaption class="pt-2 text-xs text-muted-foreground">{{ artwork.caption }}</figcaption>
|
|
66
|
+
</figure>
|
|
67
|
+
}
|
|
68
|
+
</div>
|
|
69
|
+
</ScrollArea>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### RTL Content
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<ScrollArea
|
|
76
|
+
dir="rtl"
|
|
77
|
+
lang="ar"
|
|
78
|
+
class="h-72 w-48 rounded-md border border-border text-right"
|
|
79
|
+
viewportAriaLabel="قائمة العلامات">
|
|
80
|
+
<div class="p-4">...</div>
|
|
81
|
+
</ScrollArea>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API Reference
|
|
85
|
+
|
|
86
|
+
| Input | Type | Default | Description |
|
|
87
|
+
| ------------------- | ---------------- | ------- | --------------------------------------------------------------------------------------------- |
|
|
88
|
+
| `class` | `string` | `''` | Classes applied to the custom-element host. Use this for size, border, radius, and placement. |
|
|
89
|
+
| `viewportClass` | `string` | `''` | Classes merged onto the native scroll viewport for axis control, whitespace, or overflow. |
|
|
90
|
+
| `viewportAriaLabel` | `string \| null` | `null` | Adds an accessible label and exposes the viewport as a named region. |
|
|
91
|
+
| `viewportTabIndex` | `number` | `0` | Keeps the scroll viewport keyboard-focusable. Use `-1` only when focusable children suffice. |
|
|
92
|
+
|
|
93
|
+
## Styling and Theming
|
|
94
|
+
|
|
95
|
+
The host is block-level, relatively positioned, and overflow-hidden. The inner
|
|
96
|
+
viewport uses native `overflow: auto`, `scrollbar-width: thin` for Firefox, and
|
|
97
|
+
WebKit scrollbar pseudo-elements for Chromium/Safari.
|
|
98
|
+
|
|
99
|
+
Tokens consumed:
|
|
100
|
+
|
|
101
|
+
- `--border` for the default scrollbar thumb.
|
|
102
|
+
- `--muted-foreground` for the hover thumb fallback.
|
|
103
|
+
- `--ring` for keyboard focus indication.
|
|
104
|
+
|
|
105
|
+
Pass `class` for host sizing and borders, and `viewportClass` when the viewport
|
|
106
|
+
itself needs adjustments such as `whitespace-nowrap`, custom padding, or axis
|
|
107
|
+
constraints.
|
|
108
|
+
|
|
109
|
+
## Accessibility
|
|
110
|
+
|
|
111
|
+
The component keeps native wheel, touch, momentum, and keyboard scrolling. The
|
|
112
|
+
viewport is focusable by default with `tabindex="0"`, so keyboard users can
|
|
113
|
+
scroll overflowing content even when there are no focusable descendants.
|
|
114
|
+
|
|
115
|
+
Use `viewportAriaLabel` when the scrollable region needs a spoken name. When a
|
|
116
|
+
label is present, the viewport is exposed as `role="region"`. Keep long lists in
|
|
117
|
+
logical DOM order and avoid hiding important content behind custom-only pointer
|
|
118
|
+
controls.
|
|
119
|
+
|
|
120
|
+
## Keyboard Interactions
|
|
121
|
+
|
|
122
|
+
When the viewport has focus, the browser handles scrolling keys:
|
|
123
|
+
|
|
124
|
+
- Arrow keys move in the matching direction.
|
|
125
|
+
- Page Up and Page Down scroll by a larger step.
|
|
126
|
+
- Home and End jump to the start or end where supported.
|
|
127
|
+
- Space follows the browser's scroll behavior for the focused region.
|
|
128
|
+
|
|
129
|
+
## Angular Notes
|
|
130
|
+
|
|
131
|
+
`ScrollAreaComponent` is standalone and has no provider setup. Use Angular
|
|
132
|
+
`@for` with stable track expressions for long content. For static artwork inside
|
|
133
|
+
horizontal scroll examples, import `NgOptimizedImage` and use `ngSrc`.
|
|
134
|
+
|
|
135
|
+
The component intentionally does not add a child `ScrollBar` directive. Native
|
|
136
|
+
scrollbars are styled by CSS and remain visible only when the content overflows.
|
|
137
|
+
|
|
138
|
+
## Source Parity
|
|
139
|
+
|
|
140
|
+
The shadcn Scroll Area docs show `ScrollArea` plus a `ScrollBar` part. This
|
|
141
|
+
Angular implementation preserves the same usage goals, examples, RTL guidance,
|
|
142
|
+
and themed scrollbar appearance while mapping `ScrollBar` to browser-native
|
|
143
|
+
scrollbars instead of a separate decorative child component.
|