@ojiepermana/angular 21.1.9 → 21.1.12

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 CHANGED
@@ -33,6 +33,19 @@ If you install the package with `npm install`, `bun add`, `pnpm add`, or `yarn
33
33
  add` directly, peer dependency installation falls back to the package
34
34
  manager's own behavior.
35
35
 
36
+ ## Navigation primitives
37
+
38
+ `@ojiepermana/angular/navigation` exports the shared sidebar and topbar building blocks used by the demo shells. The vertical navigation selector is now `sidebar` and the projected sidebar slots use `sidebar-header` and `sidebar-footer`.
39
+
40
+ ```html
41
+ <sidebar [ariaLabel]="'Primary'" [appearance]="'default'">
42
+ <div sidebar-header>Brand</div>
43
+ <div sidebar-footer>Footer actions</div>
44
+ </sidebar>
45
+ ```
46
+
47
+ Register items through `NavigationService.registerItems(...)`, or pass them directly with the `items` input when composing a local navigation tree.
48
+
36
49
  ## SDK generator in a consumer workspace
37
50
 
38
51
  After `@ojiepermana/angular` is installed, a consumer workspace can scaffold an
@@ -6,7 +6,6 @@ Use this folder for Etos-specific implementation:
6
6
 
7
7
  - `core/` contains Etos provider composition and brand defaults.
8
8
  - `themes/` contains Etos color, style, layout, and component-facing CSS.
9
- - `layouts/` contains Etos shell components.
10
9
  - `navigation/`, `components/`, `assets/`, and `docs/` can be added when Etos needs brand-specific behavior beyond shared primitives.
11
10
 
12
11
  Shared services, types, and primitives stay in the generic libraries:
@@ -20,14 +19,7 @@ Shared services, types, and primitives stay in the generic libraries:
20
19
  Published consumers should import Etos through the short public entrypoint:
21
20
 
22
21
  ```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';
22
+ import { type EtosBrandOptions, EtosThemeSwitcherComponent, provideEtosBrand } from '@ojiepermana/angular/etos';
31
23
 
32
24
  export const etosBrandConfig = {
33
25
  theme: {
@@ -36,7 +28,7 @@ export const etosBrandConfig = {
36
28
  },
37
29
  layout: {
38
30
  mode: 'vertical',
39
- width: 'fixed',
31
+ width: 'container',
40
32
  },
41
33
  } satisfies EtosBrandOptions;
42
34
 
@@ -45,60 +37,7 @@ export const appConfig = {
45
37
  };
46
38
  ```
47
39
 
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
- ```
40
+ `provideEtosBrand()` applies the Etos brand tokens and wires the shared `ThemeService` and `LayoutService` defaults for the app. `EtosThemeSwitcherComponent` remains the Etos-owned UI component exported from this entrypoint.
102
41
 
103
42
  ## Using the brand theme
104
43
 
@@ -127,7 +66,7 @@ export const etosBrandConfig = {
127
66
  },
128
67
  layout: {
129
68
  mode: 'vertical',
130
- width: 'fixed',
69
+ width: 'container',
131
70
  },
132
71
  } satisfies EtosBrandOptions;
133
72
 
@@ -136,7 +75,7 @@ export const appConfig = {
136
75
  };
137
76
  ```
138
77
 
139
- From there, switch between `EtosHorizontalLayoutComponent` and `EtosVerticalLayoutComponent` based on the current `LayoutService.mode()` value, as shown above.
78
+ From there, mount Etos-specific UI such as `EtosThemeSwitcherComponent` inside your existing application shell.
140
79
 
141
80
  ## Using the theme switcher
142
81
 
@@ -166,7 +105,7 @@ In this mode the trigger shows only the avatar or initials plus the standalone n
166
105
  Vertical usage:
167
106
 
168
107
  ```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">
108
+ <div sidebar-footer class="flex h-full w-full min-w-0 items-center justify-start gap-3 px-0 py-0">
170
109
  <etos-theme-switcher [userInfo]="profileInfo" [quickActions]="verticalQuickActions" popoverAlign="start" />
171
110
 
172
111
  <div class="min-w-0 flex flex-col gap-px">
@@ -178,6 +117,15 @@ Vertical usage:
178
117
 
179
118
  Use `popoverAlign="start"` in the vertical sidebar so the popup opens toward the content area instead of clipping against the left viewport edge.
180
119
 
120
+ When composing the shared navigation shell directly, use the renamed sidebar API:
121
+
122
+ ```html
123
+ <sidebar [ariaLabel]="'Primary'">
124
+ <div sidebar-header>Brand</div>
125
+ <div sidebar-footer>Footer actions</div>
126
+ </sidebar>
127
+ ```
128
+
181
129
  Input shape examples:
182
130
 
183
131
  ```ts
