@fragments-sdk/ui 0.1.1 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragments-sdk/ui",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Customizable UI components built on Base UI headless primitives",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -29,9 +29,9 @@
29
29
  "react-dom": "^19.0.0",
30
30
  "sass": "^1.83.0",
31
31
  "typescript": "^5.7.0",
32
- "@fragments/cli": "0.1.0",
33
- "@fragments/core": "0.1.0",
34
- "@fragments/viewer": "0.1.0"
32
+ "@fragments/cli": "0.2.0",
33
+ "@fragments/core": "0.2.0",
34
+ "@fragments/viewer": "0.1.1"
35
35
  },
36
36
  "files": [
37
37
  "src"
@@ -0,0 +1,177 @@
1
+ import React from 'react';
2
+ import { defineSegment } from '@fragments/core';
3
+ import { Grid } from './index.js';
4
+
5
+ export default defineSegment({
6
+ component: Grid,
7
+
8
+ meta: {
9
+ name: 'Grid',
10
+ description: 'Responsive grid layout for arranging items in columns with consistent spacing',
11
+ category: 'layout',
12
+ status: 'stable',
13
+ tags: ['grid', 'layout', 'columns', 'responsive'],
14
+ },
15
+
16
+ usage: {
17
+ when: [
18
+ 'Arranging cards, tiles, or media in a responsive grid',
19
+ 'Building form layouts with multi-column fields',
20
+ 'Creating dashboard layouts with widgets',
21
+ 'Any content that should reflow across breakpoints',
22
+ ],
23
+ whenNot: [
24
+ 'Single-column stacked content (use a simple flex column or Stack)',
25
+ 'Navigation bars or toolbars (use a dedicated nav component)',
26
+ 'Content that must not wrap (use inline layout)',
27
+ ],
28
+ guidelines: [
29
+ 'Use columns="auto" with minChildWidth for grids that adapt without breakpoints',
30
+ 'Use responsive object { base: 1, md: 2, lg: 3 } when you need explicit control per breakpoint',
31
+ 'Use fixed column counts when exact column control is needed and responsiveness is not required',
32
+ 'Use Grid.Item with colSpan to create asymmetric layouts within a fixed grid',
33
+ 'Keep gap consistent within a context — md is the default and works for most cases',
34
+ ],
35
+ accessibility: [
36
+ 'Grid is purely visual — it does not affect reading order or semantics',
37
+ 'Ensure logical source order matches visual order for screen readers',
38
+ ],
39
+ },
40
+
41
+ props: {
42
+ columns: {
43
+ type: 'union',
44
+ description: 'Number of columns: a number (1-12), a responsive object { base, sm, md, lg, xl }, or "auto" for auto-fill',
45
+ default: 1,
46
+ constraints: [
47
+ 'Use "auto" with minChildWidth for fully fluid layouts',
48
+ 'Use responsive object for explicit breakpoint control: { base: 1, md: 2, lg: 3 }',
49
+ ],
50
+ },
51
+ minChildWidth: {
52
+ type: 'string',
53
+ description: 'Minimum width for auto-fill columns (e.g., "16rem", "250px")',
54
+ constraints: ['Only applies when columns="auto"'],
55
+ },
56
+ gap: {
57
+ type: 'enum',
58
+ values: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
59
+ default: 'md',
60
+ description: 'Gap between grid items, mapped to spacing tokens',
61
+ },
62
+ alignItems: {
63
+ type: 'enum',
64
+ values: ['start', 'center', 'end', 'stretch'],
65
+ description: 'Vertical alignment of items within their cells',
66
+ },
67
+ justifyItems: {
68
+ type: 'enum',
69
+ values: ['start', 'center', 'end', 'stretch'],
70
+ description: 'Horizontal alignment of items within their cells',
71
+ },
72
+ padding: {
73
+ type: 'enum',
74
+ values: ['none', 'sm', 'md', 'lg'],
75
+ default: 'none',
76
+ description: 'Internal padding of the grid container',
77
+ },
78
+ },
79
+
80
+ relations: [
81
+ {
82
+ component: 'Card',
83
+ relationship: 'child',
84
+ note: 'Grid commonly contains Card components for dashboard and tile layouts',
85
+ },
86
+ {
87
+ component: 'Separator',
88
+ relationship: 'sibling',
89
+ note: 'Use Separator between grid sections',
90
+ },
91
+ ],
92
+
93
+ contract: {
94
+ propsSummary: [
95
+ 'columns: 1-12 | { base, sm, md, lg, xl } | "auto" (default: 1)',
96
+ 'minChildWidth: string — min width for auto-fill (only with columns="auto")',
97
+ 'gap: none|xs|sm|md|lg|xl (default: md)',
98
+ 'alignItems: start|center|end|stretch — vertical alignment',
99
+ 'justifyItems: start|center|end|stretch — horizontal alignment',
100
+ 'padding: none|sm|md|lg (default: none)',
101
+ ],
102
+ scenarioTags: [
103
+ 'layout.grid',
104
+ 'layout.columns',
105
+ 'layout.responsive',
106
+ 'pattern.card-grid',
107
+ 'pattern.dashboard',
108
+ 'pattern.form-layout',
109
+ ],
110
+ },
111
+
112
+ variants: [
113
+ {
114
+ name: 'Default',
115
+ description: 'Basic 3-column grid',
116
+ render: () => (
117
+ <Grid columns={3} gap="md">
118
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Item 1</div>
119
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Item 2</div>
120
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Item 3</div>
121
+ </Grid>
122
+ ),
123
+ },
124
+ {
125
+ name: 'Responsive',
126
+ description: '1 column on mobile, 2 on tablet, 3 on desktop',
127
+ render: () => (
128
+ <Grid columns={{ base: 1, md: 2, lg: 3 }} gap="md">
129
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Card 1</div>
130
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Card 2</div>
131
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Card 3</div>
132
+ </Grid>
133
+ ),
134
+ },
135
+ {
136
+ name: 'Auto-fill',
137
+ description: 'Responsive grid that auto-fills based on minimum child width',
138
+ render: () => (
139
+ <Grid columns="auto" minChildWidth="12rem" gap="md">
140
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Card 1</div>
141
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Card 2</div>
142
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Card 3</div>
143
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Card 4</div>
144
+ </Grid>
145
+ ),
146
+ },
147
+ {
148
+ name: 'With Spanning',
149
+ description: 'Grid items spanning multiple columns',
150
+ render: () => (
151
+ <Grid columns={4} gap="md">
152
+ <Grid.Item colSpan={2}>
153
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Spans 2 cols</div>
154
+ </Grid.Item>
155
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>1 col</div>
156
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>1 col</div>
157
+ <Grid.Item colSpan="full">
158
+ <div style={{ padding: '1rem', background: '#f1f5f9' }}>Full width</div>
159
+ </Grid.Item>
160
+ </Grid>
161
+ ),
162
+ },
163
+ {
164
+ name: 'Form Layout',
165
+ description: 'Two-column form that collapses to single column on mobile',
166
+ render: () => (
167
+ <Grid columns={{ base: 1, md: 2 }} gap="md">
168
+ <div style={{ padding: '0.5rem', background: '#f1f5f9' }}>First Name</div>
169
+ <div style={{ padding: '0.5rem', background: '#f1f5f9' }}>Last Name</div>
170
+ <Grid.Item colSpan="full">
171
+ <div style={{ padding: '0.5rem', background: '#f1f5f9' }}>Email Address</div>
172
+ </Grid.Item>
173
+ </Grid>
174
+ ),
175
+ },
176
+ ],
177
+ });
@@ -0,0 +1,142 @@
1
+ @use '../../tokens/variables' as *;
2
+ @use '../../tokens/mixins' as *;
3
+
4
+ // ============================================
5
+ // Grid Container
6
+ // ============================================
7
+
8
+ .grid {
9
+ display: grid;
10
+ width: 100%;
11
+ }
12
+
13
+ // ============================================
14
+ // Fixed column counts (1-12)
15
+ // ============================================
16
+
17
+ .columns1 { grid-template-columns: repeat(1, 1fr); }
18
+ .columns2 { grid-template-columns: repeat(2, 1fr); }
19
+ .columns3 { grid-template-columns: repeat(3, 1fr); }
20
+ .columns4 { grid-template-columns: repeat(4, 1fr); }
21
+ .columns5 { grid-template-columns: repeat(5, 1fr); }
22
+ .columns6 { grid-template-columns: repeat(6, 1fr); }
23
+ .columns7 { grid-template-columns: repeat(7, 1fr); }
24
+ .columns8 { grid-template-columns: repeat(8, 1fr); }
25
+ .columns9 { grid-template-columns: repeat(9, 1fr); }
26
+ .columns10 { grid-template-columns: repeat(10, 1fr); }
27
+ .columns11 { grid-template-columns: repeat(11, 1fr); }
28
+ .columns12 { grid-template-columns: repeat(12, 1fr); }
29
+
30
+ // ============================================
31
+ // Auto-fill (responsive without media queries)
32
+ // ============================================
33
+
34
+ .columnsAuto {
35
+ grid-template-columns: repeat(auto-fill, minmax(var(--fui-grid-min-child-width, 16rem), 1fr));
36
+ }
37
+
38
+ // ============================================
39
+ // Responsive columns via CSS custom properties
40
+ //
41
+ // Usage: set --fui-grid-cols (base) and
42
+ // --fui-grid-cols-{sm,md,lg,xl} via inline style.
43
+ // Each breakpoint inherits from the previous
44
+ // if not explicitly set.
45
+ // ============================================
46
+
47
+ .columnsResponsive {
48
+ --_cols: var(--fui-grid-cols, 1);
49
+
50
+ grid-template-columns: repeat(var(--_cols), 1fr);
51
+
52
+ @include breakpoint-sm {
53
+ --_cols: var(--fui-grid-cols-sm, var(--fui-grid-cols, 1));
54
+ }
55
+
56
+ @include breakpoint-md {
57
+ --_cols: var(--fui-grid-cols-md, var(--fui-grid-cols-sm, var(--fui-grid-cols, 1)));
58
+ }
59
+
60
+ @include breakpoint-lg {
61
+ --_cols: var(--fui-grid-cols-lg, var(--fui-grid-cols-md, var(--fui-grid-cols-sm, var(--fui-grid-cols, 1))));
62
+ }
63
+
64
+ @include breakpoint-xl {
65
+ --_cols: var(--fui-grid-cols-xl, var(--fui-grid-cols-lg, var(--fui-grid-cols-md, var(--fui-grid-cols-sm, var(--fui-grid-cols, 1)))));
66
+ }
67
+ }
68
+
69
+ // ============================================
70
+ // Gap
71
+ // ============================================
72
+
73
+ .gapNone { gap: 0; }
74
+ .gapXs { gap: var(--fui-space-1, $fui-space-1); }
75
+ .gapSm { gap: var(--fui-space-2, $fui-space-2); }
76
+ .gapMd { gap: var(--fui-space-4, $fui-space-4); }
77
+ .gapLg { gap: var(--fui-space-6, $fui-space-6); }
78
+ .gapXl { gap: var(--fui-space-8, $fui-space-8); }
79
+
80
+ // ============================================
81
+ // Padding
82
+ // ============================================
83
+
84
+ .paddingNone { padding: 0; }
85
+ .paddingSm { padding: var(--fui-space-3, $fui-space-3); }
86
+ .paddingMd { padding: var(--fui-space-4, $fui-space-4); }
87
+ .paddingLg { padding: var(--fui-space-6, $fui-space-6); }
88
+
89
+ // ============================================
90
+ // Align items (cross-axis within cells)
91
+ // ============================================
92
+
93
+ .alignStart { align-items: start; }
94
+ .alignCenter { align-items: center; }
95
+ .alignEnd { align-items: end; }
96
+ .alignStretch { align-items: stretch; }
97
+
98
+ // ============================================
99
+ // Justify items (main-axis within cells)
100
+ // ============================================
101
+
102
+ .justifyStart { justify-items: start; }
103
+ .justifyCenter { justify-items: center; }
104
+ .justifyEnd { justify-items: end; }
105
+ .justifyStretch { justify-items: stretch; }
106
+
107
+ // ============================================
108
+ // Grid Item
109
+ // ============================================
110
+
111
+ .item {
112
+ min-width: 0; // Prevent overflow from children
113
+ }
114
+
115
+ // Column spans
116
+ .colSpan1 { grid-column: span 1; }
117
+ .colSpan2 { grid-column: span 2; }
118
+ .colSpan3 { grid-column: span 3; }
119
+ .colSpan4 { grid-column: span 4; }
120
+ .colSpan5 { grid-column: span 5; }
121
+ .colSpan6 { grid-column: span 6; }
122
+ .colSpan7 { grid-column: span 7; }
123
+ .colSpan8 { grid-column: span 8; }
124
+ .colSpan9 { grid-column: span 9; }
125
+ .colSpan10 { grid-column: span 10; }
126
+ .colSpan11 { grid-column: span 11; }
127
+ .colSpan12 { grid-column: span 12; }
128
+ .colSpanFull { grid-column: 1 / -1; }
129
+
130
+ // Row spans
131
+ .rowSpan1 { grid-row: span 1; }
132
+ .rowSpan2 { grid-row: span 2; }
133
+ .rowSpan3 { grid-row: span 3; }
134
+ .rowSpan4 { grid-row: span 4; }
135
+ .rowSpan5 { grid-row: span 5; }
136
+ .rowSpan6 { grid-row: span 6; }
137
+
138
+ // Self alignment overrides
139
+ .selfAlignStart { align-self: start; }
140
+ .selfAlignCenter { align-self: center; }
141
+ .selfAlignEnd { align-self: end; }
142
+ .selfAlignStretch { align-self: stretch; }
@@ -0,0 +1,197 @@
1
+ import * as React from 'react';
2
+ import styles from './Grid.module.scss';
3
+ // Import globals to ensure CSS variables are defined
4
+ import '../../styles/globals.scss';
5
+
6
+ // ============================================
7
+ // Types
8
+ // ============================================
9
+
10
+ type ColumnCount = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
11
+
12
+ /** Responsive value — either a single value or per-breakpoint overrides */
13
+ export interface ResponsiveColumns {
14
+ /** Default (mobile-first) */
15
+ base?: ColumnCount;
16
+ /** ≥640px */
17
+ sm?: ColumnCount;
18
+ /** ≥768px */
19
+ md?: ColumnCount;
20
+ /** ≥1024px */
21
+ lg?: ColumnCount;
22
+ /** ≥1280px */
23
+ xl?: ColumnCount;
24
+ }
25
+
26
+ export interface GridProps {
27
+ children?: React.ReactNode;
28
+ /**
29
+ * Number of columns.
30
+ * - A number (1-12) for fixed columns at all sizes
31
+ * - An object for responsive columns: `{ base: 1, md: 2, lg: 3 }`
32
+ * - `"auto"` for responsive auto-fill based on minChildWidth
33
+ */
34
+ columns?: ColumnCount | ResponsiveColumns | 'auto';
35
+ /** Minimum width for auto-fill columns (only used with columns="auto") */
36
+ minChildWidth?: string;
37
+ /** Gap between grid items */
38
+ gap?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
39
+ /** Vertical alignment of items within their cells */
40
+ alignItems?: 'start' | 'center' | 'end' | 'stretch';
41
+ /** Horizontal alignment of items within their cells */
42
+ justifyItems?: 'start' | 'center' | 'end' | 'stretch';
43
+ /** Padding inside the grid container */
44
+ padding?: 'none' | 'sm' | 'md' | 'lg';
45
+ /** Additional class name */
46
+ className?: string;
47
+ }
48
+
49
+ export interface GridItemProps {
50
+ children?: React.ReactNode;
51
+ /** Number of columns this item spans */
52
+ colSpan?: ColumnCount | 'full';
53
+ /** Number of rows this item spans */
54
+ rowSpan?: 1 | 2 | 3 | 4 | 5 | 6;
55
+ /** Override alignment for this item */
56
+ alignSelf?: 'start' | 'center' | 'end' | 'stretch';
57
+ /** Additional class name */
58
+ className?: string;
59
+ }
60
+
61
+ // ============================================
62
+ // Helpers
63
+ // ============================================
64
+
65
+ function isResponsiveColumns(
66
+ columns: GridProps['columns']
67
+ ): columns is ResponsiveColumns {
68
+ return typeof columns === 'object' && columns !== null;
69
+ }
70
+
71
+ const gapClasses: Record<NonNullable<GridProps['gap']>, string> = {
72
+ none: styles.gapNone,
73
+ xs: styles.gapXs,
74
+ sm: styles.gapSm,
75
+ md: styles.gapMd,
76
+ lg: styles.gapLg,
77
+ xl: styles.gapXl,
78
+ };
79
+
80
+ const paddingClasses: Record<NonNullable<GridProps['padding']>, string> = {
81
+ none: styles.paddingNone,
82
+ sm: styles.paddingSm,
83
+ md: styles.paddingMd,
84
+ lg: styles.paddingLg,
85
+ };
86
+
87
+ // ============================================
88
+ // Grid Component
89
+ // ============================================
90
+
91
+ export const Grid = React.forwardRef<HTMLDivElement, GridProps>(
92
+ function Grid(
93
+ {
94
+ children,
95
+ columns = 1,
96
+ minChildWidth,
97
+ gap = 'md',
98
+ alignItems,
99
+ justifyItems,
100
+ padding = 'none',
101
+ className,
102
+ },
103
+ ref
104
+ ) {
105
+ // Determine classes and style based on columns type
106
+ let columnsClass: string;
107
+ let inlineStyle: React.CSSProperties | undefined;
108
+
109
+ if (columns === 'auto') {
110
+ columnsClass = styles.columnsAuto;
111
+ if (minChildWidth) {
112
+ inlineStyle = { '--fui-grid-min-child-width': minChildWidth } as React.CSSProperties;
113
+ }
114
+ } else if (isResponsiveColumns(columns)) {
115
+ columnsClass = styles.columnsResponsive;
116
+ const vars: Record<string, string> = {};
117
+ if (columns.base) vars['--fui-grid-cols'] = String(columns.base);
118
+ if (columns.sm) vars['--fui-grid-cols-sm'] = String(columns.sm);
119
+ if (columns.md) vars['--fui-grid-cols-md'] = String(columns.md);
120
+ if (columns.lg) vars['--fui-grid-cols-lg'] = String(columns.lg);
121
+ if (columns.xl) vars['--fui-grid-cols-xl'] = String(columns.xl);
122
+ inlineStyle = vars as unknown as React.CSSProperties;
123
+ } else {
124
+ columnsClass = styles[`columns${columns}`];
125
+ }
126
+
127
+ const classes = [
128
+ styles.grid,
129
+ columnsClass,
130
+ gapClasses[gap],
131
+ paddingClasses[padding],
132
+ alignItems && styles[`align${cap(alignItems)}`],
133
+ justifyItems && styles[`justify${cap(justifyItems)}`],
134
+ className,
135
+ ]
136
+ .filter(Boolean)
137
+ .join(' ');
138
+
139
+ return (
140
+ <div ref={ref} className={classes} style={inlineStyle}>
141
+ {children}
142
+ </div>
143
+ );
144
+ }
145
+ ) as GridComponent;
146
+
147
+ // ============================================
148
+ // Grid.Item Sub-component
149
+ // ============================================
150
+
151
+ const GridItem = React.forwardRef<HTMLDivElement, GridItemProps>(
152
+ function GridItem(
153
+ {
154
+ children,
155
+ colSpan,
156
+ rowSpan,
157
+ alignSelf,
158
+ className,
159
+ },
160
+ ref
161
+ ) {
162
+ const classes = [
163
+ styles.item,
164
+ colSpan && (colSpan === 'full' ? styles.colSpanFull : styles[`colSpan${colSpan}`]),
165
+ rowSpan && styles[`rowSpan${rowSpan}`],
166
+ alignSelf && styles[`selfAlign${cap(alignSelf)}`],
167
+ className,
168
+ ]
169
+ .filter(Boolean)
170
+ .join(' ');
171
+
172
+ return (
173
+ <div ref={ref} className={classes}>
174
+ {children}
175
+ </div>
176
+ );
177
+ }
178
+ );
179
+
180
+ // ============================================
181
+ // Utilities
182
+ // ============================================
183
+
184
+ function cap(s: string): string {
185
+ return s.charAt(0).toUpperCase() + s.slice(1);
186
+ }
187
+
188
+ // ============================================
189
+ // Compound component type
190
+ // ============================================
191
+
192
+ interface GridComponent
193
+ extends React.ForwardRefExoticComponent<GridProps & React.RefAttributes<HTMLDivElement>> {
194
+ Item: typeof GridItem;
195
+ }
196
+
197
+ (Grid as GridComponent).Item = GridItem;
package/src/index.ts CHANGED
@@ -105,6 +105,9 @@ export {
105
105
  type RadioItemProps,
106
106
  } from './components/RadioGroup';
107
107
 
108
+ // Grid
109
+ export { Grid, type GridProps, type GridItemProps, type ResponsiveColumns } from './components/Grid';
110
+
108
111
  // Separator
109
112
  export { Separator, type SeparatorProps } from './components/Separator';
110
113
 
@@ -0,0 +1,18 @@
1
+ import { defineRecipe } from '@fragments/core';
2
+
3
+ export default defineRecipe({
4
+ name: 'Card Grid',
5
+ description: 'Responsive grid of cards that reflows based on available space',
6
+ category: 'layout',
7
+ components: ['Grid', 'Card'],
8
+ tags: ['grid', 'cards', 'responsive', 'dashboard', 'tiles'],
9
+ code: `
10
+ <Grid columns="auto" minChildWidth="16rem" gap="md">
11
+ {items.map(item => (
12
+ <Card key={item.id} title={item.title} description={item.description}>
13
+ {item.content}
14
+ </Card>
15
+ ))}
16
+ </Grid>
17
+ `.trim(),
18
+ });
@@ -0,0 +1,19 @@
1
+ import { defineRecipe } from '@fragments/core';
2
+
3
+ export default defineRecipe({
4
+ name: 'Confirm Dialog',
5
+ description: 'Confirmation dialog with destructive action warning',
6
+ category: 'overlays',
7
+ components: ['Dialog', 'Button'],
8
+ tags: ['confirm', 'dialog', 'modal', 'destructive'],
9
+ code: `
10
+ <Dialog open={isOpen} onClose={onClose}>
11
+ <Dialog.Title>{title}</Dialog.Title>
12
+ <Dialog.Description>{description}</Dialog.Description>
13
+ <Dialog.Actions>
14
+ <Button variant="secondary" onClick={onClose}>Cancel</Button>
15
+ <Button variant="danger" onClick={onConfirm}>Confirm</Button>
16
+ </Dialog.Actions>
17
+ </Dialog>
18
+ `.trim(),
19
+ });
@@ -0,0 +1,39 @@
1
+ import { defineRecipe } from '@fragments/core';
2
+
3
+ export default defineRecipe({
4
+ name: 'Dashboard Layout',
5
+ description: 'Dashboard grid with a featured full-width card and smaller metric cards below',
6
+ category: 'layout',
7
+ components: ['Grid', 'Card', 'Badge', 'Separator'],
8
+ tags: ['dashboard', 'layout', 'metrics', 'widgets', 'overview'],
9
+ code: `
10
+ <Grid columns={4} gap="lg">
11
+ <Grid.Item colSpan="full">
12
+ <Card title="Overview" description="Key metrics for this period">
13
+ {summaryContent}
14
+ </Card>
15
+ </Grid.Item>
16
+ <Card title="Users" variant="outlined">
17
+ <Badge variant="success">{stats.users}</Badge>
18
+ </Card>
19
+ <Card title="Revenue" variant="outlined">
20
+ <Badge variant="info">{stats.revenue}</Badge>
21
+ </Card>
22
+ <Card title="Orders" variant="outlined">
23
+ <Badge variant="warning">{stats.orders}</Badge>
24
+ </Card>
25
+ <Card title="Errors" variant="outlined">
26
+ <Badge variant="danger">{stats.errors}</Badge>
27
+ </Card>
28
+ <Grid.Item colSpan="full">
29
+ <Separator spacing="md" />
30
+ </Grid.Item>
31
+ <Grid.Item colSpan={2}>
32
+ <Card title="Recent Activity">{activityList}</Card>
33
+ </Grid.Item>
34
+ <Grid.Item colSpan={2}>
35
+ <Card title="Notifications">{notificationList}</Card>
36
+ </Grid.Item>
37
+ </Grid>
38
+ `.trim(),
39
+ });
@@ -0,0 +1,31 @@
1
+ import { defineRecipe } from '@fragments/core';
2
+
3
+ export default defineRecipe({
4
+ name: 'Form Layout',
5
+ description: 'Two-column form with full-width fields where needed, using Grid for alignment',
6
+ category: 'forms',
7
+ components: ['Grid', 'Input', 'Textarea', 'Select', 'Button'],
8
+ tags: ['form', 'layout', 'grid', 'inputs', 'settings'],
9
+ code: `
10
+ <Grid columns={2} gap="md">
11
+ <Input label="First Name" placeholder="Jane" />
12
+ <Input label="Last Name" placeholder="Doe" />
13
+ <Grid.Item colSpan="full">
14
+ <Input label="Email" type="email" placeholder="jane@example.com" />
15
+ </Grid.Item>
16
+ <Grid.Item colSpan="full">
17
+ <Select label="Role">
18
+ <Select.Item value="admin">Admin</Select.Item>
19
+ <Select.Item value="editor">Editor</Select.Item>
20
+ <Select.Item value="viewer">Viewer</Select.Item>
21
+ </Select>
22
+ </Grid.Item>
23
+ <Grid.Item colSpan="full">
24
+ <Textarea label="Bio" placeholder="Tell us about yourself" />
25
+ </Grid.Item>
26
+ <Grid.Item colSpan="full">
27
+ <Button type="submit" variant="primary">Save</Button>
28
+ </Grid.Item>
29
+ </Grid>
30
+ `.trim(),
31
+ });
@@ -0,0 +1,19 @@
1
+ import { defineRecipe } from '@fragments/core';
2
+
3
+ export default defineRecipe({
4
+ name: 'Login Form',
5
+ description: 'Email/password authentication form with validation states',
6
+ category: 'forms',
7
+ components: ['FormField', 'Input', 'Button', 'Alert'],
8
+ tags: ['auth', 'login', 'form'],
9
+ code: `
10
+ <FormField label="Email" error={errors.email}>
11
+ <Input type="email" placeholder="you@example.com" />
12
+ </FormField>
13
+ <FormField label="Password" error={errors.password}>
14
+ <Input type="password" />
15
+ </FormField>
16
+ <Button type="submit" variant="primary">Sign in</Button>
17
+ {error && <Alert variant="danger">{error}</Alert>}
18
+ `.trim(),
19
+ });
@@ -0,0 +1,41 @@
1
+ import { defineRecipe } from '@fragments/core';
2
+
3
+ export default defineRecipe({
4
+ name: 'Settings Page',
5
+ description: 'Settings page with labeled sections using cards, toggles, and a save action',
6
+ category: 'forms',
7
+ components: ['Grid', 'Card', 'Toggle', 'Input', 'Select', 'Separator', 'Button'],
8
+ tags: ['settings', 'preferences', 'form', 'toggle', 'layout'],
9
+ code: `
10
+ <Grid columns={1} gap="lg">
11
+ <Card title="Profile" description="Your public profile information">
12
+ <Grid columns={2} gap="md">
13
+ <Input label="Display Name" defaultValue={user.name} />
14
+ <Input label="Email" type="email" defaultValue={user.email} />
15
+ <Grid.Item colSpan="full">
16
+ <Input label="Website" type="url" defaultValue={user.website} />
17
+ </Grid.Item>
18
+ </Grid>
19
+ </Card>
20
+
21
+ <Card title="Notifications" description="Choose what you get notified about">
22
+ <Grid columns={1} gap="sm">
23
+ <Toggle label="Email notifications" checked={prefs.emailNotifs} onChange={onToggle('emailNotifs')} />
24
+ <Toggle label="Push notifications" checked={prefs.pushNotifs} onChange={onToggle('pushNotifs')} />
25
+ <Toggle label="Weekly digest" checked={prefs.digest} onChange={onToggle('digest')} />
26
+ </Grid>
27
+ </Card>
28
+
29
+ <Card title="Appearance">
30
+ <Select label="Theme" value={prefs.theme} onChange={onThemeChange}>
31
+ <Select.Item value="light">Light</Select.Item>
32
+ <Select.Item value="dark">Dark</Select.Item>
33
+ <Select.Item value="system">System</Select.Item>
34
+ </Select>
35
+ </Card>
36
+
37
+ <Separator />
38
+ <Button variant="primary" type="submit">Save Changes</Button>
39
+ </Grid>
40
+ `.trim(),
41
+ });
@@ -79,6 +79,12 @@
79
79
  border-radius: var(--fui-radius-lg, #{$fui-radius-lg});
80
80
  }
81
81
 
82
+ // Responsive breakpoint mixins (mobile-first)
83
+ @mixin breakpoint-sm { @media (min-width: $fui-breakpoint-sm) { @content; } }
84
+ @mixin breakpoint-md { @media (min-width: $fui-breakpoint-md) { @content; } }
85
+ @mixin breakpoint-lg { @media (min-width: $fui-breakpoint-lg) { @content; } }
86
+ @mixin breakpoint-xl { @media (min-width: $fui-breakpoint-xl) { @content; } }
87
+
82
88
  // Visually hidden (for screen readers)
83
89
  @mixin visually-hidden {
84
90
  position: absolute;
@@ -134,6 +134,14 @@ $fui-dark-color-info-bg: rgba(59, 130, 246, 0.15) !default;
134
134
  $fui-backdrop: rgba(0, 0, 0, 0.5) !default;
135
135
  $fui-dark-backdrop: rgba(0, 0, 0, 0.7) !default;
136
136
 
137
+ // --------------------------------------------
138
+ // Breakpoints
139
+ // --------------------------------------------
140
+ $fui-breakpoint-sm: 640px !default; // Large phones
141
+ $fui-breakpoint-md: 768px !default; // Tablets
142
+ $fui-breakpoint-lg: 1024px !default; // Small laptops
143
+ $fui-breakpoint-xl: 1280px !default; // Desktops
144
+
137
145
  // --------------------------------------------
138
146
  // Component-specific tokens
139
147
  // --------------------------------------------