@synerise/ds-app-menu 1.2.23 → 1.2.25

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 (3) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/CLAUDE.md +176 -0
  3. package/package.json +7 -6
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.2.25](https://github.com/Synerise/synerise-design/compare/@synerise/ds-app-menu@1.2.24...@synerise/ds-app-menu@1.2.25) (2026-08-11)
7
+
8
+ **Note:** Version bump only for package @synerise/ds-app-menu
9
+
10
+ ## [1.2.24](https://github.com/Synerise/synerise-design/compare/@synerise/ds-app-menu@1.2.23...@synerise/ds-app-menu@1.2.24) (2026-07-23)
11
+
12
+ **Note:** Version bump only for package @synerise/ds-app-menu
13
+
6
14
  ## [1.2.23](https://github.com/Synerise/synerise-design/compare/@synerise/ds-app-menu@1.2.22...@synerise/ds-app-menu@1.2.23) (2026-06-17)
7
15
 
8
16
  **Note:** Version bump only for package @synerise/ds-app-menu
package/CLAUDE.md ADDED
@@ -0,0 +1,176 @@
1
+ # AppMenu (`@synerise/ds-app-menu`)
2
+
3
+ > A fixed left-side icon navigation bar (60px collapsed, 322px expanded) with sliding sub-menu panels, overflow pagination, and a footer slot.
4
+
5
+ ## Package structure
6
+
7
+ ```
8
+ src/
9
+ AppMenu.tsx — root component; owns menu open/active state
10
+ AppMenu.types.ts — AppMenuProps
11
+ AppMenu.styles.ts — MenuWrapper, ItemsWrapper, ItemsDivider
12
+ MenuContext/MenuContext.ts — React Context for open/active state shared down the tree
13
+ NavigableItems/ — handles overflow pagination when items exceed viewport height
14
+ Item/
15
+ Item.tsx — nav item; attaches Icon sub-component
16
+ Item.styles.ts
17
+ Icon/
18
+ Icon.tsx — dual-state icon (active / inactive)
19
+ Icon.styles.ts
20
+ SubMenu/
21
+ SubMenu.tsx — sliding panel; attaches Title, SubTitle, Item
22
+ SubMenu.types.ts
23
+ SubMenu.styles.ts
24
+ SubMenuContext/ — React Context scoped to a single sub-menu
25
+ Item/
26
+ Item.tsx — sub-menu row; closes menu on click; attaches Action
27
+ Item.types.ts
28
+ Item.styles.ts
29
+ hooks/
30
+ useMenu.ts — consumes MenuContext (throws outside provider)
31
+ useSubMenu.ts — consumes SubMenuContext (throws outside provider)
32
+ index.ts — public exports
33
+ ```
34
+
35
+ ## Public exports
36
+
37
+ ### `AppMenu` (default export)
38
+
39
+ | Prop | Type | Default | Description |
40
+ |------|------|---------|-------------|
41
+ | `activeItem` | `string` | **required** | `id` of the initially-active menu item |
42
+ | `children` | `ReactNodeArray` | **required** | `AppMenu.Item` elements rendered in the icon rail |
43
+ | `footer` | `ReactNode` | `undefined` | Content below a divider at the bottom of the rail (e.g. a settings item) |
44
+ | `top` | `number` | `0` | Vertical offset in px from the top of the viewport (e.g. height of a top navbar) |
45
+ | `className` | `string` | `undefined` | Extra CSS class on the outermost wrapper |
46
+
47
+ No `forwardRef`. No imperative handle.
48
+
49
+ The root component is `position: fixed; left: 0`. It owns the open/active state and wraps everything in `MenuContext.Provider`.
50
+
51
+ ### `AppMenu.Item`
52
+
53
+ | Prop | Type | Default | Description |
54
+ |------|------|---------|-------------|
55
+ | `id` | `string` | **required** | Unique identifier; matched against `activeItem` |
56
+ | `name` | `string` | **required** | Text shown in the tooltip (suppressed when sub-menu is open) |
57
+ | `children` | `ReactNode` | `undefined` | Typically `<AppMenu.Item.Icon>` |
58
+ | `subMenu` | `ReactElement` | `undefined` | The `<AppMenu.SubMenu>` to slide in when this item is active |
59
+ | `className` | `string` | `undefined` | Extra CSS class on the `<li>` wrapper |
60
+
61
+ Clicking an already-active item with a subMenu **toggles** the panel; clicking a different item switches active and opens the panel if that item has a subMenu.
62
+
63
+ ### `AppMenu.Item.Icon`
64
+
65
+ | Prop | Type | Default | Description |
66
+ |------|------|---------|-------------|
67
+ | `active` | `ReactNode` | **required** | Icon shown on hover and when item is active |
68
+ | `inActive` | `ReactNode` | **required** | Icon shown when item is not active |
69
+
70
+ Both icons are always in the DOM; toggling is done via CSS opacity transitions (`item__icon--active` / `item__icon--in-active` classes).
71
+
72
+ ### `AppMenu.SubMenu`
73
+
74
+ | Prop | Type | Default | Description |
75
+ |------|------|---------|-------------|
76
+ | `children` | `ReactNode` | `undefined` | `SubMenu.Title`, `SubMenu.SubTitle`, `SubMenu.Item` elements |
77
+ | `className` | `string` | `undefined` | Extra CSS class |
78
+ | `style` | `CSSProperties` | `undefined` | Inline style |
79
+
80
+ Slides in from `left: 61px` as an absolutely-positioned panel (262px wide). Visibility/opacity controlled by `menu__sub-menu--active` class driven by `SubMenuContext`.
81
+
82
+ ### `AppMenu.SubMenu.Title`
83
+
84
+ Styled `h3` (`macro.h600`). Accepts `children` only (it is a styled-component, not a React component with typed props).
85
+
86
+ ### `AppMenu.SubMenu.SubTitle`
87
+
88
+ Styled `h4` (`macro.h100`). Uppercase, grey-500, dashed top border. Accepts `children` only.
89
+
90
+ ### `AppMenu.SubMenu.Item`
91
+
92
+ | Prop | Type | Default | Description |
93
+ |------|------|---------|-------------|
94
+ | `active` | `boolean` | `undefined` | Applies active highlight style |
95
+ | `children` | `ReactNode` | `undefined` | Row content (text, links, etc.) |
96
+
97
+ Clicking calls `setOpened(false)` — **closes the entire menu**. Must be inside `SubMenuContext` (i.e. inside `AppMenu.SubMenu`).
98
+
99
+ `SubMenu.Item.Action` is a `styled.span` attached to the item (`float: right`, fades in on hover) for rendering action buttons inside a row.
100
+
101
+ ### `useMenu` (named export)
102
+
103
+ Reads `MenuContext`. **Throws** `"Cannot use useMenu hook outside MenuContext"` if called outside an `AppMenu` tree. Returns `{ isOpened, setOpened, activeItem, setActiveItem }`.
104
+
105
+ ### `useSubMenu` (named export)
106
+
107
+ Reads `SubMenuContext`. **Throws** `"Cannot use useMenu hook outside MenuContext"` if called outside a `SubMenu` tree. Returns `{ id, isActive, setOpened }`.
108
+
109
+ ## Usage patterns
110
+
111
+ ```tsx
112
+ import AppMenu from '@synerise/ds-app-menu';
113
+ import { DashboardColorM, DashboardGreyM, SettingsColorM, SettingsGreyM } from '@synerise/ds-icon';
114
+
115
+ <AppMenu
116
+ activeItem="settings"
117
+ top={56} // height of top navbar
118
+ footer={
119
+ <AppMenu.Item id="dashboards" name="Dashboards">
120
+ <AppMenu.Item.Icon active={<DashboardColorM />} inActive={<DashboardGreyM />} />
121
+ </AppMenu.Item>
122
+ }
123
+ >
124
+ <AppMenu.Item
125
+ id="settings"
126
+ name="Settings"
127
+ subMenu={
128
+ <AppMenu.SubMenu>
129
+ <AppMenu.SubMenu.Title>Settings</AppMenu.SubMenu.Title>
130
+ <AppMenu.SubMenu.SubTitle>My Account</AppMenu.SubMenu.SubTitle>
131
+ <AppMenu.SubMenu.Item active>Account Details</AppMenu.SubMenu.Item>
132
+ <AppMenu.SubMenu.Item>Business profile</AppMenu.SubMenu.Item>
133
+ </AppMenu.SubMenu>
134
+ }
135
+ >
136
+ <AppMenu.Item.Icon active={<SettingsColorM />} inActive={<SettingsGreyM />} />
137
+ </AppMenu.Item>
138
+ </AppMenu>
139
+ ```
140
+
141
+ ## Context / Provider
142
+
143
+ Two contexts are used internally — no external Provider is needed:
144
+
145
+ - **`MenuContext`** — created in `AppMenu.tsx`. Holds `{ isOpened, setOpened, activeItem, setActiveItem }`. Consumed by `Item`, `NavigableItems`, and the exported `useMenu` hook.
146
+ - **`SubMenuContext`** — created in `Item/Item.tsx` and wrapped around each `subMenu` element. Holds `{ id, isActive, setOpened }`. Consumed by `SubMenu` and `SubMenu.Item`, and the exported `useSubMenu` hook.
147
+
148
+ `useMenu` and `useSubMenu` throw if used outside their respective providers — useful when building custom sub-components.
149
+
150
+ ## Styling
151
+
152
+ All styles in `*.styles.ts` files. Uses `theme.palette` tokens throughout (no hardcoded colours). Notable specifics:
153
+
154
+ - `MenuWrapper`: `position: fixed; left: 0; width: 60px` → `width: 322px` on `.menu--opened`, with a `cubic-bezier` transition and `0.25s` delay.
155
+ - `SubMenu` panel: `position: absolute; left: 61px; width: 262px`. Child elements stagger in using a generated array of 100 `transition-delay` CSS rules.
156
+ - `Icon`: both active/inactive icons are always rendered; CSS `opacity` transitions between them on `.menu__item--active` or hover.
157
+
158
+ ## NavigableItems
159
+
160
+ `NavigableItems` (internal, not exported) measures its container with `getBoundingClientRect` and shows up/down navigation arrows when the total item height exceeds the available space. Item height is hardcoded at `64px`. Navigation triggers a 100ms CSS slide animation and calls `onHideMenu` to close the sub-menu panel.
161
+
162
+ ## Key dependencies
163
+
164
+ - `@synerise/ds-utils` (`useOnClickOutside`) — closes menu and resets `activeItem` to the original `activeId` prop on outside click
165
+ - `@synerise/ds-tooltip` — wraps each `Item`; tooltip title is suppressed (set to `''`) when the item is active and the menu is open
166
+ - `@synerise/ds-icon` — renders both active/inactive icon variants inside `AppMenu.Item.Icon`; also `AngleUpS`/`AngleDownS` for NavigableItems pagination
167
+ - `@synerise/ds-typography` (`macro`) — used in SubMenu heading styles
168
+
169
+ ## Implementation notes
170
+
171
+ - Clicking `SubMenu.Item` closes the **entire** menu (calls `setOpened(false)` from `SubMenuContext`). If you want an item that doesn't close the menu, don't use `SubMenu.Item` — render a plain `div` instead.
172
+ - Clicking outside resets `activeItem` back to the initial `activeId` prop value (not the currently selected item). This means if the user navigates to a different item and then clicks outside, the selection reverts.
173
+ - `top` is used in the inline CSS `height: calc(100% - ${top}px)` and `top: ${top}px`. This is the only way to offset the fixed menu below a top navbar.
174
+ - `footer` content is rendered below an `ItemsDivider` but **outside** the `NavigableItems` scroll container, so footer items are always visible regardless of overflow.
175
+ - `SubMenu` is rendered inside an `Item` via the `subMenu` prop — it is positioned absolutely relative to the `MenuWrapper`, not relative to the `Item`.
176
+ - The component adds `ds-app-menu` as a fixed CSS class on the wrapper alongside `menu` and `menu--opened`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-app-menu",
3
- "version": "1.2.23",
3
+ "version": "1.2.25",
4
4
  "description": "AppMenu UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "Synerise/synerise-design",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "/dist",
19
19
  "CHANGELOG.md",
20
+ "CLAUDE.md",
20
21
  "README.md",
21
22
  "package.json",
22
23
  "LICENSE.md"
@@ -41,10 +42,10 @@
41
42
  ],
42
43
  "types": "dist/index.d.ts",
43
44
  "dependencies": {
44
- "@synerise/ds-icon": "^1.18.4",
45
- "@synerise/ds-tooltip": "^1.5.3",
46
- "@synerise/ds-typography": "^1.1.26",
47
- "@synerise/ds-utils": "^1.10.1"
45
+ "@synerise/ds-icon": "^1.18.5",
46
+ "@synerise/ds-tooltip": "^1.5.5",
47
+ "@synerise/ds-typography": "^1.1.28",
48
+ "@synerise/ds-utils": "^1.10.2"
48
49
  },
49
50
  "peerDependencies": {
50
51
  "@synerise/ds-core": "*",
@@ -52,5 +53,5 @@
52
53
  "styled-components": "^5.3.3",
53
54
  "vitest": "4"
54
55
  },
55
- "gitHead": "d8c64070f58f14e3fb1bfbcbf00d1e3b8fd51eb8"
56
+ "gitHead": "6df24ed12cd5f276d8eccde730591807982e3385"
56
57
  }