@ojiepermana/angular-theme 22.0.36 → 22.0.43

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.
Files changed (46) hide show
  1. package/README.md +25 -12
  2. package/fesm2022/ojiepermana-angular-theme-layout-types.mjs +10 -1
  3. package/fesm2022/ojiepermana-angular-theme-layout-wrapper.mjs +72 -22
  4. package/fesm2022/ojiepermana-angular-theme-layout.mjs +117 -45
  5. package/fesm2022/ojiepermana-angular-theme-page.mjs +251 -101
  6. package/fesm2022/ojiepermana-angular-theme-styles.mjs +19 -0
  7. package/layout/README.md +41 -36
  8. package/package.json +3 -3
  9. package/page/README.md +53 -15
  10. package/styles/README.md +11 -3
  11. package/styles/css/base/components.css +212 -0
  12. package/styles/css/base/theme.css +117 -49
  13. package/styles/css/base/tokens.css +98 -48
  14. package/styles/css/color/amber.css +2 -0
  15. package/styles/css/color/blue.css +2 -0
  16. package/styles/css/color/cyan.css +2 -0
  17. package/styles/css/color/emerald.css +2 -0
  18. package/styles/css/color/fuchsia.css +2 -0
  19. package/styles/css/color/green.css +2 -0
  20. package/styles/css/color/indigo.css +2 -0
  21. package/styles/css/color/lime.css +2 -0
  22. package/styles/css/color/orange.css +2 -0
  23. package/styles/css/color/pink.css +2 -0
  24. package/styles/css/color/purple.css +2 -0
  25. package/styles/css/color/red.css +2 -0
  26. package/styles/css/color/rose.css +2 -0
  27. package/styles/css/color/sky.css +2 -0
  28. package/styles/css/color/teal.css +2 -0
  29. package/styles/css/color/violet.css +2 -0
  30. package/styles/css/color/yellow.css +2 -0
  31. package/styles/css/neutral/gray.css +2 -0
  32. package/styles/css/neutral/mauve.css +2 -0
  33. package/styles/css/neutral/mist.css +2 -0
  34. package/styles/css/neutral/neutral.css +2 -0
  35. package/styles/css/neutral/olive.css +2 -0
  36. package/styles/css/neutral/slate.css +2 -0
  37. package/styles/css/neutral/stone.css +2 -0
  38. package/styles/css/neutral/taupe.css +2 -0
  39. package/styles/css/neutral/zinc.css +2 -0
  40. package/styles/css/space/index.css +6 -3
  41. package/types/ojiepermana-angular-theme-layout-services.d.ts +1 -1
  42. package/types/ojiepermana-angular-theme-layout-types.d.ts +1 -1
  43. package/types/ojiepermana-angular-theme-layout-wrapper.d.ts +40 -7
  44. package/types/ojiepermana-angular-theme-layout.d.ts +4 -2
  45. package/types/ojiepermana-angular-theme-page.d.ts +88 -36
  46. package/types/ojiepermana-angular-theme-styles.d.ts +1 -0
package/README.md CHANGED
@@ -11,13 +11,13 @@ bun add @ojiepermana/angular-theme
11
11
 
12
12
  ## Entry points
13
13
 
14
- | Import path | Contents |
15
- | ----------------------------------------- | ------------------------------------------------------- |
16
- | `@ojiepermana/angular-theme/styles` | `provideUiTheme`, `ThemeModeService`, … |
17
- | `@ojiepermana/angular-theme/layout` | Layout shell building blocks |
18
- | `@ojiepermana/angular-theme/page` | Page-level scaffolding |
19
- | `@ojiepermana/angular-theme/theme.css` | Base tokens + component styles (CSS) |
20
- | `@ojiepermana/angular-theme/styles/css/*` | Raw CSS assets (color + neutral palettes, Tailwind map) |
14
+ | Import path | Contents |
15
+ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
16
+ | `@ojiepermana/angular-theme/styles` | `provideUiTheme`, `ThemeModeService`, `ThemeColorService`, `ThemeBrandService`, `ThemeRadiusService`, `ThemeSpaceService`, |
17
+ | `@ojiepermana/angular-theme/layout` | Layout shell building blocks |
18
+ | `@ojiepermana/angular-theme/page` | Page-level scaffolding |
19
+ | `@ojiepermana/angular-theme/theme.css` | Base tokens + component styles (CSS) |
20
+ | `@ojiepermana/angular-theme/styles/css/*` | Raw CSS assets (color + neutral palettes, Tailwind map) |
21
21
 
22
22
  ## Tailwind v4 setup
23
23
 
@@ -44,24 +44,33 @@ export const appConfig = {
44
44
  mode: 'light',
45
45
  color: 'base', // accent palette (base, red…rose, brand)
46
46
  neutral: 'base', // gray family (base, slate, gray, zinc, …)
47
+ radius: 'md', // corner radius preset (none, sm, md, lg, xl, full)
48
+ space: 'normal', // spacing density preset (compact, normal, relaxed, spacious)
47
49
  brand: { color: '221 83% 53%', foreground: '0 0% 100%' }, // consumer brand
48
50
  }),
49
51
  ],
50
52
  };
