@fragments-sdk/ui 0.4.0 → 0.5.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/README.md +98 -2
- package/fragments.json +1 -1
- package/package.json +3 -2
- package/src/components/Accordion/Accordion.fragment.tsx +1 -1
- package/src/components/Alert/Alert.fragment.tsx +1 -1
- package/src/components/AppShell/AppShell.fragment.tsx +4 -4
- package/src/components/Avatar/Avatar.fragment.tsx +2 -2
- package/src/components/Badge/Badge.fragment.tsx +2 -2
- package/src/components/Badge/Badge.module.scss +1 -1
- package/src/components/Box/Box.fragment.tsx +1 -1
- package/src/components/Button/Button.fragment.tsx +2 -2
- package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +153 -0
- package/src/components/Card/Card.fragment.tsx +1 -1
- package/src/components/Chart/Chart.fragment.tsx +213 -0
- package/src/components/Chart/Chart.module.scss +123 -0
- package/src/components/Chart/index.tsx +267 -0
- package/src/components/Checkbox/Checkbox.fragment.tsx +1 -1
- package/src/components/CodeBlock/CodeBlock.fragment.tsx +265 -6
- package/src/components/CodeBlock/CodeBlock.module.scss +141 -3
- package/src/components/CodeBlock/index.tsx +250 -36
- package/src/components/Collapsible/Collapsible.fragment.tsx +199 -0
- package/src/components/Collapsible/Collapsible.module.scss +117 -0
- package/src/components/Collapsible/index.tsx +219 -0
- package/src/components/ColorPicker/ColorPicker.fragment.tsx +196 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +33 -23
- package/src/components/ColorPicker/index.tsx +34 -12
- package/src/components/ConversationList/ConversationList.fragment.tsx +202 -0
- package/src/components/ConversationList/ConversationList.module.scss +160 -0
- package/src/components/ConversationList/index.tsx +254 -0
- package/src/components/Dialog/Dialog.fragment.tsx +3 -3
- package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
- package/src/components/Field/Field.fragment.tsx +3 -3
- package/src/components/Fieldset/Fieldset.fragment.tsx +7 -7
- package/src/components/Form/Form.fragment.tsx +11 -11
- package/src/components/Grid/Grid.fragment.tsx +1 -1
- package/src/components/Header/Header.fragment.tsx +4 -4
- package/src/components/Header/Header.module.scss +9 -10
- package/src/components/Icon/Icon.fragment.tsx +2 -2
- package/src/components/Image/Image.fragment.tsx +2 -2
- package/src/components/Input/Input.fragment.tsx +1 -1
- package/src/components/Input/Input.module.scss +2 -2
- package/src/components/Link/Link.fragment.tsx +1 -1
- package/src/components/List/List.fragment.tsx +2 -2
- package/src/components/Listbox/Listbox.fragment.tsx +1 -1
- package/src/components/Loading/Loading.fragment.tsx +153 -0
- package/src/components/Loading/Loading.module.scss +256 -0
- package/src/components/Loading/index.tsx +236 -0
- package/src/components/Menu/Menu.fragment.tsx +3 -3
- package/src/components/Message/Message.fragment.tsx +200 -0
- package/src/components/Message/Message.module.scss +224 -0
- package/src/components/Message/index.tsx +278 -0
- package/src/components/Popover/Popover.fragment.tsx +4 -4
- package/src/components/Progress/Progress.fragment.tsx +1 -1
- package/src/components/Prompt/Prompt.fragment.tsx +2 -2
- package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
- package/src/components/RadioGroup/RadioGroup.module.scss +7 -4
- package/src/components/Select/Select.fragment.tsx +1 -1
- package/src/components/Select/Select.module.scss +8 -0
- package/src/components/Select/index.tsx +85 -5
- package/src/components/Separator/Separator.fragment.tsx +1 -1
- package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
- package/src/components/Sidebar/Sidebar.module.scss +19 -0
- package/src/components/Sidebar/index.tsx +52 -11
- package/src/components/Skeleton/Skeleton.fragment.tsx +1 -1
- package/src/components/Slider/Slider.fragment.tsx +201 -0
- package/src/components/Stack/Stack.fragment.tsx +194 -0
- package/src/components/Table/Table.fragment.tsx +3 -3
- package/src/components/Tabs/Tabs.fragment.tsx +1 -1
- package/src/components/Tabs/Tabs.module.scss +2 -2
- package/src/components/Text/Text.fragment.tsx +188 -0
- package/src/components/Textarea/Textarea.fragment.tsx +1 -1
- package/src/components/Theme/Theme.fragment.tsx +2 -2
- package/src/components/Theme/ThemeToggle.module.scss +13 -13
- package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +182 -0
- package/src/components/ThinkingIndicator/ThinkingIndicator.module.scss +226 -0
- package/src/components/ThinkingIndicator/index.tsx +258 -0
- package/src/components/Toast/Toast.fragment.tsx +1 -1
- package/src/components/Toggle/Toggle.fragment.tsx +1 -1
- package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +207 -0
- package/src/components/Tooltip/Tooltip.fragment.tsx +3 -3
- package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
- package/src/index.ts +86 -3
- package/src/recipes/AIChat.recipe.ts +266 -0
- package/src/tokens/_computed.scss +212 -0
- package/src/tokens/_density.scss +171 -0
- package/src/tokens/_derive.scss +287 -0
- package/src/tokens/_index.scss +39 -1
- package/src/tokens/_mixins.scss +41 -0
- package/src/tokens/_palettes.scss +185 -0
- package/src/tokens/_radius.scss +107 -0
- package/src/tokens/_seeds.scss +59 -0
- package/src/tokens/_variables.scss +171 -130
- package/src/components/ColorChip/ColorChip.module.scss +0 -165
- package/src/components/ColorChip/index.tsx +0 -157
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fragments-sdk/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Customizable UI components built on Base UI headless primitives",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": "^18.0.0 || ^19.0.0",
|
|
22
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
22
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
23
|
+
"recharts": ">=2.0.0 || >=3.0.0"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
26
|
"@base-ui/react": "^1.0.0",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { defineSegment } from '@fragments/core';
|
|
3
|
-
import { AppShell } from '
|
|
4
|
-
import { Header } from '../Header
|
|
5
|
-
import { Sidebar } from '../Sidebar
|
|
6
|
-
import { ThemeToggle } from '../Theme
|
|
3
|
+
import { AppShell } from '.';
|
|
4
|
+
import { Header } from '../Header';
|
|
5
|
+
import { Sidebar } from '../Sidebar';
|
|
6
|
+
import { ThemeToggle } from '../Theme';
|
|
7
7
|
|
|
8
8
|
// Demo icons
|
|
9
9
|
function HomeIcon() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { defineSegment } from '@fragments/core';
|
|
3
|
-
import { Avatar } from '
|
|
3
|
+
import { Avatar } from '.';
|
|
4
4
|
|
|
5
5
|
export default defineSegment({
|
|
6
6
|
component: Avatar,
|
|
@@ -8,7 +8,7 @@ export default defineSegment({
|
|
|
8
8
|
meta: {
|
|
9
9
|
name: 'Avatar',
|
|
10
10
|
description: 'Visual representation of a user or entity',
|
|
11
|
-
category: '
|
|
11
|
+
category: 'display',
|
|
12
12
|
status: 'stable',
|
|
13
13
|
tags: ['user', 'profile', 'image', 'identity'],
|
|
14
14
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { defineSegment } from '@fragments/core';
|
|
3
|
-
import { Badge } from '
|
|
3
|
+
import { Badge } from '.';
|
|
4
4
|
|
|
5
5
|
export default defineSegment({
|
|
6
6
|
component: Badge,
|
|
@@ -8,7 +8,7 @@ export default defineSegment({
|
|
|
8
8
|
meta: {
|
|
9
9
|
name: 'Badge',
|
|
10
10
|
description: 'Compact label for status, counts, or categorization. Draws attention to metadata without dominating the layout.',
|
|
11
|
-
category: '
|
|
11
|
+
category: 'display',
|
|
12
12
|
status: 'stable',
|
|
13
13
|
tags: ['status', 'label', 'tag', 'count', 'chip'],
|
|
14
14
|
since: '0.1.0',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { defineSegment } from '@fragments/core';
|
|
3
|
-
import { Button } from '
|
|
3
|
+
import { Button } from '.';
|
|
4
4
|
|
|
5
5
|
export default defineSegment({
|
|
6
6
|
component: Button,
|
|
@@ -8,7 +8,7 @@ export default defineSegment({
|
|
|
8
8
|
meta: {
|
|
9
9
|
name: 'Button',
|
|
10
10
|
description: 'Interactive element for user actions and form submissions',
|
|
11
|
-
category: '
|
|
11
|
+
category: 'forms',
|
|
12
12
|
status: 'stable',
|
|
13
13
|
tags: ['action', 'button', 'form', 'interactive'],
|
|
14
14
|
},
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { ButtonGroup } from '.';
|
|
4
|
+
import { Button } from '../Button';
|
|
5
|
+
|
|
6
|
+
export default defineSegment({
|
|
7
|
+
component: ButtonGroup,
|
|
8
|
+
|
|
9
|
+
meta: {
|
|
10
|
+
name: 'ButtonGroup',
|
|
11
|
+
description: 'Groups related buttons together with consistent spacing and alignment. Useful for action bars, toolbars, and related button sets.',
|
|
12
|
+
category: 'forms',
|
|
13
|
+
status: 'stable',
|
|
14
|
+
tags: ['button', 'group', 'toolbar', 'actions', 'layout'],
|
|
15
|
+
since: '0.2.0',
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
usage: {
|
|
19
|
+
when: [
|
|
20
|
+
'Grouping related actions together',
|
|
21
|
+
'Creating toolbars or action bars',
|
|
22
|
+
'Form submit/cancel button pairs',
|
|
23
|
+
'Pagination controls',
|
|
24
|
+
],
|
|
25
|
+
whenNot: [
|
|
26
|
+
'Unrelated buttons (use Stack instead)',
|
|
27
|
+
'Navigation links (use nav element)',
|
|
28
|
+
'Single button (no grouping needed)',
|
|
29
|
+
],
|
|
30
|
+
guidelines: [
|
|
31
|
+
'Keep button groups focused on related actions',
|
|
32
|
+
'Use consistent button variants within a group',
|
|
33
|
+
'Consider visual hierarchy - primary action should stand out',
|
|
34
|
+
'Limit to 2-4 buttons per group for clarity',
|
|
35
|
+
],
|
|
36
|
+
accessibility: [
|
|
37
|
+
'Group provides semantic relationship between buttons',
|
|
38
|
+
'Each button remains individually focusable',
|
|
39
|
+
'Consider using role="group" with aria-label for screen readers',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
props: {
|
|
44
|
+
children: {
|
|
45
|
+
type: 'node',
|
|
46
|
+
description: 'Button elements to group together',
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
gap: {
|
|
50
|
+
type: 'enum',
|
|
51
|
+
description: 'Spacing between buttons',
|
|
52
|
+
values: ['none', 'xs', 'sm', 'md'],
|
|
53
|
+
default: 'sm',
|
|
54
|
+
},
|
|
55
|
+
wrap: {
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
description: 'Allow buttons to wrap to next line',
|
|
58
|
+
default: 'false',
|
|
59
|
+
},
|
|
60
|
+
align: {
|
|
61
|
+
type: 'enum',
|
|
62
|
+
description: 'Alignment of buttons',
|
|
63
|
+
values: ['start', 'center', 'end'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
relations: [
|
|
68
|
+
{ component: 'Button', relationship: 'child', note: 'ButtonGroup contains Button components' },
|
|
69
|
+
{ component: 'Stack', relationship: 'alternative', note: 'Use Stack for more general layout needs' },
|
|
70
|
+
],
|
|
71
|
+
|
|
72
|
+
contract: {
|
|
73
|
+
propsSummary: [
|
|
74
|
+
'children: ReactNode - buttons to group',
|
|
75
|
+
'gap: none|xs|sm|md - spacing between buttons',
|
|
76
|
+
'wrap: boolean - allow wrapping',
|
|
77
|
+
'align: start|center|end - alignment',
|
|
78
|
+
],
|
|
79
|
+
scenarioTags: [
|
|
80
|
+
'layout.group',
|
|
81
|
+
'actions.toolbar',
|
|
82
|
+
'buttons.related',
|
|
83
|
+
],
|
|
84
|
+
a11yRules: ['A11Y_GROUP_LABEL'],
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
variants: [
|
|
88
|
+
{
|
|
89
|
+
name: 'Default',
|
|
90
|
+
description: 'Basic button group with default spacing',
|
|
91
|
+
render: () => (
|
|
92
|
+
<ButtonGroup>
|
|
93
|
+
<Button variant="secondary">Cancel</Button>
|
|
94
|
+
<Button variant="primary">Save</Button>
|
|
95
|
+
</ButtonGroup>
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Gap Variants',
|
|
100
|
+
description: 'Different spacing options',
|
|
101
|
+
render: () => (
|
|
102
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
103
|
+
<ButtonGroup gap="none">
|
|
104
|
+
<Button variant="secondary" size="sm">None</Button>
|
|
105
|
+
<Button variant="secondary" size="sm">Gap</Button>
|
|
106
|
+
</ButtonGroup>
|
|
107
|
+
<ButtonGroup gap="xs">
|
|
108
|
+
<Button variant="secondary" size="sm">XS</Button>
|
|
109
|
+
<Button variant="secondary" size="sm">Gap</Button>
|
|
110
|
+
</ButtonGroup>
|
|
111
|
+
<ButtonGroup gap="sm">
|
|
112
|
+
<Button variant="secondary" size="sm">SM</Button>
|
|
113
|
+
<Button variant="secondary" size="sm">Gap</Button>
|
|
114
|
+
</ButtonGroup>
|
|
115
|
+
<ButtonGroup gap="md">
|
|
116
|
+
<Button variant="secondary" size="sm">MD</Button>
|
|
117
|
+
<Button variant="secondary" size="sm">Gap</Button>
|
|
118
|
+
</ButtonGroup>
|
|
119
|
+
</div>
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Alignment',
|
|
124
|
+
description: 'Different alignment options',
|
|
125
|
+
render: () => (
|
|
126
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
127
|
+
<ButtonGroup align="start">
|
|
128
|
+
<Button variant="secondary" size="sm">Start</Button>
|
|
129
|
+
<Button variant="secondary" size="sm">Aligned</Button>
|
|
130
|
+
</ButtonGroup>
|
|
131
|
+
<ButtonGroup align="center">
|
|
132
|
+
<Button variant="secondary" size="sm">Center</Button>
|
|
133
|
+
<Button variant="secondary" size="sm">Aligned</Button>
|
|
134
|
+
</ButtonGroup>
|
|
135
|
+
<ButtonGroup align="end">
|
|
136
|
+
<Button variant="secondary" size="sm">End</Button>
|
|
137
|
+
<Button variant="secondary" size="sm">Aligned</Button>
|
|
138
|
+
</ButtonGroup>
|
|
139
|
+
</div>
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'Form Actions',
|
|
144
|
+
description: 'Common pattern for form submit/cancel',
|
|
145
|
+
render: () => (
|
|
146
|
+
<ButtonGroup align="end">
|
|
147
|
+
<Button variant="ghost">Cancel</Button>
|
|
148
|
+
<Button variant="primary">Submit</Button>
|
|
149
|
+
</ButtonGroup>
|
|
150
|
+
),
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import {
|
|
4
|
+
LineChart,
|
|
5
|
+
Line,
|
|
6
|
+
BarChart,
|
|
7
|
+
Bar,
|
|
8
|
+
AreaChart,
|
|
9
|
+
Area,
|
|
10
|
+
PieChart,
|
|
11
|
+
Pie,
|
|
12
|
+
Cell,
|
|
13
|
+
XAxis,
|
|
14
|
+
YAxis,
|
|
15
|
+
CartesianGrid,
|
|
16
|
+
} from 'recharts';
|
|
17
|
+
import { ChartContainer, ChartTooltip, ChartLegend } from '.';
|
|
18
|
+
|
|
19
|
+
// Sample data
|
|
20
|
+
const monthlyData = [
|
|
21
|
+
{ month: 'Jan', revenue: 4000, users: 2400 },
|
|
22
|
+
{ month: 'Feb', revenue: 3000, users: 1398 },
|
|
23
|
+
{ month: 'Mar', revenue: 5000, users: 3800 },
|
|
24
|
+
{ month: 'Apr', revenue: 4500, users: 3200 },
|
|
25
|
+
{ month: 'May', revenue: 6000, users: 4300 },
|
|
26
|
+
{ month: 'Jun', revenue: 5500, users: 4100 },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const deviceData = [
|
|
30
|
+
{ device: 'Desktop', sessions: 4500 },
|
|
31
|
+
{ device: 'Mobile', sessions: 3200 },
|
|
32
|
+
{ device: 'Tablet', sessions: 1800 },
|
|
33
|
+
{ device: 'Other', sessions: 500 },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const browserData = [
|
|
37
|
+
{ name: 'Chrome', value: 55, color: 'var(--fui-color-accent)' },
|
|
38
|
+
{ name: 'Firefox', value: 20, color: 'var(--fui-color-info)' },
|
|
39
|
+
{ name: 'Safari', value: 15, color: 'var(--fui-color-success)' },
|
|
40
|
+
{ name: 'Edge', value: 10, color: 'var(--fui-color-warning)' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export default defineSegment({
|
|
44
|
+
component: ChartContainer,
|
|
45
|
+
|
|
46
|
+
meta: {
|
|
47
|
+
name: 'Chart',
|
|
48
|
+
description: 'Composable chart wrapper for recharts with theme-aware tooltips, legends, and color integration.',
|
|
49
|
+
category: 'display',
|
|
50
|
+
status: 'stable',
|
|
51
|
+
tags: ['chart', 'graph', 'data-visualization', 'recharts'],
|
|
52
|
+
since: '0.3.0',
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
usage: {
|
|
56
|
+
when: [
|
|
57
|
+
'Displaying data trends over time',
|
|
58
|
+
'Comparing categorical data',
|
|
59
|
+
'Showing distribution or composition',
|
|
60
|
+
'Dashboard data visualizations',
|
|
61
|
+
],
|
|
62
|
+
whenNot: [
|
|
63
|
+
'Simple numeric values (use Text or Badge)',
|
|
64
|
+
'Progress toward a goal (use Progress)',
|
|
65
|
+
'Tabular data without visualization (use Table)',
|
|
66
|
+
],
|
|
67
|
+
guidelines: [
|
|
68
|
+
'Use ChartContainer to wrap recharts chart components',
|
|
69
|
+
'Define a ChartConfig to map data keys to labels and colors',
|
|
70
|
+
'Use FUI CSS variables for colors so charts adapt to theme changes',
|
|
71
|
+
'Use ChartTooltip and ChartLegend for consistent themed overlays',
|
|
72
|
+
],
|
|
73
|
+
accessibility: [
|
|
74
|
+
'Charts include recharts accessibilityLayer by default',
|
|
75
|
+
'Provide meaningful labels in ChartConfig for screen readers',
|
|
76
|
+
'Consider providing a data table alternative for complex charts',
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
props: {
|
|
81
|
+
config: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
description: 'ChartConfig mapping data keys to labels and colors',
|
|
84
|
+
},
|
|
85
|
+
children: {
|
|
86
|
+
type: 'ReactElement',
|
|
87
|
+
description: 'A recharts chart component (LineChart, BarChart, etc.)',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
relations: [
|
|
92
|
+
{ component: 'Card', relationship: 'parent', note: 'Charts are typically placed inside Cards' },
|
|
93
|
+
{ component: 'Progress', relationship: 'alternative', note: 'Use Progress for single-value completion' },
|
|
94
|
+
{ component: 'Table', relationship: 'sibling', note: 'Combine with Table for full data views' },
|
|
95
|
+
],
|
|
96
|
+
|
|
97
|
+
contract: {
|
|
98
|
+
propsSummary: [
|
|
99
|
+
'config: ChartConfig - maps data keys to labels and theme colors',
|
|
100
|
+
'children: ReactElement - recharts chart component',
|
|
101
|
+
],
|
|
102
|
+
scenarioTags: [
|
|
103
|
+
'display.chart',
|
|
104
|
+
'data.visualization',
|
|
105
|
+
'dashboard.analytics',
|
|
106
|
+
],
|
|
107
|
+
a11yRules: ['A11Y_CHART_LABEL'],
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
variants: [
|
|
111
|
+
{
|
|
112
|
+
name: 'Line Chart',
|
|
113
|
+
description: 'Multi-series line chart showing trends over time',
|
|
114
|
+
render: () => (
|
|
115
|
+
<div style={{ width: '100%', height: 300 }}>
|
|
116
|
+
<ChartContainer
|
|
117
|
+
config={{
|
|
118
|
+
revenue: { label: 'Revenue', color: 'var(--fui-color-accent)' },
|
|
119
|
+
users: { label: 'Users', color: 'var(--fui-color-info)' },
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<LineChart data={monthlyData} margin={{ top: 5, right: 20, bottom: 5, left: 0 }}>
|
|
123
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
124
|
+
<XAxis dataKey="month" />
|
|
125
|
+
<YAxis />
|
|
126
|
+
<ChartTooltip />
|
|
127
|
+
<ChartLegend />
|
|
128
|
+
<Line type="monotone" dataKey="revenue" stroke="var(--fui-color-accent)" strokeWidth={2} dot={false} />
|
|
129
|
+
<Line type="monotone" dataKey="users" stroke="var(--fui-color-info)" strokeWidth={2} dot={false} />
|
|
130
|
+
</LineChart>
|
|
131
|
+
</ChartContainer>
|
|
132
|
+
</div>
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'Bar Chart',
|
|
137
|
+
description: 'Categorical bar chart comparing device sessions',
|
|
138
|
+
render: () => (
|
|
139
|
+
<div style={{ width: '100%', height: 300 }}>
|
|
140
|
+
<ChartContainer
|
|
141
|
+
config={{
|
|
142
|
+
sessions: { label: 'Sessions', color: 'var(--fui-color-accent)' },
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<BarChart data={deviceData} margin={{ top: 5, right: 20, bottom: 5, left: 0 }}>
|
|
146
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
147
|
+
<XAxis dataKey="device" />
|
|
148
|
+
<YAxis />
|
|
149
|
+
<ChartTooltip />
|
|
150
|
+
<Bar dataKey="sessions" fill="var(--fui-color-accent)" radius={[4, 4, 0, 0]} />
|
|
151
|
+
</BarChart>
|
|
152
|
+
</ChartContainer>
|
|
153
|
+
</div>
|
|
154
|
+
),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'Area Chart',
|
|
158
|
+
description: 'Filled area chart showing revenue trend',
|
|
159
|
+
render: () => (
|
|
160
|
+
<div style={{ width: '100%', height: 300 }}>
|
|
161
|
+
<ChartContainer
|
|
162
|
+
config={{
|
|
163
|
+
revenue: { label: 'Revenue', color: 'var(--fui-color-accent)' },
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
<AreaChart data={monthlyData} margin={{ top: 5, right: 20, bottom: 5, left: 0 }}>
|
|
167
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
168
|
+
<XAxis dataKey="month" />
|
|
169
|
+
<YAxis />
|
|
170
|
+
<ChartTooltip />
|
|
171
|
+
<Area type="monotone" dataKey="revenue" stroke="var(--fui-color-accent)" fill="var(--fui-color-accent)" fillOpacity={0.15} strokeWidth={2} />
|
|
172
|
+
</AreaChart>
|
|
173
|
+
</ChartContainer>
|
|
174
|
+
</div>
|
|
175
|
+
),
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'Pie Chart',
|
|
179
|
+
description: 'Donut chart showing browser share distribution',
|
|
180
|
+
render: () => (
|
|
181
|
+
<div style={{ width: '100%', height: 300 }}>
|
|
182
|
+
<ChartContainer
|
|
183
|
+
config={{
|
|
184
|
+
Chrome: { label: 'Chrome', color: 'var(--fui-color-accent)' },
|
|
185
|
+
Firefox: { label: 'Firefox', color: 'var(--fui-color-info)' },
|
|
186
|
+
Safari: { label: 'Safari', color: 'var(--fui-color-success)' },
|
|
187
|
+
Edge: { label: 'Edge', color: 'var(--fui-color-warning)' },
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<PieChart>
|
|
191
|
+
<ChartTooltip />
|
|
192
|
+
<ChartLegend />
|
|
193
|
+
<Pie
|
|
194
|
+
data={browserData}
|
|
195
|
+
dataKey="value"
|
|
196
|
+
nameKey="name"
|
|
197
|
+
cx="50%"
|
|
198
|
+
cy="50%"
|
|
199
|
+
innerRadius={60}
|
|
200
|
+
outerRadius={90}
|
|
201
|
+
strokeWidth={2}
|
|
202
|
+
>
|
|
203
|
+
{browserData.map((entry) => (
|
|
204
|
+
<Cell key={entry.name} fill={entry.color} />
|
|
205
|
+
))}
|
|
206
|
+
</Pie>
|
|
207
|
+
</PieChart>
|
|
208
|
+
</ChartContainer>
|
|
209
|
+
</div>
|
|
210
|
+
),
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
|
|
3
|
+
// Container
|
|
4
|
+
.container {
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
font-family: var(--fui-font-sans, $fui-font-sans);
|
|
8
|
+
|
|
9
|
+
// Override recharts defaults to use FUI tokens
|
|
10
|
+
:global(.recharts-text) {
|
|
11
|
+
fill: var(--fui-text-secondary, $fui-text-secondary);
|
|
12
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:global(.recharts-cartesian-grid) line {
|
|
16
|
+
stroke: var(--fui-border, $fui-border);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:global(.recharts-cartesian-axis-line),
|
|
20
|
+
:global(.recharts-cartesian-axis-tick-line) {
|
|
21
|
+
stroke: var(--fui-border, $fui-border);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:global(.recharts-curve.recharts-tooltip-cursor) {
|
|
25
|
+
stroke: var(--fui-border, $fui-border);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
:global(.recharts-rectangle.recharts-tooltip-cursor) {
|
|
29
|
+
fill: var(--fui-bg-hover, $fui-bg-hover);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Tooltip
|
|
34
|
+
.tooltip {
|
|
35
|
+
background: var(--fui-bg-elevated, $fui-bg-elevated);
|
|
36
|
+
border: 1px solid var(--fui-border, $fui-border);
|
|
37
|
+
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
38
|
+
box-shadow: var(--fui-shadow-md, $fui-shadow-md);
|
|
39
|
+
padding: var(--fui-space-2, $fui-space-2) var(--fui-space-3, $fui-space-3);
|
|
40
|
+
font-family: var(--fui-font-sans, $fui-font-sans);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.tooltipLabel {
|
|
44
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
45
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
46
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
47
|
+
margin-bottom: var(--fui-space-1, $fui-space-1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.tooltipItems {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: 2px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.tooltipItem {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
60
|
+
justify-content: space-between;
|
|
61
|
+
min-width: 100px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.tooltipIndicator {
|
|
65
|
+
width: 8px;
|
|
66
|
+
height: 8px;
|
|
67
|
+
border-radius: 50%;
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
|
|
70
|
+
&.tooltipIndicatorLine {
|
|
71
|
+
width: 12px;
|
|
72
|
+
height: 2px;
|
|
73
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&.tooltipIndicatorDashed {
|
|
77
|
+
width: 12px;
|
|
78
|
+
height: 0;
|
|
79
|
+
border-top: 2px dashed;
|
|
80
|
+
border-radius: 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.tooltipItemLabel {
|
|
85
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
86
|
+
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.tooltipItemValue {
|
|
90
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
91
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
92
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
93
|
+
font-variant-numeric: tabular-nums;
|
|
94
|
+
margin-left: auto;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Legend
|
|
98
|
+
.legend {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-wrap: wrap;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
gap: var(--fui-space-3, $fui-space-3);
|
|
104
|
+
padding-top: var(--fui-space-2, $fui-space-2);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.legendItem {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
gap: var(--fui-space-1, $fui-space-1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.legendDot {
|
|
114
|
+
width: 8px;
|
|
115
|
+
height: 8px;
|
|
116
|
+
border-radius: 50%;
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.legendLabel {
|
|
121
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
122
|
+
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
123
|
+
}
|