@ojiepermana/angular 21.1.2 → 21.1.5

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/etos/README.md ADDED
@@ -0,0 +1,243 @@
1
+ # Etos Brand
2
+
3
+ Etos implementation lives under `projects/angular/etos`. Published consumers should use `@ojiepermana/angular/etos`.
4
+
5
+ Use this folder for Etos-specific implementation:
6
+
7
+ - `core/` contains Etos provider composition and brand defaults.
8
+ - `themes/` contains Etos color, style, layout, and component-facing CSS.
9
+ - `layouts/` contains Etos shell components.
10
+ - `navigation/`, `components/`, `assets/`, and `docs/` can be added when Etos needs brand-specific behavior beyond shared primitives.
11
+
12
+ Shared services, types, and primitives stay in the generic libraries:
13
+
14
+ - `@ojiepermana/angular/theme`
15
+ - `@ojiepermana/angular/layout`
16
+ - `@ojiepermana/angular/navigation`
17
+
18
+ ## Package usage
19
+
20
+ Published consumers should import Etos through the short public entrypoint:
21
+
22
+ ```ts
23
+ import {
24
+ type EtosBrandOptions,
25
+ EtosHorizontalLayoutComponent,
26
+ EtosThemeSwitcherComponent,
27
+ EtosVerticalLayoutComponent,
28
+ provideEtosBrand,
29
+ } from '@ojiepermana/angular/etos';
30
+ import { LayoutService } from '@ojiepermana/angular/layout';
31
+
32
+ export const etosBrandConfig = {
33
+ theme: {
34
+ mode: 'light',
35
+ style: 'default',
36
+ },
37
+ layout: {
38
+ mode: 'vertical',
39
+ width: 'fixed',
40
+ },
41
+ } satisfies EtosBrandOptions;
42
+
43
+ export const appConfig = {
44
+ providers: [provideEtosBrand(etosBrandConfig)],
45
+ };
46
+ ```
47
+
48
+ `provideEtosBrand()` applies the Etos brand tokens and wires the shared `ThemeService` and `LayoutService` defaults for the app.
49
+
50
+ ```ts
51
+ @Component({
52
+ imports: [EtosHorizontalLayoutComponent, EtosThemeSwitcherComponent, EtosVerticalLayoutComponent],
53
+ template: `
54
+ @switch (layoutMode()) {
55
+ @case ('horizontal') {
56
+ <etos-horizontal-layout>
57
+ <div ui-layout-profile>
58
+ <etos-theme-switcher
59
+ [userInfo]="profileInfo"
60
+ [quickActions]="horizontalQuickActions"
61
+ [notificationShortcut]="horizontalNotificationShortcut" />
62
+ </div>
63
+ </etos-horizontal-layout>
64
+ }
65
+ @default {
66
+ <etos-vertical-layout>
67
+ <div ui-sidebar-footer class="flex items-center gap-3">
68
+ <etos-theme-switcher [userInfo]="profileInfo" [quickActions]="verticalQuickActions" popoverAlign="start" />
69
+
70
+ <div class="flex flex-col gap-px">
71
+ <span>Ojie Permana</span>
72
+ <span>Etos design system navigator</span>
73
+ </div>
74
+ </div>
75
+ </etos-vertical-layout>
76
+ }
77
+ }
78
+ `,
79
+ })
80
+ export class Pages {
81
+ protected readonly layoutMode = inject(LayoutService).mode;
82
+ protected readonly profileInfo = {
83
+ name: 'Ojie Permana',
84
+ subtitle: 'Etos design system navigator',
85
+ avatarSrc: '/avatar-ojie.svg',
86
+ avatarAlt: 'Portrait of Ojie Permana',
87
+ };
88
+ protected readonly horizontalQuickActions = [
89
+ { value: 'sign-out', label: 'Logout', icon: 'logout', tone: 'destructive' },
90
+ ];
91
+ protected readonly verticalQuickActions = [
92
+ { value: 'notifications', label: 'Notifications', icon: 'notifications' },
93
+ { value: 'sign-out', label: 'Logout', icon: 'logout', tone: 'destructive' },
94
+ ];
95
+ protected readonly horizontalNotificationShortcut = {
96
+ value: 'notifications',
97
+ icon: 'notifications',
98
+ ariaLabel: 'Open notifications for Ojie Permana',
99
+ };
100
+ }
101
+ ```
102
+
103
+ ## Using the brand theme
104
+
105
+ The Etos brand theme has two parts:
106
+
107
+ - TypeScript setup through `provideEtosBrand()` for theme and layout defaults.
108
+ - CSS setup through the Etos stylesheet so the `theme-brand="etos"` tokens are available at runtime.
109
+
110
+ App CSS should import the Etos package stylesheet before Tailwind:
111
+
112
+ ```css
113
+ @import '@ojiepermana/angular/etos/styles';
114
+ @import 'tailwindcss';
115
+ @import '@ojiepermana/angular/theme/tailwind/theme.css';
116
+ ```
117
+
118
+ Recommended provider configuration:
119
+
120
+ ```ts
121
+ import { type EtosBrandOptions, provideEtosBrand } from '@ojiepermana/angular/etos';
122
+
123
+ export const etosBrandConfig = {
124
+ theme: {
125
+ mode: 'light',
126
+ style: 'default',
127
+ },
128
+ layout: {
129
+ mode: 'vertical',
130
+ width: 'fixed',
131
+ },
132
+ } satisfies EtosBrandOptions;
133
+
134
+ export const appConfig = {
135
+ providers: [provideEtosBrand(etosBrandConfig)],
136
+ };
137
+ ```
138
+
139
+ From there, switch between `EtosHorizontalLayoutComponent` and `EtosVerticalLayoutComponent` based on the current `LayoutService.mode()` value, as shown above.
140
+
141
+ ## Using the theme switcher
142
+
143
+ `EtosThemeSwitcherComponent` is exported from `@ojiepermana/angular/etos` and can be mounted in either the horizontal topbar or the vertical sidebar footer.
144
+
145
+ Common inputs:
146
+
147
+ - `userInfo`: object input for the user display data: `name`, `subtitle`, `avatarSrc`, and `avatarAlt`.
148
+ - `quickActions`: required array of action items rendered in the popup footer.
149
+ - `notificationShortcut`: object input for the standalone notification trigger shown beside the avatar in horizontal mode.
150
+ - `popoverAlign` and `popoverSide`: override the popover anchor position.
151
+ - `showNotificationShortcut`: legacy boolean shortcut for rendering the default notification button. Prefer `notificationShortcut` when the data should come from the parent.
152
+
153
+ Horizontal usage:
154
+
155
+ ```html
156
+ <div ui-layout-profile class="etos-profile-trigger gap-2 px-0 py-0">
157
+ <etos-theme-switcher
158
+ [userInfo]="profileInfo"
159
+ [quickActions]="horizontalQuickActions"
160
+ [notificationShortcut]="horizontalNotificationShortcut" />
161
+ </div>
162
+ ```
163
+
164
+ In this mode the trigger shows only the avatar or initials plus the standalone notification shortcut.
165
+
166
+ Vertical usage:
167
+
168
+ ```html
169
+ <div ui-sidebar-footer class="flex h-full w-full min-w-0 items-center justify-start gap-3 px-0 py-0">
170
+ <etos-theme-switcher [userInfo]="profileInfo" [quickActions]="verticalQuickActions" popoverAlign="start" />
171
+
172
+ <div class="min-w-0 flex flex-col gap-px">
173
+ <span class="truncate text-sm font-semibold leading-none text-foreground">Ojie Permana</span>
174
+ <span class="truncate text-xs leading-none text-muted-foreground">Etos design system navigator</span>
175
+ </div>
176
+ </div>
177
+ ```
178
+
179
+ Use `popoverAlign="start"` in the vertical sidebar so the popup opens toward the content area instead of clipping against the left viewport edge.
180
+
181
+ Input shape examples:
182
+
183
+ ```ts
184
+ profileInfo = {
185
+ name: 'Ojie Permana',
186
+ subtitle: 'Etos design system navigator',
187
+ avatarSrc: '/avatar-ojie.svg',
188
+ avatarAlt: 'Portrait of Ojie Permana',
189
+ };
190
+
191
+ horizontalNotificationShortcut = {
192
+ value: 'notifications',
193
+ icon: 'notifications',
194
+ ariaLabel: 'Open notifications for Ojie Permana',
195
+ };
196
+
197
+ verticalQuickActions = [
198
+ { value: 'notifications', label: 'Notifications', icon: 'notifications' },
199
+ { value: 'sign-out', label: 'Logout', icon: 'logout', tone: 'destructive' },
200
+ ];
201
+ ```
202
+
203
+ The popup always exposes theme scheme, layout mode, and layout width controls. The quick action area is fully consumer-driven:
204
+
205
+ - Provide the action values and labels you need in `quickActions`.
206
+ - Handle actions such as logout in the app consumer through `(actionSelected)`.
207
+ - When `notificationShortcut.value` matches one of the `quickActions` values, the matching popup item is removed to avoid duplication.
208
+
209
+ Example consumer-owned action handling:
210
+
211
+ ```html
212
+ <etos-theme-switcher
213
+ [userInfo]="profileInfo"
214
+ [quickActions]="verticalQuickActions"
215
+ (actionSelected)="onThemeSwitcherAction($event)" />
216
+ ```
217
+
218
+ ```ts
219
+ onThemeSwitcherAction(action: string): void {
220
+ if (action === 'sign-out') {
221
+ this.authService.logout();
222
+ }
223
+ }
224
+ ```
225
+
226
+ ## Workspace usage in this repository
227
+
228
+ The Etos demo under `projects/demo/etos` already uses the short TypeScript entrypoint:
229
+
230
+ - `src/app/app.config.ts` imports `provideEtosBrand` from `@ojiepermana/angular/etos`.
231
+ - `src/pages/pages.ts` imports `EtosHorizontalLayoutComponent`, `EtosVerticalLayoutComponent`, and `EtosThemeSwitcherComponent` from `@ojiepermana/angular/etos`.
232
+
233
+ For CSS, the workspace demo currently imports the Etos source stylesheet directly:
234
+
235
+ ```css
236
+ @import '../../../angular/etos/themes/index.css';
237
+ @import 'tailwindcss';
238
+ @import '@ojiepermana/angular/theme/tailwind/theme.css';
239
+ ```
240
+
241
+ That local CSS path is intentional while developing inside this monorepo. Angular resolves bare CSS package imports from `node_modules/@ojiepermana/angular`, while TypeScript imports in the demo resolve through `tsconfig.json` path aliases. After the package is built, published, and installed with the latest exports, consumers should use `@ojiepermana/angular/etos/styles`.
242
+
243
+ The older `@ojiepermana/angular/theme/styles/etos.css` path is kept only as a compatibility shim.
@@ -6,7 +6,9 @@
6
6
  --etos-layout-shell-padding: clamp(1rem, 2vw, 2rem);