51
53
  ```
52
54
 
53
- - `mode` — bootstraps `ThemeModeService` and persists the default mode.
55
+ - `mode` — bootstraps `ThemeModeService` and persists the default mode (`light` / `dark` / `system`).
54
56
  - `color` — bootstraps `ThemeColorService`; initial accent palette (`<html theme-color>`).
55
57
  - `neutral` — initial neutral family (`<html theme-neutral>`); composes with any accent.
58
+ - `radius` — bootstraps `ThemeRadiusService`; initial corner-radius preset (`<html theme-radius>`).
59
+ Drives the single `--radius-base` knob so the whole `--radius-*` scale and `rounded-*`
60
+ utilities follow. Values: `none`, `sm`, `md` (default), `lg`, `xl`, `full`.
61
+ - `space` — bootstraps `ThemeSpaceService`; initial spacing-density preset (`<html theme-space>`).
62
+ Drives the single `--spacing-base` knob so every `p-*` / `m-*` / `gap-*` / `w-*` / `h-*`
63
+ utility follows. Values: `compact`, `normal` (default), `relaxed`, `spacious`.
56
64
  - `brand` — bootstraps `ThemeBrandService`; sets `--brand` / `bg-brand` and the
57
- `theme-color='brand'` accent preset. Settable at runtime via `setBrand()`.
65
+ `theme-color='brand'` accent preset. Accepts an HSL triplet string (`'221 83% 53%'`)
66
+ or `{ color, foreground }`. Settable at runtime via `setBrand()`.
58
67
 
59
- A persisted choice (localStorage `theme-color` / `theme-neutral` / `theme-brand`)
60
- always wins over the configured default.
68
+ A persisted choice (localStorage `theme-color` / `theme-neutral` / `theme-radius` /
69
+ `theme-space` / `theme-brand`) always wins over the configured default.
61
70
 
62
71
  ### Color system (FluxUI-style)
63
72
 
64
- Two independent axes switch at runtime via attribute selectors on `<html>`:
73
+ Four independent axes switch at runtime via attribute selectors on `<html>`:
65
74
 
66
75
  - **accent** (`theme-color`) — `base` (core), `red … rose`, and `brand`. Each
67
76
  re-tints the full palette. `base` = no override.
@@ -69,6 +78,10 @@ Two independent axes switch at runtime via attribute selectors on `<html>`:
69
78
  `stone`, `mauve`, `olive`, `mist`, `taupe`. Overrides only the gray family and is
70
79
  layered after accent so it wins the shared neutral tokens — letting you pair any
71
80
  accent with any neutral.
81
+ - **radius** (`theme-radius`) — `none`, `sm`, `md` (default), `lg`, `xl`, `full`.
82
+ Sets the `--radius-base` knob; the full `--radius-*` scale and `rounded-*` utilities follow.
83
+ - **space** (`theme-space`) — `compact`, `normal` (default), `relaxed`, `spacious`.
84
+ Sets the `--spacing-base` knob; every `p-*` / `m-*` / `gap-*` / `w-*` / `h-*` utility follows.
72
85
 
73
86
  `styles/css/index.css` bundles the core base theme plus every accent and neutral
74
87
  palette, so switching needs no runtime CSS loading.
@@ -1,5 +1,14 @@
1
1
  const LAYOUT_TYPES = ['vertical', 'horizontal', 'empty', 'fluid'];
2
- const LAYOUT_SURFACES = ['flat', 'grid', 'honeycome', 'line-vertical', 'line-horizontal'];
2
+ const LAYOUT_SURFACES = [
3
+ 'flat',
4
+ 'grid',
5
+ 'grid-line',
6
+ 'honeycomb',
7
+ 'matrix',
8
+ 'circuit',
9
+ 'line-vertical',
10
+ 'line-horizontal',
11
+ ];
3
12
  const LAYOUT_APPEARANCES = ['flat', 'border-rail'];
4
13
  const LAYOUT_WIDTHS = ['full', 'wide', 'container', 'fluid'];
5
14
  const LAYOUT_DEFAULT_SURFACE = 'flat';
@@ -1,8 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, booleanAttribute, Component, computed } from '@angular/core';
2
+ import { signal, Service, input, booleanAttribute, Component, computed, inject, effect } from '@angular/core';
3
3
  import { NavigationHeaderInitialComponent, NavigationHeaderTitleComponent, NavigationFooterActionComponent, NavigationFooterInitialComponent, NavigationFooterTitleComponent, NavigationSidebarComponent, NavigationHeaderComponent, NavigationFooterComponent, NavigationDockbarComponent, NavigationNavbarComponent, NavigationFlyoutComponent, NavigationContainerComponent } from '@ojiepermana/angular-navigation';
4
4
  import { IconComponent } from '@ojiepermana/angular-component/icon';
5
5
  import { NgTemplateOutlet } from '@angular/common';
6
+ import { cn } from '@ojiepermana/angular-component/utils';
6
7
  import { LayoutComponent, LayoutVerticalComponent, LayoutHorizontalComponent, LayoutEmptyComponent, LayoutFluidComponent, LayoutNavigationComponent, LayoutContentComponent } from '@ojiepermana/angular-theme/layout';
7
8
  import { LAYOUT_DEFAULT_SURFACE, LAYOUT_DEFAULT_APPEARANCE, LAYOUT_DEFAULT_WIDTH, LAYOUT_DEFAULT_TYPE } from '@ojiepermana/angular-theme/layout/types';
8
9
 
@@ -11,6 +12,31 @@ import { LAYOUT_DEFAULT_SURFACE, LAYOUT_DEFAULT_APPEARANCE, LAYOUT_DEFAULT_WIDTH
11
12
  * strukturnya; nilai konkret (brand/user) disediakan consumer sebagai input.
12
13
  */
13
14
 
15
+ /**
16
+ * Registry identitas brand/user yang dipublikasikan shell (mis. `LayoutWrapperDefault`).
17
+ * Surface lain — seperti apps-launcher di `Page` — membacanya agar menampilkan brand/user
18
+ * yang sama dengan flyout nav tanpa menerima input langsung.
19
+ */
20
+ class LayoutIdentityService {
21
+ brandState = signal(null, /* @ts-ignore */
22
+ ...(ngDevMode ? [{ debugName: "brandState" }] : /* istanbul ignore next */ []));
23
+ userState = signal(null, /* @ts-ignore */
24
+ ...(ngDevMode ? [{ debugName: "userState" }] : /* istanbul ignore next */ []));
25
+ brand = this.brandState.asReadonly();
26
+ user = this.userState.asReadonly();
27
+ setBrand(brand) {
28
+ this.brandState.set(brand);
29
+ }
30
+ setUser(user) {
31
+ this.userState.set(user);
32
+ }
33
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.3", ngImport: i0, type: LayoutIdentityService, deps: [], target: i0.ɵɵFactoryTarget.Service });
34
+ static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.3", ngImport: i0, type: LayoutIdentityService });
35
+ }
36
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImport: i0, type: LayoutIdentityService, decorators: [{
37
+ type: Service
38
+ }] });
39
+
14
40
  /**
15
41
  * Slot brand untuk `<NavigationHeader>`: inisial/logo + judul. Murni input-driven.
16
42
  *
@@ -129,21 +155,27 @@ class LayoutNavSidebar {
129
155
  ...(ngDevMode ? [{ debugName: "brand" }] : /* istanbul ignore next */ []));
130
156
  user = input.required(/* @ts-ignore */
131
157
  ...(ngDevMode ? [{ debugName: "user" }] : /* istanbul ignore next */ []));
132
- /** Appearance dari shell (`[appearance]`); menentukan ketebalan border kanan pemisah konten. */
158
+ /** Appearance dari shell (`[appearance]`); menentukan ketebalan border pemisah. */
133
159
  appearance = input('flat', /* @ts-ignore */
134
160
  ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
161
+ isBorderRail = computed(() => this.appearance() === 'border-rail', /* @ts-ignore */
162
+ ...(ngDevMode ? [{ debugName: "isBorderRail" }] : /* istanbul ignore next */ []));
135
163
  /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` 1.5px, `flat` 1px. */
