Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
dist
src
storybook-static
2 changes: 1 addition & 1 deletion .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
stories: ['../stories/**/*.stories.@(js|jsx|ts|tsx)'],
stories: ['../stories/Docs.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-a11y', '@storybook/addon-docs'],
framework: {
name: '@storybook/react-vite',
Expand Down
31 changes: 17 additions & 14 deletions index-template.cjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
const path = require('node:path');

/**
* SVGR index template: generates src/index.ts with named exports.
* Component names starting with a number get an "Icon" prefix (e.g. 42.svg -> Icon42).
*/
function indexTemplate(filePaths) {
const exportEntries = filePaths
.filter((entry) => entry && (entry.path != null || entry.originalPath != null))
.map((entry) => {
const filePath = entry.path ?? entry.originalPath;
const basename = path.basename(filePath, path.extname(filePath));
const exportName = /^\d/.test(basename) ? `Icon${basename}` : basename;
return `export { default as ${exportName} } from './${basename}'`;
});
return exportEntries.join('\n');
function indexTemplate(fileEntries) {
const entries = Array.isArray(fileEntries) ? fileEntries : [];
const filePaths = entries
.map((entry) => (typeof entry === 'string' ? entry : (entry?.path ?? entry?.originalPath)))
.filter(Boolean);

filePaths.sort((a, b) =>
path.basename(a, path.extname(a)).localeCompare(path.basename(b, path.extname(b))),
);

const exportLines = filePaths.map((filePath) => {
const basename = path.basename(filePath, path.extname(filePath));
const exportName = /^\d/.test(basename) ? `Icon${basename}` : basename;
return `export { default as ${exportName} } from './${basename}'`;
});

return `export type { IconSize, IconStrokeWidth } from '../lib/icon-config';\n${exportLines.join('\n')}`;
}

module.exports = indexTemplate;
27 changes: 27 additions & 0 deletions lib/icon-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export type IconSize = '5xl' | '4xl' | '3xl' | '2xl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';

export const ICON_SIZE_MAP: Record<IconSize, number> = {
xs: 10,
sm: 12,
md: 14,
lg: 16,
xl: 18,
'2xl': 20,
'3xl': 24,
'4xl': 28,
'5xl': 32,
};

export const STROKE_WIDTH_MAP: Record<IconSize, number> = {
xs: 0.83,
sm: 1,
md: 1.17,
lg: 1.33,
xl: 1.5,
'2xl': 1.67,
'3xl': 2,
'4xl': 2.33,
'5xl': 2.67,
};

export type IconStrokeWidth = (typeof STROKE_WIDTH_MAP)[IconSize];
96 changes: 96 additions & 0 deletions stories/Docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="Docs" />

# @signozhq/icons

SVG-based React icon components with fixed size presets and custom sizing.

---

## Installation

```bash
# pnpm
pnpm add @signozhq/icons

# npm
npm install @signozhq/icons

# yarn
yarn add @signozhq/icons
```

---

## Usage

Import the icon component you need and render it. Icons accept `size`, `color`, and `strokeWidth` (and standard SVG props).

```tsx
import { ArrowRight } from '@signozhq/icons';

<ArrowRight size="lg" color="#2A2E37" />
```

---

## Fixed sizes (presets)

Use a **preset** when you want icons to match the design system: each preset defines both the icon size (in pixels) and a stroke width so icons stay sharp and consistent across the UI. Pass the preset name to the `size` prop.

### Quick reference

<table style={{ width: '100%', maxWidth: 360, borderCollapse: 'collapse', marginTop: 8, marginBottom: 16, fontSize: 14 }}>
<thead>
<tr style={{ borderBottom: '2px solid var(--docs-border-color, #e5e7eb)' }}>
<th style={{ textAlign: 'left', padding: '10px 12px', fontWeight: 600 }}>Preset</th>
<th style={{ textAlign: 'right', padding: '10px 12px', fontWeight: 600 }}>Size</th>
<th style={{ textAlign: 'right', padding: '10px 12px', fontWeight: 600 }}>Stroke width</th>
</tr>
</thead>
<tbody>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>xs</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>10px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>0.83</td></tr>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>sm</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>12px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>1</td></tr>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>md</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>14px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>1.17</td></tr>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>lg</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>16px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>1.33</td></tr>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>xl</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>18px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>1.5</td></tr>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>2xl</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>20px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>1.67</td></tr>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>3xl</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>24px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>2</td></tr>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>4xl</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>28px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>2.33</td></tr>
<tr style={{ borderBottom: '1px solid var(--docs-border-color, #e5e7eb)' }}><td style={{ padding: '10px 12px' }}><code>5xl</code></td><td style={{ textAlign: 'right', padding: '10px 12px' }}>32px</td><td style={{ textAlign: 'right', padding: '10px 12px' }}>2.67</td></tr>
</tbody>
</table>

### Example

```tsx
<ArrowRight size="sm" /> {/* 12px, stroke 1 */}
<ArrowRight size="lg" /> {/* 16px, stroke 1.33 */}
<ArrowRight size="2xl" /> {/* 20px, stroke 1.67 */}
```

You can override the stroke for a preset with the `strokeWidth` prop if needed.

---

## Custom size

Pass a number to `size` for any pixel dimension. Stroke width defaults to `2` when using a custom size; override with `strokeWidth` if needed.
Comment thread
AshwinBhatkal marked this conversation as resolved.

```tsx
<ArrowRight size={32} />
<ArrowRight size={48} strokeWidth={1.5} color="currentColor" />
```

---

## Other props

- **`color`** — Stroke/fill color (default: `"currentColor"`).
- **`strokeWidth`** — Override stroke width when using a preset or custom size.
- **`className`** — Applied alongside the default `signoz-icon` class.

---

Browse the **Icons / Gallery** in the sidebar to search and preview all icons and copy snippets.
28 changes: 16 additions & 12 deletions stories/Icons.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import React, { useMemo, useState } from 'react';
import type { IconSize } from '../lib/icon-config';

type GallerySizePreset = IconSize | 'custom';

// Load all generated icon components directly (no barrel file needed). Exclude index.
// Vite provides import.meta.glob at build time.
const iconModules = (
import.meta as unknown as { glob: (p: string, o?: { eager?: boolean }) => Record<string, unknown> }
).glob('../src/*.tsx', { eager: true }) as Record<
Expand Down Expand Up @@ -30,7 +31,8 @@ const meta: Meta = {
layout: 'fullscreen',
docs: {
description: {
component: 'All icons from the library. Use the search to filter by name.',
component:
'Browse and search all icons. For install and usage (fixed sizes, custom size), see **Docs** in the sidebar.',
},
},
},
Expand Down Expand Up @@ -233,8 +235,7 @@ export const Gallery: Story = {
},
render: function IconsGallery() {
const [search, setSearch] = useState('');
type IconSizePreset = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs' | 'custom';
const [sizePreset, setSizePreset] = useState<IconSizePreset>('lg');
const [sizePreset, setSizePreset] = useState<GallerySizePreset>('lg');
const [customPxInput, setCustomPxInput] = useState('24');
const [searchFocused, setSearchFocused] = useState(false);
const [theme, setTheme] = useState<Theme>('light');
Expand Down Expand Up @@ -342,16 +343,19 @@ export const Gallery: Story = {
<span style={themeStyles.sizeLabel}>Size</span>
<select
value={sizePreset}
onChange={(e) => setSizePreset(e.target.value as IconSizePreset)}
onChange={(e) => setSizePreset(e.target.value as GallerySizePreset)}
aria-label="Icon size"
style={themeStyles.sizeSelect}
>
<option value="xs">XS (16px)</option>
<option value="sm">SM (18px)</option>
<option value="md">MD (20px)</option>
<option value="lg">LG (22px)</option>
<option value="xl">XL (24px)</option>
<option value="xxl">XXL (28px)</option>
<option value="xs">XS (10px)</option>
<option value="sm">SM (12px)</option>
<option value="md">MD (14px)</option>
<option value="lg">LG (16px)</option>
<option value="xl">XL (18px)</option>
<option value="2xl">2XL (20px)</option>
<option value="3xl">3XL (24px)</option>
<option value="4xl">4XL (28px)</option>
<option value="5xl">5XL (32px)</option>
<option value="custom">Custom</option>
</select>
{sizePreset === 'custom' && (
Expand Down
27 changes: 14 additions & 13 deletions svgr-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@ module.exports = function template(variables, { tpl }) {
return tpl`
import * as React from 'react';
import { JSX } from 'react/jsx-runtime';

export type IconSize = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';

const ICON_SIZE_MAP: Record<IconSize, number> = {
xs: 16,
sm: 18,
md: 20,
lg: 22,
xl: 24,
xxl: 28,
};
import type { IconSize } from '../lib/icon-config';
import { ICON_SIZE_MAP, STROKE_WIDTH_MAP } from '../lib/icon-config';

export type { IconSize } from '../lib/icon-config';

export interface IconProps extends React.SVGProps<SVGSVGElement> {
size?: IconSize | number;
Expand All @@ -23,7 +16,7 @@ module.exports = function template(variables, { tpl }) {
const ${variables.componentName} = ({
color = 'currentColor',
size,
strokeWidth = 2,
strokeWidth,
className,
...props
}: IconProps): JSX.Element => {
Expand All @@ -37,6 +30,14 @@ module.exports = function template(variables, { tpl }) {
? size
: ICON_SIZE_MAP[size]
: defaultSize;
const resolvedStrokeWidth =
strokeWidth != null
? strokeWidth
: typeof size === 'string' && size in STROKE_WIDTH_MAP
? STROKE_WIDTH_MAP[size]
: size == null
? STROKE_WIDTH_MAP.xs
: 2;

const w = element.props.width != null ? Number(element.props.width) : 24;
const h = element.props.height != null ? Number(element.props.height) : 24;
Expand All @@ -45,7 +46,7 @@ module.exports = function template(variables, { tpl }) {
const elementProps = {
...props,
className: className ? \`signoz-icon \${className}\` : 'signoz-icon',
...(!isCustomIcon && { stroke: color, strokeWidth }),
...(!isCustomIcon && { stroke: color, strokeWidth: resolvedStrokeWidth }),
...(!isCustomIcon && !hasViewBox && { viewBox: viewBoxWhenMissing }),
...(isCustomIcon && { style: { color, ...props.style }, viewBox: viewBoxWhenMissing }),
width: resolvedSize,
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
"strict": true,
"importHelpers": true
},
"include": ["src"],
"include": ["src", "lib"],
"exclude": ["node_modules", "dist"]
}