7
7
  --etos-layout-frame-radius: var(--radius-lg);
8
8
  --etos-layout-frame-shadow: var(--shadow-sm);
9
+ --etos-layout-sidebar-width: 17.5rem;
9
10
  --etos-layout-main-max-width: 80rem;
11
+ --etos-layout-vertical-shell-max-width: calc(var(--etos-layout-sidebar-width) + var(--etos-layout-main-max-width));
10
12
  --etos-layout-empty-max-width: 24rem;
11
13
  --etos-layout-empty-padding: clamp(1.5rem, 4vw, 3rem);
12
14
  --etos-layout-topbar-inline: 0.875rem;
@@ -166,10 +168,19 @@
166
168
  padding: var(--etos-layout-shell-padding);
167
169
  }
168
170
 
171
+ .etos-layout-frame--vertical-fixed {
172
+ max-width: var(--etos-layout-vertical-shell-max-width);
173
+ margin-inline: auto;
174
+ }
175
+
169
176
  .etos-layout-frame--fixed {
170
177
  border: var(--border-width) solid hsl(var(--border));
171
178
  border-radius: var(--etos-layout-frame-radius);
172
179
  box-shadow: var(--etos-layout-frame-shadow);
173
180
  }
181
+
182
+ .etos-layout-main--vertical-fixed {
183
+ margin-inline: 0;
184
+ }
174
185
  }
175
186
  }
@@ -525,7 +525,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
525
525
  }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
526
526
 
527
527
  const buttonVariants = cva([
528
- 'inline-flex items-center justify-center gap-2 whitespace-nowrap',
528
+ 'inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap',
529
529
  'rounded-md text-sm font-medium',
530
530
  'transition-colors',
531
531
  'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',