136
- shellClass = computed(() => this.appearance() === 'border-rail'
137
- ? 'border-r-[1.5px] border-border'
138
- : 'border-r border-border', /* @ts-ignore */
164
+ shellClass = computed(() => this.isBorderRail() ? 'border-r-[1.5px] border-border' : 'border-r border-border', /* @ts-ignore */
139
165
  ...(ngDevMode ? [{ debugName: "shellClass" }] : /* istanbul ignore next */ []));
166
+ /** Header divider menebal jadi 1.5px di `border-rail` (selaras nav/shell); `flat` pakai default. */
167
+ headerClass = computed(() => this.isBorderRail() ? 'border-b-[1.5px] border-border' : '', /* @ts-ignore */
168
+ ...(ngDevMode ? [{ debugName: "headerClass" }] : /* istanbul ignore next */ []));
169
+ /** Footer divider menebal jadi 1.5px di `border-rail`; `px-3` tetap dipertahankan. */
170
+ footerClass = computed(() => this.isBorderRail() ? 'px-3 border-t-[1.5px] border-border' : 'px-3', /* @ts-ignore */
171
+ ...(ngDevMode ? [{ debugName: "footerClass" }] : /* istanbul ignore next */ []));
140
172
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.3", ngImport: i0, type: LayoutNavSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
141
173
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.3", type: LayoutNavSidebar, isStandalone: true, selector: "LayoutNavSidebar", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: true, transformFunction: null }, user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: true, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
142
174
  <NavigationSidebar nav-sidebar-collapse [class]="shellClass()">
143
- <NavigationHeader>
175
+ <NavigationHeader [class]="headerClass()">
144
176
  <LayoutBrand [brand]="brand()" />
145
177
  </NavigationHeader>
146
- <NavigationFooter class="px-3">
178
+ <NavigationFooter [class]="footerClass()">
147
179
  <LayoutUser [user]="user()" detailed />
148
180
  </NavigationFooter>
149
181
  </NavigationSidebar>
@@ -163,10 +195,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
163
195
  host: { class: 'contents' },
164
196
  template: `
165
197
  <NavigationSidebar nav-sidebar-collapse [class]="shellClass()">
166
- <NavigationHeader>
198
+ <NavigationHeader [class]="headerClass()">
167
199
  <LayoutBrand [brand]="brand()" />
168
200
  </NavigationHeader>
169
- <NavigationFooter class="px-3">
201
+ <NavigationFooter [class]="footerClass()">
170
202
  <LayoutUser [user]="user()" detailed />
171
203
  </NavigationFooter>
172
204
  </NavigationSidebar>
@@ -183,21 +215,27 @@ class LayoutNavDockbar {
183
215
  ...(ngDevMode ? [{ debugName: "brand" }] : /* istanbul ignore next */ []));
184
216
  user = input.required(/* @ts-ignore */
185
217
  ...(ngDevMode ? [{ debugName: "user" }] : /* istanbul ignore next */ []));
186
- /** Appearance dari shell (`[appearance]`); menentukan ketebalan border kanan pemisah konten. */
218
+ /** Appearance dari shell (`[appearance]`); menentukan ketebalan border pemisah. */
187
219
  appearance = input('flat', /* @ts-ignore */
188
220
  ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
221
+ isBorderRail = computed(() => this.appearance() === 'border-rail', /* @ts-ignore */
222
+ ...(ngDevMode ? [{ debugName: "isBorderRail" }] : /* istanbul ignore next */ []));
189
223
  /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` 1.5px, `flat` 1px. */
190
- shellClass = computed(() => this.appearance() === 'border-rail'
191
- ? 'border-r-[1.5px] border-border'
192
- : 'border-r border-border', /* @ts-ignore */
224
+ shellClass = computed(() => this.isBorderRail() ? 'border-r-[1.5px] border-border' : 'border-r border-border', /* @ts-ignore */
193
225
  ...(ngDevMode ? [{ debugName: "shellClass" }] : /* istanbul ignore next */ []));
226
+ /** Header divider menebal jadi 1.5px di `border-rail` (selaras nav/shell); `flat` pakai default. */
227
+ headerClass = computed(() => this.isBorderRail() ? 'border-b-[1.5px] border-border' : '', /* @ts-ignore */
228
+ ...(ngDevMode ? [{ debugName: "headerClass" }] : /* istanbul ignore next */ []));
229
+ /** Footer divider menebal jadi 1.5px di `border-rail`; `px-3` tetap dipertahankan. */
230
+ footerClass = computed(() => this.isBorderRail() ? 'px-3 border-t-[1.5px] border-border' : 'px-3', /* @ts-ignore */
231
+ ...(ngDevMode ? [{ debugName: "footerClass" }] : /* istanbul ignore next */ []));
194
232
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.3", ngImport: i0, type: LayoutNavDockbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
195
233
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.3", type: LayoutNavDockbar, isStandalone: true, selector: "LayoutNavDockbar", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: true, transformFunction: null }, user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: true, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
196
234
  <NavigationDockbar [class]="shellClass()">
197
- <NavigationHeader>
235
+ <NavigationHeader [class]="headerClass()">
198
236
  <LayoutBrand [brand]="brand()" compact />
199
237
  </NavigationHeader>
200
- <NavigationFooter class="px-3">
238
+ <NavigationFooter [class]="footerClass()">
201
239
  <LayoutUser [user]="user()" />
202
240
  </NavigationFooter>
203
241
  </NavigationDockbar>
@@ -217,10 +255,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
217
255
  host: { class: 'contents' },
218
256
  template: `
219
257
  <NavigationDockbar [class]="shellClass()">
220
- <NavigationHeader>
258
+ <NavigationHeader [class]="headerClass()">
221
259
  <LayoutBrand [brand]="brand()" compact />
222
260
  </NavigationHeader>
223
- <NavigationFooter class="px-3">
261
+ <NavigationFooter [class]="footerClass()">
224
262
  <LayoutUser [user]="user()" />
225
263
  </NavigationFooter>
226
264
  </NavigationDockbar>
@@ -410,6 +448,18 @@ class LayoutWrapperDefault {
410
448
  /** Identitas user. */
411
449
  user = input.required(/* @ts-ignore */
412
450
  ...(ngDevMode ? [{ debugName: "user" }] : /* istanbul ignore next */ []));
451
+ identity = inject(LayoutIdentityService);
452
+ constructor() {
453
+ // Publikasikan identitas ke registry agar surface lain (mis. apps-launcher di `Page`)
454
+ // menampilkan brand/user yang sama dengan flyout nav.
455
+ effect(() => this.identity.setBrand(this.brand()));
456
+ effect(() => this.identity.setUser(this.user()));
457
+ }
458
+ /**
459
+ * Kelas `<LayoutContent>` yang diatur consumer (mis. padding). Default kosong —
460
+ * library tidak lagi memaksa `p-6`; consumer/`<Page>` yang menentukan spacing.
461
+ */
462
+ contentClassInput = input('', { ...(ngDevMode ? { debugName: "contentClassInput" } : /* istanbul ignore next */ {}), alias: 'content-class' });
413
463
  /** Wrapper layout & nilai `<Layout [layout-type]>`: navbar/flyout = horizontal, sisanya vertical. */
414
464
  effectiveLayoutType = computed(() => {
415
465
  const layoutType = this.layoutType();
@@ -428,11 +478,11 @@ class LayoutWrapperDefault {
428
478
  /** Lebar host `<Navigation>`: penuh-tinggi (vertical) atau penuh-lebar (horizontal). */
429
479
  navClass = computed(() => this.effectiveLayoutType() === 'horizontal' ? 'w-full' : 'h-full', /* @ts-ignore */
430
480
  ...(ngDevMode ? [{ debugName: "navClass" }] : /* istanbul ignore next */ []));
431
- /** Kelas `<LayoutContent>`: `fluid` membatasi lebar & memusatkan; selain itu padding biasa. */
432
- contentClass = computed(() => this.effectiveLayoutType() === 'fluid' ? 'w-full max-w-3xl p-6' : 'p-6', /* @ts-ignore */
481
+ /** Kelas `<LayoutContent>`: `fluid` membatasi lebar & memusatkan; padding diserahkan ke consumer. */
482
+ contentClass = computed(() => cn(this.effectiveLayoutType() === 'fluid' ? 'w-full max-w-3xl' : '', this.contentClassInput()), /* @ts-ignore */
433
483
  ...(ngDevMode ? [{ debugName: "contentClass" }] : /* istanbul ignore next */ []));
434
484
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.3", ngImport: i0, type: LayoutWrapperDefault, deps: [], target: i0.ɵɵFactoryTarget.Component });
435
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.3", type: LayoutWrapperDefault, isStandalone: true, selector: "LayoutWrapperDefault", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "layout-appearance", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, layoutType: { classPropertyName: "layoutType", publicName: "layout-type", isSignal: true, isRequired: false, transformFunction: null }, navType: { classPropertyName: "navType", publicName: "nav-type", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: true, transformFunction: null }, user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
485
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.3", type: LayoutWrapperDefault, isStandalone: true, selector: "LayoutWrapperDefault", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "layout-appearance", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, layoutType: { classPropertyName: "layoutType", publicName: "layout-type", isSignal: true, isRequired: false, transformFunction: null }, navType: { classPropertyName: "navType", publicName: "nav-type", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: true, transformFunction: null }, user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: true, transformFunction: null }, contentClassInput: { classPropertyName: "contentClassInput", publicName: "content-class", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
436
486
  <Layout
437
487
  [surface]="surface()"
438
488
  [layout-appearance]="appearance()"
@@ -575,7 +625,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
575
625
  </Layout>
576
626
  `,
577
627
  }]
