@iyulab/modern-app 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.12.0] - 2026-08-07
4
+
5
+ ### Added
6
+
7
+ - **`u-info-field` now accepts `value` as a plain HTML attribute**, not just a property binding.
8
+ `value` was declared `attribute: false`, so `<u-info-field value="42">` was silently ignored —
9
+ no error, no warning, the field just stayed blank. Property binding (`.value=${x}`) is
10
+ unaffected and still accepts any type (numbers, dates); the attribute converter only applies
11
+ when the value arrives as a string attribute.
12
+
13
+ - **Sidebar links can point outside the app.** `SidebarLinkConfig` accepted only `href` and could
14
+ not say whether that link was a SPA route. `SidebarLink` rendered a `<u-link>` without passing
15
+ `target` either, so even the one escape the lower layer offered was unreachable from config.
16
+ A same-origin help site placed in the menu had its click intercepted by the router and fell
17
+ through to not-found.
18
+
19
+ Real menus are not all SPA screens: a documentation site, a report endpoint, an admin console
20
+ and a legacy page sit in the same list. `navigate: 'document'` hands the click to the browser;
21
+ `target` is passed through for new-tab links.
22
+
23
+ ```ts
24
+ { type: 'link', icon: 'help', label: 'Help', href: '/help/', navigate: 'document' }
25
+ ```
26
+
27
+ The entry stays an anchor, which is the point — the alternative was a `type: 'button'` with an
28
+ imperative navigation, which loses middle-click and Ctrl+click, address copying, the link role
29
+ for screen readers, and `pattern`-based highlighting, and cannot sit inside a section or group.
30
+
31
+ ### Changed
32
+
33
+ - **`@iyulab/router` requirement raised to `^0.11.0`.** The sidebar `navigate: 'document'` support
34
+ above needs `<u-link navigate="document">`, added in that release; `^0.10.2` does not resolve to
35
+ it (a 0.x range's minor segment behaves like a major boundary under semver).
36
+
37
+ ### Fixed
38
+
39
+ - **`sideEffects` omitted the source-resolved entry barrel.** The allowlist covered the component
40
+ and layout modules but not the barrel that `exports["."]` resolves to. The published artifacts
41
+ were unaffected — the shipped allowlist already covered them — but a consumer resolving this
42
+ package from source (a workspace sibling) could have the barrel elided, dropping the element
43
+ registrations it pulls in. The failure is silent: no error, and unregistered custom elements
44
+ render nothing. The source-form entry points are now declared alongside the artifact ones.
45
+
46
+ ## [0.11.1] - 2026-08-04
47
+
48
+ ### Fixed
49
+
50
+ - 🔴**`@iyulab/components` 의존 범위가 실제로 필요한 것보다 느슨했다** — `^1.21.0` 이었으나
51
+ `0.11.0` 의 **내비 아이콘 폴백은 `u-icon[fallback]`(1.24.0)** 을 쓴다. 범위가 그것을
52
+ «요구»하지 않으므로, components 를 `1.22.x` 에 고정한 소비자에게는 폴백이 **조용히
53
+ 동작하지 않는다** — 아이콘 없는 그룹 항목이 접힌 사이드바에서 다시 빈 버튼이 된다.
54
+ ⇒ `^1.24.0` 으로 좁혔다.
55
+
56
+ ★**이것을 드러낸 것은 CI 였다** — `0.11.0` 태그 직전의 main 빌드가 아직 게시되지 않은
57
+ `1.24.0` 대신 `1.23.0` 을 설치했고, 그 조합에서 폴백 회귀 7건이 실패했다.
58
+ ***«범위가 담는다»와 «범위가 요구한다»는 다르다.***
59
+
3
60
  ## [0.11.0] - 2026-08-04
4
61
 
5
62
  ### Fixed
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.143.0/helpers/esm/decorate.js
2
2
  function e(e, t, n, r) {
3
3
  var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
4
4
  if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.143.0/helpers/esm/decorateMetadata.js
2
2
  function e(e, t) {
3
3
  if (typeof Reflect == "object" && typeof Reflect.metadata == "function") return Reflect.metadata(e, t);
4
4
  }
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { slotHasContent as r } from "../internals/slotted.js";
5
5
  import { styles as i } from "./ActionBar.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { slotHasContent as r } from "../internals/slotted.js";
5
5
  import { styles as i } from "./GroupBox.styles.js";
@@ -25,6 +25,9 @@ export declare class InfoField extends StyledElement<ElementParts> {
25
25
  /**
26
26
  * 값. `null`/`undefined`/빈 문자열이면 `blank` 문구로 대체된다.
27
27
  * ⚠`0`·`false` 는 **대체되지 않는다** — 값이기 때문이다.
28
+ * 속성(`value="…"`)과 프로퍼티(`.value=`) 둘 다 받는다 — HTML 속성은 항상 문자열이라
29
+ * `type: String` 변환기가 붙어도 `.value=${order.quantity}` 같은 비-문자열 프로퍼티
30
+ * 바인딩은 그대로 통과한다(변환기는 속성 파싱에만 관여한다). 렌더는 항상 `String(value)`.
28
31
  */
29
32
  value?: unknown;
30
33
  /** "아직 없음"을 나타낼 문구. */
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./InfoField.styles.js";
5
5
  import { html as i } from "lit";
@@ -28,6 +28,6 @@ var c = class extends n {
28
28
  `;
29
29
  }
30
30
  };
31
- t([o({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), t([o({ attribute: !1 }), e("design:type", Object)], c.prototype, "value", void 0), t([o({ type: String }), e("design:type", Object)], c.prototype, "blank", void 0), t([o({ type: Boolean }), e("design:type", Object)], c.prototype, "numeric", void 0), c = t([a("u-info-field")], c);
31
+ t([o({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), t([o({ type: String }), e("design:type", Object)], c.prototype, "value", void 0), t([o({ type: String }), e("design:type", Object)], c.prototype, "blank", void 0), t([o({ type: Boolean }), e("design:type", Object)], c.prototype, "numeric", void 0), c = t([a("u-info-field")], c);
32
32
  //#endregion
33
33
  export { c as InfoField, s as isBlank };
@@ -10,7 +10,7 @@ type ElementParts = 'host' | 'grid';
10
10
  *
11
11
  * ```html
12
12
  * <u-info-section>
13
- * <u-info-field label="파트" value="일반"></u-info-field>
13
+ * <u-info-field label="파트" .value=${'일반'}></u-info-field>
14
14
  * <u-info-field label="부수" .value=${0} numeric></u-info-field>
15
15
  * </u-info-section>
16
16
  * ```
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./InfoSection.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { styles as i } from "./SidebarButton.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { SidebarLink as i } from "./SidebarLink.js";
@@ -10,6 +10,25 @@ export interface SidebarLinkConfig extends SidebarPermissionGuard {
10
10
  href: string;
11
11
  pattern?: string | URLPattern;
12
12
  styles?: StyleMap<ElementParts>;
13
+ /**
14
+ * 라우터가 가로채지 않고 브라우저의 문서 이동에 맡긴다 — 같은 오리진의 비-SPA 경로용.
15
+ *
16
+ * 실무 앱의 메뉴에는 SPA 화면만 서지 않는다: 도움말/문서 사이트, 리포트 출력 엔드포인트,
17
+ * 관리 콘솔, 레거시 페이지가 같은 목록에 선다. 종전에는 그것을 **표현할 수 없어서**
18
+ * 메뉴에 걸면 라우터가 클릭을 가로채 not-found 로 떨어졌다.
19
+ *
20
+ * ```ts
21
+ * { type: 'link', label: '도움말', href: '/help/', navigate: 'document' }
22
+ * ```
23
+ */
24
+ navigate?: 'router' | 'document';
25
+ /**
26
+ * 앵커 `target`. 새 탭으로 열 때 쓴다.
27
+ *
28
+ * ⚠**`navigate` 를 대신하지 못한다** — `_blank` 는 **새 탭을 강제**하므로 「같은 탭에서
29
+ * 다른 문서로」를 표현할 수 없다. 그것이 종전에 유일한 탈출구였고, 그래서 부족했다.
30
+ */
31
+ target?: '_self' | '_blank';
13
32
  }
14
33
  /**
15
34
  * SidebarLink 컴포넌트는 계층형 네비게이션 아이템을 표시합니다.
@@ -29,6 +48,10 @@ export declare class SidebarLink extends StyledElement<ElementParts> {
29
48
  href?: string;
30
49
  /** 네비게이션 패턴 매칭 */
31
50
  pattern?: string | URLPattern;
51
+ /** 라우터가 가로채지 않고 브라우저 문서 이동에 맡긴다 (같은 오리진의 비-SPA 경로) */
52
+ navigate?: 'router' | 'document';
53
+ /** 앵커 target — 새 탭으로 열 때 */
54
+ target?: '_self' | '_blank';
32
55
  render(): import('lit-html').TemplateResult<1>;
33
56
  }
34
57
  export {};
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { styles as i } from "./SidebarLink.styles.js";
@@ -16,7 +16,11 @@ var c = class extends n {
16
16
  }
17
17
  render() {
18
18
  return a`
19
- <u-link .href=${this.href || "#"}>
19
+ <u-link
20
+ .href=${this.href || "#"}
21
+ .navigate=${this.navigate}
22
+ .target=${this.target}
23
+ >
20
24
  <div class="container" part="base" ?compact=${this.compact}>
21
25
  <u-icon part="icon"
22
26
  .name=${this.icon}
@@ -33,6 +37,6 @@ var c = class extends n {
33
37
  t([s({
34
38
  type: Boolean,
35
39
  reflect: !0
36
- }), e("design:type", Object)], c.prototype, "selected", void 0), t([s({ type: Boolean }), e("design:type", Object)], c.prototype, "compact", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "href", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "pattern", void 0), c = t([o("u-sidebar-link")], c);
40
+ }), e("design:type", Object)], c.prototype, "selected", void 0), t([s({ type: Boolean }), e("design:type", Object)], c.prototype, "compact", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "href", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "pattern", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "navigate", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "target", void 0), c = t([o("u-sidebar-link")], c);
37
41
  //#endregion
38
42
  export { c as SidebarLink };
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./SidebarSection.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { property as n } from "lit/decorators.js";
4
4
  import { UElement as r } from "@iyulab/components/dist/components/UElement.js";
5
5
  //#region src/internals/StyledElement.ts
@@ -12,11 +12,13 @@ var i = class extends r {
12
12
  }
13
13
  applyToParts(e) {
14
14
  let t = Object.entries(e);
15
- for (let [e, n] of t) if (n) if (e === "host") this.assignToObject(this, n);
16
- else {
17
- let t = this.renderRoot?.querySelector(`[part="${String(e)}"]`);
18
- if (!t) continue;
19
- this.assignToObject(t, n);
15
+ for (let [e, n] of t) if (n) {
16
+ if (e === "host") this.assignToObject(this, n);
17
+ else {
18
+ let t = this.renderRoot?.querySelector(`[part="${String(e)}"]`);
19
+ if (!t) continue;
20
+ this.assignToObject(t, n);
21
+ }
20
22
  }
21
23
  }
22
24
  assignToObject(e, t) {
@@ -1,7 +1,7 @@
1
1
  import { app as e } from "../App.js";
2
2
  import { filterSidebarItems as t } from "./filterSidebarItems.js";
3
- import n from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
4
- import r from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
3
+ import n from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
4
+ import r from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
5
5
  import { StyledElement as i } from "../internals/StyledElement.js";
6
6
  import "../components/SidebarSection.js";
7
7
  import "../components/SidebarLink.js";
@@ -172,6 +172,8 @@ var g = class extends i {
172
172
  .label="${e.label}"
173
173
  .href="${e.href}"
174
174
  .pattern="${e.pattern}"
175
+ .navigate="${e.navigate}"
176
+ .target="${e.target}"
175
177
  .styles="${e.styles}"
176
178
  ></u-sidebar-link>
177
179
  `;
@@ -5,14 +5,16 @@ function e(e, t) {
5
5
  function t(n, r) {
6
6
  if (!r) return n;
7
7
  let i = r, a = [];
8
- for (let r of n) if (r && e(r, i)) if (r.type === "section" || r.type === "group") {
9
- let e = t(r.items, i);
10
- if (e.length === 0) continue;
11
- a.push({
12
- ...r,
13
- items: e
14
- });
15
- } else a.push(r);
8
+ for (let r of n) if (r && e(r, i)) {
9
+ if (r.type === "section" || r.type === "group") {
10
+ let e = t(r.items, i);
11
+ if (e.length === 0) continue;
12
+ a.push({
13
+ ...r,
14
+ items: e
15
+ });
16
+ } else a.push(r);
17
+ }
16
18
  return a;
17
19
  }
18
20
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/modern-app",
3
3
  "description": "web-framework by iyulab based on lit-element",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -23,6 +23,17 @@
23
23
  ],
24
24
  "type": "module",
25
25
  "types": "dist/index.d.ts",
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "sideEffects": [
30
+ "./dist/components/*.js",
31
+ "./src/components/*.ts",
32
+ "./dist/index.js",
33
+ "./src/index.ts",
34
+ "./dist/layouts/*.js",
35
+ "./src/layouts/*.ts"
36
+ ],
26
37
  "exports": {
27
38
  ".": {
28
39
  "types": "./dist/index.d.ts",
@@ -42,8 +53,8 @@
42
53
  "typecheck": "tsc --noEmit"
43
54
  },
44
55
  "dependencies": {
45
- "@iyulab/components": "^1.21.0",
46
- "@iyulab/router": "^0.10.2",
56
+ "@iyulab/components": "^1.24.0",
57
+ "@iyulab/router": "^0.11.0",
47
58
  "i18next": "^26.3.6",
48
59
  "lit": "^3.3.3"
49
60
  },
@@ -41,6 +41,10 @@ interface SidebarLinkConfig {
41
41
  icon?: string;
42
42
  /** Override the URL matching pattern. Accepts a string or URLPattern. */
43
43
  pattern?: string | URLPattern;
44
+ /** Let the browser navigate instead of the router. For same-origin, non-SPA paths. */
45
+ navigate?: 'router' | 'document';
46
+ /** Anchor target. Use for opening in a new tab. */
47
+ target?: '_self' | '_blank';
44
48
  styles?: StyleMap<'host' | 'base' | 'icon' | 'label'>;
45
49
  }
46
50
  ```
@@ -51,6 +55,22 @@ Example:
51
55
  { type: 'link', icon: 'dashboard', label: 'Dashboard', href: '/' }
52
56
  ```
53
57
 
58
+ #### Linking outside the app
59
+
60
+ Not every entry in a real menu is a SPA screen. A help site, a report endpoint, an
61
+ admin console, a legacy page — these sit in the same list. Mark them so the router
62
+ leaves the click alone:
63
+
64
+ ```typescript
65
+ { type: 'link', icon: 'help', label: 'Help', href: '/help/', navigate: 'document' }
66
+ ```
67
+
68
+ The entry stays an anchor, which is the point: middle-click and Ctrl+click still open
69
+ a tab, the address is copyable, screen readers announce a link, `pattern`-based
70
+ highlighting still works, and it can live inside a section or group. `target: '_blank'`
71
+ is available too, but it is not a substitute — it forces a new tab, so same-tab
72
+ navigation to another document needs `navigate`.
73
+
54
74
  ---
55
75
 
56
76
  ### `SidebarGroupConfig` — `type: 'group'`