@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 +24 -0
- package/README.md +313 -0
- package/dist/index.cjs +3645 -0
- package/dist/index.d.cts +706 -0
- package/dist/index.d.ts +706 -0
- package/dist/index.js +3282 -0
- package/package.json +59 -0
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.
|