578
- }], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout-appearance", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], layoutType: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout-type", required: false }] }], navType: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-type", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], brand: [{ type: i0.Input, args: [{ isSignal: true, alias: "brand", required: true }] }], user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: true }] }] } });
628
+ }], ctorParameters: () => [], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout-appearance", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], layoutType: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout-type", required: false }] }], navType: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-type", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], brand: [{ type: i0.Input, args: [{ isSignal: true, alias: "brand", required: true }] }], user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: true }] }], contentClassInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "content-class", required: false }] }] } });
579
629
 
580
630
  /*
581
631
  * Public API of @ojiepermana/angular-theme/layout/wrapper
@@ -590,4 +640,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
590
640
  * Generated bundle index. Do not edit.
591
641
  */
592
642
 
593
- export { LayoutBrand, LayoutNavDockbar, LayoutNavFlyout, LayoutNavMinimal, LayoutNavNavbar, LayoutNavSidebar, LayoutUser, LayoutWrapperDefault };
643
+ export { LayoutBrand, LayoutIdentityService, LayoutNavDockbar, LayoutNavFlyout, LayoutNavMinimal, LayoutNavNavbar, LayoutNavSidebar, LayoutUser, LayoutWrapperDefault };
@@ -116,6 +116,13 @@ class LayoutComponent {
116
116
  ...(ngDevMode ? [{ debugName: "isBorderRail" }] : /* istanbul ignore next */ []));