@@ -225,10 +173,9 @@ onThemeSwitcherAction(action: string): void {
225
173
 
226
174
  ## Workspace usage in this repository
227
175
 
228
- The Etos demo under `projects/demo/etos` already uses the short TypeScript entrypoint:
176
+ The Etos demo under `projects/demo/etos` already uses the short TypeScript entrypoint for provider setup:
229
177
 
230
178
  - `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
179
 
233
180
  For CSS, the workspace demo currently imports the Etos source stylesheet directly:
234
181
 
@@ -3,6 +3,27 @@
3
3
  */
4
4
  @layer tokens {
5
5
  [theme-brand='etos'] {
6
+ --brand: 0 0% 64%;
7
+ --brand-border-alpha: 0.7;
8
+ --brand-background-alpha: 0.7;
9
+
10
+ --background: 210 38% 98%;
11
+ --surface: 0 0% 89.8%;
12
+ --surface-foreground: 211 56% 12%;
13
+ --foreground: 211 56% 12%;
14
+
15
+ --card: 0 0% 100%;
16
+ --card-foreground: 211 56% 12%;
17
+
18
+ --popover: 0 0% 100%;
19
+ --popover-foreground: 211 56% 12%;
20
+
21
+ --muted: 211 34% 94%;
22
+ --muted-foreground: 211 24% 38%;
23
+
24
+ --border: 211 28% 82%;
25
+ --input: 211 28% 82%;
26
+
6
27
  --primary: 211 95% 29%;
7
28
  --primary-foreground: 0 0% 100%;
8
29
  --accent: 211 95% 96%;
@@ -13,9 +34,40 @@
13
34
  }
14
35
 
15
36
  [data-mode='dark'][theme-brand='etos'] {
37
+ --brand: 0 0% 64%;
38
+ --brand-border-alpha: 0.45;
39
+ --brand-background-alpha: 0.55;
40
+
41
+ --background: 211 38% 9%;
42
+ --surface: 211 22% 18%;
43
+ --surface-foreground: 210 40% 96%;
44
+ --foreground: 210 40% 96%;
45
+
46
+ --card: 211 34% 11%;
47
+ --card-foreground: 210 40% 96%;
48
+
49
+ --popover: 211 34% 11%;
50
+ --popover-foreground: 210 40% 96%;
51
+
52
+ --muted: 211 22% 18%;
53
+ --muted-foreground: 211 18% 70%;
54
+
55
+ --border: 211 24% 24%;
56
+ --input: 211 24% 24%;
57
+
16
58
  --accent: 211 58% 18%;
17
59
  --accent-foreground: 211 90% 84%;
18
60
  --secondary: 211 24% 18%;
19
61
  --secondary-foreground: 211 82% 84%;
20
62
  }
21
63
  }
64
+
65
+ @layer utilities {
66
+ .border-brand {
67
+ border-color: hsl(var(--brand, var(--border)) / var(--brand-border-alpha, 0.7));
68
+ }
69
+
70
+ .bg-brand {
71
+ background-color: hsl(var(--brand, var(--border)) / var(--brand-background-alpha, 0.7));
72
+ }
73
+ }
@@ -19,168 +19,3 @@
19
19
  --etos-layout-frame-shadow: 0 0 0 1px hsl(var(--border));
20
20
  }
21
21
  }
22
-
23
- @layer components {
24
- .etos-layout-host {
25
- display: block;
26
- height: 100dvh;
27
- width: 100%;
28
- overflow: hidden;
29
- background: hsl(var(--background));
30
- color: hsl(var(--foreground));
31
- }
32
-
33
- .etos-layout-host--fixed {
34
- box-sizing: border-box;
35
- }
36
-
37
- .etos-layout-frame {
38
- display: flex;
39
- height: 100%;
40
- width: 100%;
41
- overflow: hidden;
42
- background: hsl(var(--background));
43
- }
44
-
45
- .etos-layout-frame--horizontal {
46
- flex-direction: column;
47
- }
48
-
49
- .etos-layout-main {
50
- min-width: 0;
51
- flex: 1 1 0%;
52
- overflow: auto;
53
- }
54
-
55
- .etos-layout-main--fixed {
56
- width: 100%;
57
- max-width: var(--etos-layout-main-max-width);
58
- margin-inline: auto;
59
- }
60
-
61
- .etos-layout-topbar {
62
- width: 100%;
63
- flex-shrink: 0;
64
- border-bottom: var(--border-width) solid hsl(var(--border));
65
- }
66
-
67
- .etos-layout-topbar-slot {
68
- display: flex;
69
- min-width: 0;
70
- align-items: center;
71
- }
72
-
73
- .etos-layout-topbar-slot--end {
74
- justify-content: flex-end;
75
- }
76
-
77
- .etos-layout-empty-host {
78
- display: flex;
79
- min-height: 100dvh;
80
- width: 100%;
81
- align-items: center;
82
- justify-content: center;
83
- padding: var(--etos-layout-empty-padding);
84
- background: hsl(var(--background));
85
- color: hsl(var(--foreground));
86
- }
87
-
88
- .etos-layout-empty-main {
89
- width: 100%;
90
- max-width: var(--etos-layout-empty-max-width);
91
- }
92
-
93
- .etos-brand-link,
94
- .etos-profile-trigger {
95
- display: inline-flex;
96
- align-items: center;
97
- gap: 0.75rem;
98
- min-height: 2.5rem;
99
- padding: 0.375rem 0.5rem;
100
- color: hsl(var(--foreground));
101
- text-decoration: none;
102
- transition:
103
- color 160ms ease,
104
- background-color 160ms ease;
105
- }
106
-
107
- .etos-brand-link:focus-visible,
108
- .etos-profile-trigger:focus-visible {
109
- outline: 2px solid hsl(var(--ring));
110
- outline-offset: 2px;
111
- }
112
-
113
- .etos-brand-mark,
114
- .etos-profile-mark {
115
- display: inline-flex;
116
- height: var(--etos-layout-brand-mark-size);
117
- width: var(--etos-layout-brand-mark-size);
118
- align-items: center;
119
- justify-content: center;
120
- border-radius: var(--radius-md);
121
- background: hsl(var(--primary));
122
- color: hsl(var(--primary-foreground));
123
- font-size: 0.7rem;
124
- font-weight: 700;
125
- letter-spacing: 0.18em;
126
- line-height: 1;
127
- }
128
-
129
- .etos-profile-mark {
130
- border-radius: 999px;
131
- letter-spacing: 0;
132
- }
133
-
134
- .etos-brand-copy {
135
- display: none;
136
- min-width: 0;
137
- flex-direction: column;
138
- gap: 0.125rem;
139
- }
140
-
141
- .etos-brand-name {
142
- font-size: var(--text-sm);
143
- font-weight: 700;
144
- line-height: 1;
145
- }
146
-
147
- .etos-brand-subtitle,
148
- .etos-profile-name {
149
- font-size: var(--text-xs);
150
- line-height: 1;
151
- color: hsl(var(--muted-foreground));
152
- }
153
-
154
- .etos-profile-name {
155
- display: none;
156
- font-weight: 600;
157
- }
158
-
159
- @media (min-width: 40rem) {
160
- .etos-brand-copy,
161
- .etos-profile-name {
162
- display: flex;
163
- }
164
- }
165
-
166
- @media (min-width: 64rem) {
167
- .etos-layout-host--fixed {
168
- padding: var(--etos-layout-shell-padding);
169
- }
170
-
171
- .etos-layout-frame--vertical-fixed {
172
- max-width: var(--etos-layout-vertical-shell-max-width);
173
- margin-inline: auto;
174
- }
175
-
176
- .etos-layout-frame--fixed {
177
- border: var(--border-width) solid hsl(var(--border));
178
- border-radius: var(--etos-layout-frame-radius);
179
- box-shadow: var(--etos-layout-frame-shadow);
180
- }
181
-
182
- .etos-layout-main--vertical-fixed {
183
- margin-inline: 0;
184
- }
185
- }
186
- }