@serenis/icons 2.42.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/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This package contains two categories of content under different licenses:
2
+
3
+ ================================================================================
4
+ 1. CUSTOM ICONS — All SVG components authored by Serenis Health S.r.l.
5
+ ================================================================================
6
+
7
+ Licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
8
+ International (CC BY-NC-ND 4.0).
9
+
10
+ Copyright (c) 2026 Serenis Health S.r.l.
11
+
12
+ You are free to share (copy and redistribute) the custom icons in any medium
13
+ or format, provided you give appropriate credit, do not use them for commercial
14
+ purposes, and do not distribute modified versions.
15
+
16
+ Full license text: https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode
17
+
18
+ ================================================================================
19
+ 2. ICONOIR RE-EXPORTS — Icons re-exported from iconoir-react
20
+ ================================================================================
21
+
22
+ Licensed under the MIT License, inherited from the iconoir-react package.
23
+ See https://github.com/iconoir-icons/iconoir/blob/main/LICENSE for the
24
+ original license text.
package/README.md ADDED
@@ -0,0 +1,313 @@
1
+ # @serenis/icons
2
+
3
+ Icon component library for the Serenis design system. Wraps custom SVG icons and re-exports from iconoir-react.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @serenis/icons
9
+ ```
10
+
11
+ Peer dependencies (must be installed by the consuming app):
12
+
13
+ - `react` >= 19.0.0
14
+ - `@serenis/design-tokens`
15
+
16
+ ## Quick start
17
+
18
+ ```bash
19
+ # Build
20
+ yarn workspace icons build
21
+
22
+ # Dev (watch mode)
23
+ yarn workspace icons dev
24
+
25
+ # Typecheck
26
+ yarn workspace icons typecheck
27
+ ```
28
+
29
+ ```tsx
30
+ import { Icon, Home, IconHome } from '@serenis/icons'
31
+ import { type IconElement, type IconCommonProps, type IconSize } from '@serenis/icons'
32
+
33
+ // Option A: Icon wrapper with raw SVG component (dynamic / slotted usage)
34
+ <Icon Svg={Home} colorName="primary" size={24} />
35
+
36
+ // Option B: Pre-wrapped shorthand (fixed icon usage)
37
+ <IconHome colorName="primary" size={24} />
38
+ ```
39
+
40
+ ## Directory structure
41
+
42
+ ```text
43
+ libraries/icons/
44
+ ├── src/
45
+ │ ├── components/ # One .tsx file per icon + Icon.tsx wrapper
46
+ │ │ ├── Icon.tsx # Design-token-aware wrapper
47
+ │ │ ├── Home.tsx # Example: exports Home + IconHome
48
+ │ │ ├── Agenda.tsx
49
+ │ │ └── ... # custom icon files
50
+ │ ├── stories/
51
+ │ │ ├── Overview.mdx # Storybook documentation page
52
+ │ │ ├── Playground.stories.tsx # Storybook playground for all icons
53
+ │ │ └── ...
54
+ │ ├── types.ts # Shared TypeScript types
55
+ │ └── index.ts # Public API (re-exports everything)
56
+ ├── dist/ # Built output (gitignored)
57
+ ├── tsup.config.ts # Bundle config
58
+ └── package.json
59
+ ```
60
+
61
+ ## Architecture
62
+
63
+ ### Two icon sources
64
+
65
+ The library ships icons from two sources through a single public API:
66
+
67
+ | Source | Where they live | `Icon*` shorthand? |
68
+ | ------------------ | ----------------------------------- | --------------------------------- |
69
+ | **Custom Serenis** | `src/components/*.tsx` (inline SVG) | Yes — e.g. `Home` + `IconHome` |
70
+ | **Iconoir** | Re-exported from `iconoir-react` | No — use via `<Icon Svg={...} />` |
71
+
72
+ Custom icons are hand-maintained inline SVG React components designed specifically for the Serenis product. Iconoir icons fill gaps for generic UI glyphs (arrows, navigation chrome, utility actions) that don't need custom artwork.
73
+
74
+ ### The `Icon` wrapper
75
+
76
+ `Icon` is the bridge between raw SVG components and the design token system. It accepts any `IconElement` and applies design-token-based color and size:
77
+
78
+ ```tsx
79
+ const Icon = ({ Svg, colorName, fillColorName, size }: IconProps): JSX.Element | null => {
80
+ if (Svg == null) {
81
+ return null
82
+ }
83
+
84
+ return (
85
+ <Svg
86
+ color={colorName ? cssvarColor(colorName) : 'currentColor'}
87
+ fill={fillColorName ? cssvarColor(fillColorName) : 'none'}
88
+ height={size}
89
+ width={size}
90
+ />
91
+ )
92
+ }
93
+ ```
94
+
95
+ - **`colorName`** — maps to `color` (stroke) via `cssvarColor()` from `design-tokens`. Omitting it defaults to `currentColor`, inheriting from the parent text color.
96
+ - **`fillColorName`** — maps to `fill` via `cssvarColor()`. Omitting it defaults to `none`. Rarely used (stars, a few nav icons).
97
+ - **`size`** — applied to both `width` and `height`.
98
+
99
+ ### Dual export pattern
100
+
101
+ Every custom icon file exports two things:
102
+
103
+ ```tsx
104
+ // Home.tsx
105
+ export const Home = ({ color, height, width }: IconElementProps) => (
106
+ <svg ...>...</svg>
107
+ )
108
+
109
+ export const IconHome = (props: IconCommonProps) => <Icon Svg={Home} {...props} />
110
+ ```
111
+
112
+ 1. **`Home`** — the raw SVG component. Use when passing as a reference to the `Icon` wrapper or to component props typed as `IconElement` (e.g. Button's `IconStart`).
113
+ 2. **`IconHome`** — pre-wrapped shorthand. Use for direct rendering when you don't need to pass the icon as a reference.
114
+
115
+ Iconoir icons are only exported as raw components (no `Icon*` shorthand).
116
+
117
+ ## Type system
118
+
119
+ All types are exported from the package root:
120
+
121
+ ```tsx
122
+ import {
123
+ type IconElement,
124
+ type IconElementProps,
125
+ type IconCommonProps,
126
+ type IconProps,
127
+ type IconSize,
128
+ } from '@serenis/icons'
129
+ ```
130
+
131
+ | Type | Purpose |
132
+ | ------------------ | ------------------------------------------------------------------------------------------- |
133
+ | `IconSize` | `12 \| 16 \| 20 \| 24 \| 32 \| 40 \| 48 \| 56 \| 64 \| 72 \| 80 \| 120` |
134
+ | `IconElementProps` | Low-level contract for SVG components: `{ color, fill, height, width }` |
135
+ | `IconElement` | Any component that accepts `IconElementProps` — use for props that accept an icon reference |
136
+ | `IconCommonProps` | Token-based API: `{ colorName?, fillColorName?, size }` |
137
+ | `IconProps` | `IconCommonProps & { Svg?: IconElement }` — the `Icon` wrapper props |
138
+
139
+ ### Size scale
140
+
141
+ | Size |
142
+ | ---------------- |
143
+ | 12 |
144
+ | 16 |
145
+ | 20 |
146
+ | 24 **(default)** |
147
+ | 32 |
148
+ | 40 |
149
+ | 48 |
150
+ | 56 |
151
+ | 64 |
152
+ | 72 |
153
+ | 80 |
154
+ | 120 |
155
+
156
+ ## Usage patterns
157
+
158
+ ### Direct rendering
159
+
160
+ ```tsx
161
+ // Pre-wrapped shorthand (custom icons only)
162
+ <IconHome colorName="primary" size={24} />
163
+
164
+ // Icon wrapper (both custom and Iconoir)
165
+ <Icon Svg={Home} colorName="primary" size={24} />
166
+ <Icon Svg={ArrowLeft} colorName="neutral-80" size={24} />
167
+ ```
168
+
169
+ ### Passing icons as props
170
+
171
+ Many `ui` components accept `IconElement` as a prop:
172
+
173
+ ```tsx
174
+ // Button with start icon
175
+ <Button IconStart={Home} kind="primary">Go home</Button>
176
+
177
+ // Tile with required icon
178
+ <Tile Icon={Shield} title="Security" action={<Button>Configure</Button>} />
179
+
180
+ // SideBarItem with active/inactive variants
181
+ <SideBarItem Icon={Agenda} IconActive={AgendaSolid} label="Agenda" active={isActive} />
182
+ ```
183
+
184
+ ### Active / inactive pattern
185
+
186
+ Many custom icons have a Solid variant for active states:
187
+
188
+ ```tsx
189
+ <Icon Svg={active ? AgendaSolid : Agenda} colorName={active ? 'primary-50' : 'neutral-110'} size={24} />
190
+ ```
191
+
192
+ Available pairs: `Agenda`/`AgendaSolid`, `Calendar`/`CalendarSolid`, `Chat`/`ChatSolid`, `Clock`/`ClockSolid`, `Codes`/`CodesSolid`, `Delete`/`DeleteSolid`, `Diary`/`DiarySolid`, `Home`/`HomeSolid`, `Menu`/`MenuSolid`, `Patients`/`PatientsSolid`, `Paths`/`PathsSolid`, and all `Path*`/`Path*Solid` therapy path icons.
193
+
194
+ ### Aliasing when names clash
195
+
196
+ Some icon names conflict with common identifiers. Alias on import:
197
+
198
+ ```tsx
199
+ import { Card as IconCard, Link as IconLink } from '@serenis/icons'
200
+ ```
201
+
202
+ ### Inheriting color from parent
203
+
204
+ Omit `colorName` to use `currentColor` — the icon inherits the parent's text color:
205
+
206
+ ```tsx
207
+ <Text colorName="primary">
208
+ <Icon Svg={Home} size={24} /> {/* stroke is primary */}
209
+ </Text>
210
+ ```
211
+
212
+ ## Accessibility
213
+
214
+ Icons rendered through the `Icon` wrapper are **decorative by default** — they carry no accessible name or role. Meaning is expected to come from adjacent text or from the parent control's label.
215
+
216
+ ### Icon with text (no extra ARIA needed)
217
+
218
+ When an icon appears alongside visible text, the text provides the accessible name. No extra attributes are required on the icon:
219
+
220
+ ```tsx
221
+ <Button IconStart={Home} kind="primary">Go home</Button>
222
+
223
+ <Flex>
224
+ <Icon Svg={Info} size={24} />
225
+ <Text>Important information</Text>
226
+ </Flex>
227
+ ```
228
+
229
+ ### Icon-only controls need `aria-label`
230
+
231
+ When an icon is the **sole content** of a clickable control, the parent element must provide an accessible name via `aria-label` or `aria-labelledby`:
232
+
233
+ ```tsx
234
+ <Button IconStart={Delete} kind="tertiary" aria-label="Delete item" />
235
+
236
+ <Pressable aria-label="Close" onClick={onClose}>
237
+ <Icon Svg={ActionCloseLine} size={24} />
238
+ </Pressable>
239
+ ```
240
+
241
+ ### Why the `Icon` wrapper has no ARIA attributes
242
+
243
+ The `Icon` component intentionally does not set `aria-hidden`, `role`, or any ARIA attribute. Whether an icon is decorative or meaningful depends on the usage context, which only the caller knows. Callers should:
244
+
245
+ - Add `aria-label` to the parent control when the icon is the only content
246
+ - Do nothing when adjacent text already conveys the meaning
247
+ - Avoid adding `aria-hidden="true"` to icons that appear alongside text (the text already provides the name)
248
+
249
+ ### ESLint enforcement
250
+
251
+ The custom ESLint rule `serenis/no-icon-inside-button` (active as `warn` in `libraries/ui`, `apps/web`, `apps/blog`, `apps/website`, `apps/nutrition-blog`) prevents nesting `Icon` or `Icon*` components as children of `Button`. Use the `IconStart` / `IconEnd` props instead — this ensures consistent sizing, loading-state handling, and accessible markup:
252
+
253
+ ```tsx
254
+ // Correct
255
+ <Button IconStart={Home} kind="primary">Go home</Button>
256
+
257
+ // Incorrect — caught by ESLint
258
+ <Button><Icon Svg={Home} size={24} />Go home</Button>
259
+ ```
260
+
261
+ ## How to add a new icon
262
+
263
+ There is no codegen pipeline — icons are hand-maintained TSX files.
264
+
265
+ 1. **Create the component file** at `src/components/MyIcon.tsx`:
266
+
267
+ ```tsx
268
+ import { type IconCommonProps, type IconElementProps } from '../types'
269
+ import Icon from './Icon'
270
+
271
+ type Props = IconElementProps
272
+
273
+ export const MyIcon = ({ color, height, width }: Props) => (
274
+ <svg fill="none" height={height} viewBox="0 0 24 24" width={width} xmlns="http://www.w3.org/2000/svg">
275
+ {/* Paste SVG paths here, use color={color} for strokes */}
276
+ </svg>
277
+ )
278
+
279
+ export const IconMyIcon = (props: IconCommonProps) => <Icon Svg={MyIcon} {...props} />
280
+ ```
281
+
282
+ 1. **Export from `src/index.ts`** (keep alphabetical order):
283
+
284
+ ```tsx
285
+ export { IconMyIcon, MyIcon } from './components/MyIcon'
286
+ ```
287
+
288
+ 1. **Build and typecheck**:
289
+
290
+ ```bash
291
+ yarn workspace icons build
292
+ yarn workspace icons typecheck
293
+ ```
294
+
295
+ The new icon will automatically appear in the Storybook Playground story.
296
+
297
+ ## Build
298
+
299
+ The package is bundled with [tsup](https://tsup.egoist.dev/) into ESM + CJS + TypeScript declarations. Workspace dependencies (`design-tokens`, `iconoir-react`, `react`) are externalized.
300
+
301
+ ## Consumers
302
+
303
+ - **Apps**: `apps/web` (primary consumer), `apps/blog`, `apps/nutrition-blog`, `apps/website`
304
+ - **Libraries**: `libraries/ui` (Button, Modal, Accordion, Breadcrumbs, FileUploadField, etc.), `libraries/shared-components` (Nav)
305
+
306
+ ## Documentation
307
+
308
+ - **Storybook**: Overview page and interactive Playground in `apps/design-system`
309
+ - **AGENTS.md**: Agent-oriented API reference and rules — see [AGENTS.md](AGENTS.md)
310
+
311
+ ## License
312
+
313
+ Custom Serenis SVG icons: CC BY-NC-ND 4.0 — see [LICENSE](./LICENSE) for the full text. Iconoir re-exports retain their upstream MIT license.