117
117
  isFluidFrame = computed(() => this.resolvedWidth() === 'fluid' && this.resolvedType() === 'fluid', /* @ts-ignore */
118
118
  ...(ngDevMode ? [{ debugName: "isFluidFrame" }] : /* istanbul ignore next */ []));
119
+ isFullFlat = computed(() => this.resolvedWidth() === 'full' && !this.isBorderRail(), /* @ts-ignore */
120
+ ...(ngDevMode ? [{ debugName: "isFullFlat" }] : /* istanbul ignore next */ []));
121
+ // The `grid-line` surface forces square frame corners (like `border-rail`) so
122
+ // the mosaic grid runs cleanly to the edges instead of being clipped by the
123
+ // theme-radius curve.
124
+ isGridLineSurface = computed(() => this.resolvedSurface() === 'grid-line', /* @ts-ignore */
125
+ ...(ngDevMode ? [{ debugName: "isGridLineSurface" }] : /* istanbul ignore next */ []));
119
126
  showsInsetRails = computed(() => {
120
127
  const layoutType = this.resolvedType();
121
128
  return this.isBorderRail() && (layoutType === 'horizontal' || layoutType === 'vertical');
@@ -129,7 +136,35 @@ class LayoutComponent {
129
136
  ...(ngDevMode ? [{ debugName: "contentShellClasses" }] : /* istanbul ignore next */ []));
130
137
  hostClasses = computed(() => cn('relative isolate h-dvh w-full min-w-0 box-border overflow-hidden text-foreground', this.isFluidFrame() ? 'grid place-items-center' : 'block', this.surfaceClasses(), this.widthPaddingClasses(), this.class()), /* @ts-ignore */
131
138
  ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
132
- frameClasses = computed(() => cn('relative min-h-0 min-w-0 border-border bg-background/55 backdrop-blur-xs', this.frameSizeClasses(), this.isBorderRail() ? 'overflow-visible border-[1.5px]' : 'overflow-hidden border'), /* @ts-ignore */
139
+ frameClasses = computed(() => {
140
+ const gridLine = this.isGridLineSurface();
141
+ return cn('relative min-h-0 min-w-0 bg-background/55 backdrop-blur-xs', this.frameSizeClasses(),
142
+ // Frame border color:
143
+ // • border-rail → the rail color var (--layout-rail-color / -opacity), so the
144
+ // 1.5px frame border matches the rail lines. Defaults resolve to --border
145
+ // at full opacity (unchanged for most surfaces); the `grid-line` host
146
+ // raises them to the accent (see [data-surface='grid-line'] in
147
+ // base/components.css), so grid-line's rails read brighter + theme-tinted.
148
+ // • grid-line flat → the accent at --layout-grid-line-border-opacity (~0.666).
149
+ // • everything else → --border.
150
+ this.isBorderRail()
151
+ ? 'border-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]'
152
+ : gridLine
153
+ ? 'border-[hsl(var(--accent-foreground)/var(--layout-grid-line-border-opacity))]'
154
+ : 'border-border',
155
+ // flat: the outer frame rounds with the active theme-radius
156
+ // (--layout-frame-radius → --radius-lg → --radius-base). border-rail keeps
157
+ // square corners for its grid-rail aesthetic. `grid-line` is always a clearly
158
+ // bordered square box (rounded-none + a visible border in every width) so the
159
+ // mosaic reads as a framed panel.
160
+ this.isBorderRail()
161
+ ? 'overflow-visible border-[length:var(--layout-rail-width)]'
162
+ : gridLine
163
+ ? 'overflow-hidden border rounded-none'
164
+ : this.isFullFlat()
165
+ ? 'overflow-hidden'
166
+ : 'overflow-hidden border rounded-[var(--layout-frame-radius)]');
167
+ }, /* @ts-ignore */
133
168
  ...(ngDevMode ? [{ debugName: "frameClasses" }] : /* istanbul ignore next */ []));
134
169
  frameLayerClasses = computed(() => cn('col-start-1 row-start-1', this.frameClasses()), /* @ts-ignore */
135
170
  ...(ngDevMode ? [{ debugName: "frameLayerClasses" }] : /* istanbul ignore next */ []));
@@ -168,9 +203,44 @@ class LayoutComponent {
168
203
  surfaceClasses() {
169
204
  switch (this.resolvedSurface()) {
170
205
  case 'grid':
171
- return '[--layout-grid-size:2rem] bg-background bg-[linear-gradient(hsl(var(--layout-grid-line)/var(--layout-grid-line-opacity))_1px,transparent_1px),linear-gradient(to_right,hsl(var(--layout-grid-line)/var(--layout-grid-line-opacity))_1px,transparent_1px)] bg-position-[center_center] bg-size-[var(--layout-grid-size)_var(--layout-grid-size)]';
172
- case 'honeycome':
173
- return 'bg-background bg-[radial-gradient(circle_at_0_0,hsl(var(--layout-grid-line)/var(--layout-grid-dot-opacity))_1px,transparent_1.5px)] bg-position-[center_center] bg-size-[1.25rem_1.25rem]';
206
+ // `grid`: two-axis 1px gradient. The line color is --layout-grid-color,
207
+ // which follows the accent (hue from --accent-foreground at the grid's
208
+ // pinned lightness) where CSS relative color is supported, else falls back
209
+ // to the neutral --layout-grid-line — both at --layout-grid-line-opacity
210
+ // (see base/theme.css). So the grid stays as faint as before but tints with
211
+ // the theme-color.
212
+ return '[--layout-grid-size:2rem] bg-background bg-[linear-gradient(var(--layout-grid-color)_1px,transparent_1px),linear-gradient(to_right,var(--layout-grid-color)_1px,transparent_1px)] bg-position-[center_center] bg-size-[var(--layout-grid-size)_var(--layout-grid-size)]';
213
+ case 'grid-line':
214
+ // `grid-line`: a richer "mosaic" cousin of `grid`, painted by the
215
+ // `.layout-surface-grid-line` mask layer (base/components.css). One mask
216
+ // carries the faint uniform grid PLUS scattered bright highlight squares
217
+ // and a few subtly-lit cells, so the layers stay pixel-aligned. The glow
218
+ // FOLLOWS THE ACCENT (accent hue from --accent-foreground, lightness pinned
219
+ // to --layout-grid-highlight-l so brightness stays put — grayscale in the
220
+ // base theme, tinted under a color theme), like the frame border.
221
+ return 'bg-background layout-surface-grid-line';
222
+ case 'honeycomb':
223
+ // True honeycomb: tileable hexagon outline painted by the
224
+ // `.layout-surface-honeycomb` mask layer (base/components.css), so it
225
+ // stays token-colored across themes. `bg-background` is the base.
226
+ return 'bg-background layout-surface-honeycomb';
227
+ case 'matrix':
228
+ // `matrix`: a scattered binary (0/1) field painted by the
229
+ // `.layout-surface-matrix` mask layer (base/components.css). Unlike
230
+ // the neutral grid surfaces, the glyphs are tinted with the brand
231
+ // `--primary` accent and carry baked-in per-glyph opacity, so the field
232
+ // reads as a soft "digital" signature that recolors with the theme.
233
+ return 'bg-background layout-surface-matrix';
234
+ case 'circuit':
235
+ // `circuit`: a tileable PCB / circuit-board texture — routed traces with
236
+ // 45° bends, via nodes, and concentric ring pads. Painted by the
237
+ // `.layout-surface-circuit` class (base/components.css) as TWO stacked,
238
+ // pixel-aligned mask layers (a single mask only carries alpha, so two
239
+ // colors need two layers): `::before` strokes the neutral copper traces
240
+ // (`--layout-grid-line`) and `::after` paints the via nodes/pads in the
241
+ // brand `--primary` accent — so the board is grayscale but its nodes glow
242
+ // with the theme. Both recolor across themes + dark mode.
243
+ return 'bg-background layout-surface-circuit';
174
244
  case 'line-vertical':
175
245
  return 'bg-background bg-[linear-gradient(to_right,hsl(var(--layout-grid-line)/var(--layout-grid-dot-opacity))_1px,transparent_1px)] bg-position-[center_center] bg-size-[2rem_2rem]';
176
246
  case 'line-horizontal':
@@ -180,6 +250,8 @@ class LayoutComponent {
180
250
  }
181
251
  }
182
252
  widthPaddingClasses() {
253
+ if (this.isFullFlat())
254
+ return '';
183
255
  switch (this.resolvedWidth()) {
184
256
  case 'wide':
185
257
  return 'p-4 lg:p-12';
@@ -188,7 +260,7 @@ class LayoutComponent {
188
260
  case 'fluid':
189
261
  return 'p-4 sm:p-6 lg:p-8';
190
262
  default:
191
- return 'p-4';
263
+ return 'p-4'; // full + border-rail
192
264
  }
193
265
  }
194
266
  frameSizeClasses() {
@@ -213,86 +285,86 @@ class LayoutComponent {
213
285
  <div data-layout-rail-anchor [class]="railAnchorClasses()">
214
286
  <div
215
287
  data-layout-rail-top-left-horizontal
216
- class="absolute top-0 right-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
288
+ class="absolute top-0 right-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
217
289
  ></div>
218
290
  <div
219
291
  data-layout-rail-top-left-vertical
220
- class="absolute bottom-full left-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
292
+ class="absolute bottom-full left-0 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
221
293
  ></div>
222
294
  <div
223
295
  data-layout-rail-top-right-horizontal
224
- class="absolute top-0 left-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
296
+ class="absolute top-0 left-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
225
297
  ></div>
226
298
  <div
227
299
  data-layout-rail-top-right-vertical
228
- class="absolute bottom-full right-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
300
+ class="absolute bottom-full right-0 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
229
301
  ></div>
230
302
  <div
231
303
  data-layout-rail-bottom-left-horizontal
232
- class="absolute bottom-0 right-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
304
+ class="absolute bottom-0 right-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
233
305
  ></div>
234
306
  <div
235
307
  data-layout-rail-bottom-left-vertical
236
- class="absolute top-full left-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
308
+ class="absolute top-full left-0 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
237
309
  ></div>
238
310
  <div
239
311
  data-layout-rail-bottom-right-horizontal
240
- class="absolute bottom-0 left-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
312
+ class="absolute bottom-0 left-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
241
313
  ></div>
242
314
  <div
243
315
  data-layout-rail-bottom-right-vertical
244
- class="absolute top-full right-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
316
+ class="absolute top-full right-0 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
245
317
  ></div>
246
318
 
247
319
  @if (showsInsetRails()) {
248
320
  <div
249
321
  data-layout-horizontal-top-rail
250
- class="absolute inset-x-0 top-12 h-[1.5px] bg-border"
322
+ class="absolute inset-x-0 top-12 h-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
251
323
  ></div>
252
324
  <div
253
325
  data-layout-horizontal-top-left-extension
254
- class="absolute top-12 right-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
326
+ class="absolute top-12 right-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
255
327
  ></div>
256
328
  <div
257
329
  data-layout-horizontal-top-right-extension
258
- class="absolute top-12 left-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
330
+ class="absolute top-12 left-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
259
331
  ></div>
260
332
  <div
261
333
  data-layout-horizontal-bottom-rail
262
- class="absolute inset-x-0 bottom-12 h-[1.5px] bg-border"
334
+ class="absolute inset-x-0 bottom-12 h-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
263
335
  ></div>
264
336
  <div
265
337
  data-layout-horizontal-bottom-left-extension
266
- class="absolute bottom-12 right-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
338
+ class="absolute bottom-12 right-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
267
339
  ></div>
268
340
  <div
269
341
  data-layout-horizontal-bottom-right-extension
270
- class="absolute bottom-12 left-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
342
+ class="absolute bottom-12 left-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
271
343
  ></div>
272
344
 
273
345
  <div
274
346
  data-layout-vertical-left-rail
275
- class="absolute inset-y-0 -left-4 w-[1.5px] bg-border"
347
+ class="absolute inset-y-0 -left-4 w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
276
348
  ></div>
277
349
  <div
278
350
  data-layout-vertical-left-top-extension
279
- class="absolute bottom-full -left-4 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
351
+ class="absolute bottom-full -left-4 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
280
352
  ></div>
281
353
  <div
282
354
  data-layout-vertical-left-bottom-extension
283
- class="absolute top-full -left-4 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
355
+ class="absolute top-full -left-4 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
284
356
  ></div>
285
357
  <div
286
358
  data-layout-vertical-right-rail
287
- class="absolute inset-y-0 -right-4 w-[1.5px] bg-border"
359
+ class="absolute inset-y-0 -right-4 w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
288
360
  ></div>
289
361
  <div
290
362
  data-layout-vertical-right-top-extension
291
- class="absolute bottom-full -right-4 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
363
+ class="absolute bottom-full -right-4 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
292
364
  ></div>
293
365
  <div
294
366
  data-layout-vertical-right-bottom-extension
295
- class="absolute top-full -right-4 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
367
+ class="absolute top-full -right-4 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
296
368
  ></div>
297
369
  }
298
370
  </div>
@@ -333,86 +405,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
333
405
  <div data-layout-rail-anchor [class]="railAnchorClasses()">
334
406
  <div
335
407
  data-layout-rail-top-left-horizontal
336
- class="absolute top-0 right-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
408
+ class="absolute top-0 right-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
337
409
  ></div>
338
410
  <div
339
411
  data-layout-rail-top-left-vertical
340
- class="absolute bottom-full left-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
412
+ class="absolute bottom-full left-0 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
341
413
  ></div>
342
414
  <div
343
415
  data-layout-rail-top-right-horizontal
344
- class="absolute top-0 left-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
416
+ class="absolute top-0 left-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
345
417
  ></div>
346
418
  <div
347
419
  data-layout-rail-top-right-vertical
348
- class="absolute bottom-full right-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
420
+ class="absolute bottom-full right-0 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
349
421
  ></div>
350
422
  <div
351
423
  data-layout-rail-bottom-left-horizontal
352
- class="absolute bottom-0 right-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
424
+ class="absolute bottom-0 right-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
353
425
  ></div>
354
426
  <div
355
427
  data-layout-rail-bottom-left-vertical
356
- class="absolute top-full left-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
428
+ class="absolute top-full left-0 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
357
429
  ></div>
358
430
  <div
359
431
  data-layout-rail-bottom-right-horizontal
360
- class="absolute bottom-0 left-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
432
+ class="absolute bottom-0 left-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
361
433
  ></div>
362
434
  <div
363
435
  data-layout-rail-bottom-right-vertical
364
- class="absolute top-full right-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
436
+ class="absolute top-full right-0 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
365
437
  ></div>
366
438
 
367
439
  @if (showsInsetRails()) {
368
440
  <div
369
441
  data-layout-horizontal-top-rail
370
- class="absolute inset-x-0 top-12 h-[1.5px] bg-border"
442
+ class="absolute inset-x-0 top-12 h-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
371
443
  ></div>
372
444
  <div
373
445
  data-layout-horizontal-top-left-extension
374
- class="absolute top-12 right-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
446
+ class="absolute top-12 right-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
375
447
  ></div>
376
448
  <div
377
449
  data-layout-horizontal-top-right-extension
378
- class="absolute top-12 left-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
450
+ class="absolute top-12 left-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
379
451
  ></div>
380
452
  <div
381
453
  data-layout-horizontal-bottom-rail
382
- class="absolute inset-x-0 bottom-12 h-[1.5px] bg-border"
454
+ class="absolute inset-x-0 bottom-12 h-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
383
455
  ></div>
384
456
  <div
385
457
  data-layout-horizontal-bottom-left-extension
386
- class="absolute bottom-12 right-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
458
+ class="absolute bottom-12 right-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
387
459
  ></div>
388
460
  <div
389
461
  data-layout-horizontal-bottom-right-extension
390
- class="absolute bottom-12 left-full h-[1.5px] w-[calc((100vw-100%)/2)] bg-border"
462
+ class="absolute bottom-12 left-full h-[var(--layout-rail-width)] w-[calc((100vw-100%)/2)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
391
463
  ></div>
392
464
 
393
465
  <div
394
466
  data-layout-vertical-left-rail
395
- class="absolute inset-y-0 -left-4 w-[1.5px] bg-border"
467
+ class="absolute inset-y-0 -left-4 w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
396
468
  ></div>
397
469
  <div
398
470
  data-layout-vertical-left-top-extension
399
- class="absolute bottom-full -left-4 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
471
+ class="absolute bottom-full -left-4 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
400
472
  ></div>
401
473
  <div
402
474
  data-layout-vertical-left-bottom-extension
403
- class="absolute top-full -left-4 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
475
+ class="absolute top-full -left-4 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
404
476
  ></div>
405
477
  <div
406
478
  data-layout-vertical-right-rail
407
- class="absolute inset-y-0 -right-4 w-[1.5px] bg-border"
479
+ class="absolute inset-y-0 -right-4 w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
408
480
  ></div>
409
481
  <div
410
482
  data-layout-vertical-right-top-extension
411
- class="absolute bottom-full -right-4 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
483
+ class="absolute bottom-full -right-4 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
412
484
  ></div>
413
485
  <div
414
486
  data-layout-vertical-right-bottom-extension
415
- class="absolute top-full -right-4 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
487
+ class="absolute top-full -right-4 h-[calc((100dvh-100%)/2)] w-[var(--layout-rail-width)] bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
416
488
  ></div>
417
489
  }
418
490
  </div>