@fragments-sdk/ui 0.3.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 +11 -5
- package/src/components/Accordion/Accordion.fragment.tsx +186 -0
- package/src/components/Accordion/Accordion.module.scss +111 -0
- package/src/components/Accordion/index.tsx +271 -0
- package/src/components/Alert/Alert.fragment.tsx +67 -42
- package/src/components/Alert/Alert.module.scss +31 -21
- package/src/components/Alert/index.tsx +202 -73
- package/src/components/AppShell/AppShell.fragment.tsx +315 -0
- package/src/components/AppShell/AppShell.module.scss +213 -0
- package/src/components/AppShell/index.tsx +398 -0
- package/src/components/Avatar/Avatar.fragment.tsx +2 -2
- package/src/components/Avatar/index.tsx +8 -9
- package/src/components/Badge/Badge.fragment.tsx +2 -2
- package/src/components/Badge/Badge.module.scss +16 -10
- package/src/components/Badge/index.tsx +20 -6
- package/src/components/Box/Box.fragment.tsx +168 -0
- package/src/components/Box/Box.module.scss +84 -0
- package/src/components/Box/index.tsx +78 -0
- package/src/components/Button/Button.fragment.tsx +2 -2
- package/src/components/Button/Button.module.scss +42 -0
- package/src/components/Button/index.tsx +67 -33
- package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +153 -0
- package/src/components/ButtonGroup/ButtonGroup.module.scss +37 -0
- package/src/components/ButtonGroup/index.tsx +40 -0
- package/src/components/Card/Card.fragment.tsx +52 -26
- package/src/components/Card/Card.module.scss +52 -5
- package/src/components/Card/index.tsx +154 -53
- 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/Checkbox/Checkbox.module.scss +4 -4
- package/src/components/Checkbox/index.tsx +3 -4
- package/src/components/CodeBlock/CodeBlock.fragment.tsx +460 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +362 -0
- package/src/components/CodeBlock/index.tsx +599 -0
- 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 +119 -0
- package/src/components/ColorPicker/index.tsx +129 -0
- 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 +12 -3
- package/src/components/Dialog/Dialog.module.scss +26 -7
- package/src/components/Dialog/index.tsx +12 -15
- package/src/components/EmptyState/EmptyState.fragment.tsx +55 -72
- package/src/components/EmptyState/EmptyState.module.scss +9 -9
- package/src/components/EmptyState/index.tsx +104 -69
- package/src/components/Field/Field.fragment.tsx +165 -0
- package/src/components/Field/Field.module.scss +31 -0
- package/src/components/Field/index.tsx +143 -0
- package/src/components/Fieldset/Fieldset.fragment.tsx +166 -0
- package/src/components/Fieldset/Fieldset.module.scss +22 -0
- package/src/components/Fieldset/index.tsx +47 -0
- package/src/components/Form/Form.fragment.tsx +286 -0
- package/src/components/Form/Form.module.scss +8 -0
- package/src/components/Form/index.tsx +53 -0
- package/src/components/Grid/Grid.fragment.tsx +18 -18
- package/src/components/Grid/index.tsx +6 -1
- package/src/components/Header/Header.fragment.tsx +192 -0
- package/src/components/Header/Header.module.scss +208 -0
- package/src/components/Header/index.tsx +363 -0
- package/src/components/Icon/Icon.fragment.tsx +138 -0
- package/src/components/Icon/Icon.module.scss +38 -0
- package/src/components/Icon/index.tsx +58 -0
- package/src/components/Image/Image.fragment.tsx +195 -0
- package/src/components/Image/Image.module.scss +77 -0
- package/src/components/Image/index.tsx +95 -0
- package/src/components/Input/Input.fragment.tsx +1 -1
- package/src/components/Input/Input.module.scss +75 -2
- package/src/components/Input/index.tsx +60 -21
- package/src/components/Link/Link.fragment.tsx +132 -0
- package/src/components/Link/Link.module.scss +67 -0
- package/src/components/Link/index.tsx +57 -0
- package/src/components/List/List.fragment.tsx +152 -0
- package/src/components/List/List.module.scss +71 -0
- package/src/components/List/index.tsx +106 -0
- package/src/components/Listbox/Listbox.fragment.tsx +191 -0
- package/src/components/Listbox/Listbox.module.scss +97 -0
- package/src/components/Listbox/index.tsx +121 -0
- 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 +12 -3
- package/src/components/Menu/Menu.module.scss +17 -1
- package/src/components/Menu/index.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 +13 -4
- package/src/components/Popover/Popover.module.scss +33 -10
- package/src/components/Popover/index.tsx +9 -11
- package/src/components/Progress/Progress.fragment.tsx +1 -1
- package/src/components/Progress/Progress.module.scss +11 -11
- package/src/components/Progress/index.tsx +34 -7
- package/src/components/Prompt/Prompt.fragment.tsx +231 -0
- package/src/components/Prompt/Prompt.module.scss +243 -0
- package/src/components/Prompt/index.tsx +439 -0
- package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
- package/src/components/RadioGroup/RadioGroup.module.scss +10 -7
- package/src/components/RadioGroup/index.tsx +3 -4
- package/src/components/Select/Select.fragment.tsx +10 -1
- package/src/components/Select/Select.module.scss +8 -0
- package/src/components/Select/index.tsx +91 -12
- package/src/components/Separator/Separator.fragment.tsx +1 -1
- package/src/components/Separator/index.tsx +7 -3
- package/src/components/Sidebar/Sidebar.fragment.tsx +11 -2
- package/src/components/Sidebar/Sidebar.module.scss +91 -47
- package/src/components/Sidebar/index.tsx +57 -14
- package/src/components/Skeleton/Skeleton.fragment.tsx +6 -6
- package/src/components/Skeleton/Skeleton.module.scss +11 -0
- package/src/components/Slider/Slider.fragment.tsx +201 -0
- package/src/components/Slider/Slider.module.scss +87 -0
- package/src/components/Slider/index.tsx +88 -0
- package/src/components/Stack/Stack.fragment.tsx +194 -0
- package/src/components/Stack/Stack.module.scss +120 -0
- package/src/components/Stack/index.tsx +148 -0
- package/src/components/Table/Table.fragment.tsx +10 -3
- package/src/components/Table/Table.module.scss +57 -0
- package/src/components/Table/index.tsx +44 -6
- package/src/components/Tabs/Tabs.fragment.tsx +10 -1
- package/src/components/Tabs/Tabs.module.scss +25 -10
- package/src/components/Tabs/index.tsx +11 -8
- package/src/components/Text/Text.fragment.tsx +188 -0
- package/src/components/Text/Text.module.scss +82 -0
- package/src/components/Text/index.tsx +58 -0
- package/src/components/Textarea/Textarea.fragment.tsx +1 -1
- package/src/components/Textarea/index.tsx +3 -7
- package/src/components/Theme/Theme.fragment.tsx +128 -0
- package/src/components/Theme/ThemeToggle.module.scss +82 -0
- package/src/components/Theme/index.tsx +343 -0
- 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 +6 -6
- package/src/components/Toast/Toast.module.scss +16 -1
- package/src/components/Toast/index.tsx +27 -11
- package/src/components/Toggle/Toggle.fragment.tsx +1 -1
- package/src/components/Toggle/Toggle.module.scss +25 -10
- package/src/components/Toggle/index.tsx +12 -0
- package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +207 -0
- package/src/components/ToggleGroup/ToggleGroup.module.scss +134 -0
- package/src/components/ToggleGroup/index.tsx +144 -0
- package/src/components/Tooltip/Tooltip.fragment.tsx +3 -3
- package/src/components/Tooltip/Tooltip.module.scss +4 -4
- package/src/components/Tooltip/index.tsx +4 -2
- package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +134 -0
- package/src/components/VisuallyHidden/VisuallyHidden.module.scss +13 -0
- package/src/components/VisuallyHidden/index.tsx +29 -0
- package/src/index.ts +278 -3
- package/src/recipes/AIChat.recipe.ts +266 -0
- package/src/recipes/AppShell.recipe.ts +175 -0
- package/src/recipes/CardGrid.recipe.ts +6 -2
- package/src/recipes/ChatInterface.recipe.ts +87 -0
- package/src/recipes/CodeExamples.recipe.ts +66 -0
- package/src/recipes/DashboardLayout.recipe.ts +46 -12
- package/src/recipes/DashboardNav.recipe.ts +183 -0
- package/src/recipes/LoginForm.recipe.ts +8 -1
- package/src/recipes/SettingsPage.recipe.ts +37 -20
- package/src/styles/globals.scss +31 -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 +41 -0
- package/src/tokens/_mixins.scss +95 -1
- 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 +507 -101
- package/src/utils/a11y.tsx +439 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { defineSegment } from '@fragments/core';
|
|
3
|
-
import { Tabs } from '
|
|
3
|
+
import { Tabs } from '.';
|
|
4
4
|
|
|
5
5
|
export default defineSegment({
|
|
6
6
|
component: Tabs,
|
|
@@ -87,6 +87,15 @@ export default defineSegment({
|
|
|
87
87
|
a11yRules: ['A11Y_TABS_KEYBOARD', 'A11Y_TABS_LABELS'],
|
|
88
88
|
},
|
|
89
89
|
|
|
90
|
+
ai: {
|
|
91
|
+
compositionPattern: 'compound',
|
|
92
|
+
subComponents: ['List', 'Tab', 'Panel'],
|
|
93
|
+
requiredChildren: ['List', 'Panel'],
|
|
94
|
+
commonPatterns: [
|
|
95
|
+
'<Tabs defaultValue="tab1"><Tabs.List><Tabs.Tab value="tab1">{label1}</Tabs.Tab><Tabs.Tab value="tab2">{label2}</Tabs.Tab></Tabs.List><Tabs.Panel value="tab1">{content1}</Tabs.Panel><Tabs.Panel value="tab2">{content2}</Tabs.Panel></Tabs>',
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
|
|
90
99
|
variants: [
|
|
91
100
|
{
|
|
92
101
|
name: 'Underline',
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// Variant: pills (
|
|
33
|
+
// Variant: pills (shadcn-style with background container)
|
|
34
34
|
.listPills {
|
|
35
35
|
border-bottom: none;
|
|
36
|
-
background-color: var(--fui-bg-
|
|
37
|
-
border-radius: var(--fui-radius-
|
|
38
|
-
padding: var(--fui-space-
|
|
36
|
+
background-color: var(--fui-bg-secondary, $fui-bg-secondary);
|
|
37
|
+
border-radius: var(--fui-radius-lg, $fui-radius-lg);
|
|
38
|
+
padding: var(--fui-space-0-75, $fui-space-0-75);
|
|
39
|
+
gap: var(--fui-space-0-75, $fui-space-0-75);
|
|
40
|
+
width: fit-content;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
// Variant: underline (default, with indicator line)
|
|
@@ -53,7 +55,7 @@
|
|
|
53
55
|
align-items: center;
|
|
54
56
|
justify-content: center;
|
|
55
57
|
gap: var(--fui-space-2, $fui-space-2);
|
|
56
|
-
padding: var(--fui-
|
|
58
|
+
padding: var(--fui-padding-item-sm, $fui-padding-item-sm) var(--fui-padding-item-md, $fui-padding-item-md);
|
|
57
59
|
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
58
60
|
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
59
61
|
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
@@ -78,18 +80,31 @@
|
|
|
78
80
|
.tabUnderline {
|
|
79
81
|
border-radius: 0;
|
|
80
82
|
margin-bottom: -1px;
|
|
81
|
-
padding-bottom: calc(var(--fui-
|
|
83
|
+
padding-bottom: calc(var(--fui-padding-item-sm, $fui-padding-item-sm) + 1px);
|
|
82
84
|
|
|
83
85
|
&:hover:not([data-disabled]) {
|
|
84
86
|
background-color: transparent;
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
// Tab in pills variant
|
|
90
|
+
// Tab in pills variant (shadcn-style)
|
|
89
91
|
.tabPills {
|
|
92
|
+
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
93
|
+
transition: all 0.15s ease;
|
|
94
|
+
padding: var(--fui-space-1-5, 6px) var(--fui-space-3, $fui-space-3);
|
|
95
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
96
|
+
|
|
97
|
+
&:hover:not([data-disabled]):not([data-active]) {
|
|
98
|
+
background-color: transparent;
|
|
99
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
&[data-active] {
|
|
91
|
-
background-color: var(--fui-bg-
|
|
92
|
-
|
|
103
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
104
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
105
|
+
box-shadow:
|
|
106
|
+
0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
|
107
|
+
0 1px 2px -1px rgba(0, 0, 0, 0.1);
|
|
93
108
|
}
|
|
94
109
|
}
|
|
95
110
|
|
|
@@ -122,7 +137,7 @@
|
|
|
122
137
|
|
|
123
138
|
// Tab panel content
|
|
124
139
|
.panel {
|
|
125
|
-
padding: var(--fui-
|
|
140
|
+
padding: var(--fui-padding-item-lg, $fui-padding-item-lg) 0;
|
|
126
141
|
outline: none;
|
|
127
142
|
|
|
128
143
|
&[data-hidden] {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import * as React from 'react';
|
|
2
4
|
import { Tabs as BaseTabs } from '@base-ui/react/tabs';
|
|
3
5
|
import styles from './Tabs.module.scss';
|
|
@@ -10,19 +12,17 @@ import '../../styles/globals.scss';
|
|
|
10
12
|
|
|
11
13
|
export type TabValue = string | number;
|
|
12
14
|
|
|
13
|
-
export interface TabsProps {
|
|
15
|
+
export interface TabsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'defaultValue'> {
|
|
14
16
|
children: React.ReactNode;
|
|
15
17
|
defaultValue?: TabValue;
|
|
16
18
|
value?: TabValue;
|
|
17
19
|
onValueChange?: (value: TabValue) => void;
|
|
18
20
|
orientation?: 'horizontal' | 'vertical';
|
|
19
|
-
className?: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export interface TabsListProps {
|
|
23
|
+
export interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
23
24
|
children: React.ReactNode;
|
|
24
25
|
variant?: 'underline' | 'pills';
|
|
25
|
-
className?: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface TabProps {
|
|
@@ -32,12 +32,11 @@ export interface TabProps {
|
|
|
32
32
|
className?: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export interface TabsPanelProps {
|
|
35
|
+
export interface TabsPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
36
36
|
children: React.ReactNode;
|
|
37
37
|
value: TabValue;
|
|
38
38
|
keepMounted?: boolean;
|
|
39
39
|
flush?: boolean;
|
|
40
|
-
className?: string;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
// ============================================
|
|
@@ -57,11 +56,13 @@ function TabsRoot({
|
|
|
57
56
|
onValueChange,
|
|
58
57
|
orientation = 'horizontal',
|
|
59
58
|
className,
|
|
59
|
+
...htmlProps
|
|
60
60
|
}: TabsProps) {
|
|
61
61
|
const classes = [styles.root, className].filter(Boolean).join(' ');
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
64
|
<BaseTabs.Root
|
|
65
|
+
{...htmlProps}
|
|
65
66
|
defaultValue={defaultValue}
|
|
66
67
|
value={value}
|
|
67
68
|
onValueChange={onValueChange}
|
|
@@ -77,13 +78,14 @@ function TabsList({
|
|
|
77
78
|
children,
|
|
78
79
|
variant = 'underline',
|
|
79
80
|
className,
|
|
81
|
+
...htmlProps
|
|
80
82
|
}: TabsListProps) {
|
|
81
83
|
const variantClass = variant === 'pills' ? styles.listPills : styles.listUnderline;
|
|
82
84
|
const classes = [styles.list, variantClass, className].filter(Boolean).join(' ');
|
|
83
85
|
|
|
84
86
|
return (
|
|
85
87
|
<TabsVariantContext.Provider value={variant}>
|
|
86
|
-
<BaseTabs.List className={classes}>
|
|
88
|
+
<BaseTabs.List {...htmlProps} className={classes}>
|
|
87
89
|
{children}
|
|
88
90
|
{variant === 'underline' && <BaseTabs.Indicator className={styles.indicator} />}
|
|
89
91
|
</BaseTabs.List>
|
|
@@ -114,6 +116,7 @@ function TabsPanel({
|
|
|
114
116
|
keepMounted = false,
|
|
115
117
|
flush = false,
|
|
116
118
|
className,
|
|
119
|
+
...htmlProps
|
|
117
120
|
}: TabsPanelProps) {
|
|
118
121
|
const classes = [
|
|
119
122
|
styles.panel,
|
|
@@ -122,7 +125,7 @@ function TabsPanel({
|
|
|
122
125
|
].filter(Boolean).join(' ');
|
|
123
126
|
|
|
124
127
|
return (
|
|
125
|
-
<BaseTabs.Panel value={value} keepMounted={keepMounted} className={classes}>
|
|
128
|
+
<BaseTabs.Panel {...htmlProps} value={value} keepMounted={keepMounted} className={classes}>
|
|
126
129
|
{children}
|
|
127
130
|
</BaseTabs.Panel>
|
|
128
131
|
);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Text } from '.';
|
|
4
|
+
|
|
5
|
+
export default defineSegment({
|
|
6
|
+
component: Text,
|
|
7
|
+
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'Text',
|
|
10
|
+
description: 'Typography component for rendering text with consistent styling. Supports various sizes, weights, colors, and semantic elements.',
|
|
11
|
+
category: 'display',
|
|
12
|
+
status: 'stable',
|
|
13
|
+
tags: ['text', 'typography', 'heading', 'paragraph', 'font'],
|
|
14
|
+
since: '0.2.0',
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
usage: {
|
|
18
|
+
when: [
|
|
19
|
+
'Displaying text with specific typography styles',
|
|
20
|
+
'Creating headings, paragraphs, or labels',
|
|
21
|
+
'Text that needs truncation or line clamping',
|
|
22
|
+
'Consistent typography across the application',
|
|
23
|
+
],
|
|
24
|
+
whenNot: [
|
|
25
|
+
'Complex rich text (use a rich text editor)',
|
|
26
|
+
'Code display (use CodeBlock)',
|
|
27
|
+
'Interactive text (use Link or Button)',
|
|
28
|
+
],
|
|
29
|
+
guidelines: [
|
|
30
|
+
'Use semantic elements (h1-h6, p) via the "as" prop',
|
|
31
|
+
'Maintain heading hierarchy for accessibility',
|
|
32
|
+
'Use color variants sparingly for visual hierarchy',
|
|
33
|
+
'Consider truncation for user-generated content',
|
|
34
|
+
],
|
|
35
|
+
accessibility: [
|
|
36
|
+
'Use proper heading levels (h1-h6) for document structure',
|
|
37
|
+
'Semantic elements convey meaning to screen readers',
|
|
38
|
+
'Truncated text should have full content in title/tooltip',
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
props: {
|
|
43
|
+
children: {
|
|
44
|
+
type: 'node',
|
|
45
|
+
description: 'Text content',
|
|
46
|
+
required: true,
|
|
47
|
+
},
|
|
48
|
+
as: {
|
|
49
|
+
type: 'enum',
|
|
50
|
+
description: 'HTML element to render',
|
|
51
|
+
values: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'label', 'div', 'strong', 'em', 'small', 'code'],
|
|
52
|
+
default: 'span',
|
|
53
|
+
},
|
|
54
|
+
size: {
|
|
55
|
+
type: 'enum',
|
|
56
|
+
description: 'Font size',
|
|
57
|
+
values: ['2xs', 'xs', 'sm', 'base', 'lg', 'xl', '2xl'],
|
|
58
|
+
},
|
|
59
|
+
weight: {
|
|
60
|
+
type: 'enum',
|
|
61
|
+
description: 'Font weight',
|
|
62
|
+
values: ['normal', 'medium', 'semibold'],
|
|
63
|
+
},
|
|
64
|
+
color: {
|
|
65
|
+
type: 'enum',
|
|
66
|
+
description: 'Text color',
|
|
67
|
+
values: ['primary', 'secondary', 'tertiary'],
|
|
68
|
+
},
|
|
69
|
+
font: {
|
|
70
|
+
type: 'enum',
|
|
71
|
+
description: 'Font family',
|
|
72
|
+
values: ['sans', 'mono'],
|
|
73
|
+
default: 'sans',
|
|
74
|
+
},
|
|
75
|
+
truncate: {
|
|
76
|
+
type: 'boolean',
|
|
77
|
+
description: 'Truncate with ellipsis on overflow',
|
|
78
|
+
},
|
|
79
|
+
lineClamp: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Number of lines before truncating (requires truncate)',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
relations: [
|
|
86
|
+
{ component: 'Link', relationship: 'sibling', note: 'Use Link for clickable text' },
|
|
87
|
+
{ component: 'CodeBlock', relationship: 'alternative', note: 'Use CodeBlock for code display' },
|
|
88
|
+
{ component: 'Badge', relationship: 'sibling', note: 'Use Badge for labels/tags' },
|
|
89
|
+
],
|
|
90
|
+
|
|
91
|
+
contract: {
|
|
92
|
+
propsSummary: [
|
|
93
|
+
'as: string - HTML element',
|
|
94
|
+
'size: 2xs|xs|sm|base|lg|xl|2xl - font size',
|
|
95
|
+
'weight: normal|medium|semibold - font weight',
|
|
96
|
+
'color: primary|secondary|tertiary - text color',
|
|
97
|
+
'font: sans|mono - font family',
|
|
98
|
+
'truncate: boolean - enable truncation',
|
|
99
|
+
'lineClamp: number - max lines',
|
|
100
|
+
],
|
|
101
|
+
scenarioTags: [
|
|
102
|
+
'typography.text',
|
|
103
|
+
'display.content',
|
|
104
|
+
'semantic.elements',
|
|
105
|
+
],
|
|
106
|
+
a11yRules: ['A11Y_HEADING_HIERARCHY', 'A11Y_SEMANTIC_ELEMENTS'],
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
variants: [
|
|
110
|
+
{
|
|
111
|
+
name: 'Sizes',
|
|
112
|
+
description: 'Different text sizes',
|
|
113
|
+
render: () => (
|
|
114
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
115
|
+
<Text size="2xs">Extra extra small (2xs)</Text>
|
|
116
|
+
<Text size="xs">Extra small (xs)</Text>
|
|
117
|
+
<Text size="sm">Small (sm)</Text>
|
|
118
|
+
<Text size="base">Base size</Text>
|
|
119
|
+
<Text size="lg">Large (lg)</Text>
|
|
120
|
+
<Text size="xl">Extra large (xl)</Text>
|
|
121
|
+
<Text size="2xl">Extra extra large (2xl)</Text>
|
|
122
|
+
</div>
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'Weights',
|
|
127
|
+
description: 'Different font weights',
|
|
128
|
+
render: () => (
|
|
129
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
130
|
+
<Text weight="normal">Normal weight</Text>
|
|
131
|
+
<Text weight="medium">Medium weight</Text>
|
|
132
|
+
<Text weight="semibold">Semibold weight</Text>
|
|
133
|
+
</div>
|
|
134
|
+
),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'Colors',
|
|
138
|
+
description: 'Different text colors',
|
|
139
|
+
render: () => (
|
|
140
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
141
|
+
<Text color="primary">Primary color (default)</Text>
|
|
142
|
+
<Text color="secondary">Secondary color</Text>
|
|
143
|
+
<Text color="tertiary">Tertiary color</Text>
|
|
144
|
+
</div>
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'Semantic Elements',
|
|
149
|
+
description: 'Using appropriate HTML elements',
|
|
150
|
+
render: () => (
|
|
151
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
152
|
+
<Text as="h1" size="2xl" weight="semibold">Heading 1</Text>
|
|
153
|
+
<Text as="h2" size="xl" weight="semibold">Heading 2</Text>
|
|
154
|
+
<Text as="h3" size="lg" weight="medium">Heading 3</Text>
|
|
155
|
+
<Text as="p" color="secondary">
|
|
156
|
+
This is a paragraph of text that demonstrates the Text component
|
|
157
|
+
with semantic paragraph element.
|
|
158
|
+
</Text>
|
|
159
|
+
</div>
|
|
160
|
+
),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'Monospace',
|
|
164
|
+
description: 'Monospace font for code-like text',
|
|
165
|
+
render: () => (
|
|
166
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
167
|
+
<Text font="mono" size="sm">const greeting = "Hello, World!";</Text>
|
|
168
|
+
<Text font="mono" size="sm" color="secondary">npm install @fragments-sdk/ui</Text>
|
|
169
|
+
</div>
|
|
170
|
+
),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'Truncation',
|
|
174
|
+
description: 'Text truncation with ellipsis',
|
|
175
|
+
render: () => (
|
|
176
|
+
<div style={{ width: '200px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
177
|
+
<Text truncate>
|
|
178
|
+
This is a very long text that will be truncated with an ellipsis when it overflows.
|
|
179
|
+
</Text>
|
|
180
|
+
<Text truncate lineClamp={2}>
|
|
181
|
+
This text will be clamped to two lines. Any content beyond two lines
|
|
182
|
+
will be hidden and replaced with an ellipsis at the end.
|
|
183
|
+
</Text>
|
|
184
|
+
</div>
|
|
185
|
+
),
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
|
|
3
|
+
.text {
|
|
4
|
+
font-family: var(--fui-font-sans, $fui-font-sans);
|
|
5
|
+
margin: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Font sizes
|
|
9
|
+
.size-2xs {
|
|
10
|
+
font-size: var(--fui-font-size-2xs, $fui-font-size-2xs);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.size-xs {
|
|
14
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.size-sm {
|
|
18
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.size-base {
|
|
22
|
+
font-size: var(--fui-font-size-base, $fui-font-size-base);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.size-lg {
|
|
26
|
+
font-size: var(--fui-font-size-lg, $fui-font-size-lg);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.size-xl {
|
|
30
|
+
font-size: var(--fui-font-size-xl, $fui-font-size-xl);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.size-2xl {
|
|
34
|
+
font-size: var(--fui-font-size-2xl, $fui-font-size-2xl);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Font weights
|
|
38
|
+
.weight-normal {
|
|
39
|
+
font-weight: var(--fui-font-weight-normal, $fui-font-weight-normal);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.weight-medium {
|
|
43
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.weight-semibold {
|
|
47
|
+
font-weight: var(--fui-font-weight-semibold, $fui-font-weight-semibold);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Text colors
|
|
51
|
+
.color-primary {
|
|
52
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.color-secondary {
|
|
56
|
+
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.color-tertiary {
|
|
60
|
+
color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Font family
|
|
64
|
+
.mono {
|
|
65
|
+
font-family: var(--fui-font-mono, $fui-font-mono);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Truncation
|
|
69
|
+
.truncate {
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
text-overflow: ellipsis;
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Line clamp (multi-line truncation)
|
|
76
|
+
.lineClamp {
|
|
77
|
+
display: -webkit-box;
|
|
78
|
+
-webkit-box-orient: vertical;
|
|
79
|
+
-webkit-line-clamp: var(--fui-line-clamp, 2);
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
text-overflow: ellipsis;
|
|
82
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styles from './Text.module.scss';
|
|
3
|
+
import '../../styles/globals.scss';
|
|
4
|
+
|
|
5
|
+
export interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, 'color'> {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'label' | 'div' | 'strong' | 'em' | 'small' | 'mark' | 'del' | 'ins' | 'sub' | 'sup' | 'time' | 'address' | 'blockquote' | 'cite' | 'code' | 'abbr';
|
|
8
|
+
size?: '2xs' | 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl';
|
|
9
|
+
weight?: 'normal' | 'medium' | 'semibold';
|
|
10
|
+
color?: 'primary' | 'secondary' | 'tertiary';
|
|
11
|
+
font?: 'sans' | 'mono';
|
|
12
|
+
/** Truncate text with ellipsis when it overflows */
|
|
13
|
+
truncate?: boolean;
|
|
14
|
+
/** Number of lines before truncating (requires truncate=true) */
|
|
15
|
+
lineClamp?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Text = React.forwardRef<HTMLElement, TextProps>(
|
|
19
|
+
function Text(
|
|
20
|
+
{
|
|
21
|
+
children,
|
|
22
|
+
as: Component = 'span',
|
|
23
|
+
size,
|
|
24
|
+
weight,
|
|
25
|
+
color,
|
|
26
|
+
font = 'sans',
|
|
27
|
+
truncate,
|
|
28
|
+
lineClamp,
|
|
29
|
+
className,
|
|
30
|
+
style,
|
|
31
|
+
...htmlProps
|
|
32
|
+
},
|
|
33
|
+
ref
|
|
34
|
+
) {
|
|
35
|
+
const classes = [
|
|
36
|
+
styles.text,
|
|
37
|
+
size && styles[`size-${size}`],
|
|
38
|
+
weight && styles[`weight-${weight}`],
|
|
39
|
+
color && styles[`color-${color}`],
|
|
40
|
+
font === 'mono' && styles.mono,
|
|
41
|
+
truncate && styles.truncate,
|
|
42
|
+
lineClamp && styles.lineClamp,
|
|
43
|
+
className,
|
|
44
|
+
]
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.join(' ');
|
|
47
|
+
|
|
48
|
+
const lineClampStyle = lineClamp
|
|
49
|
+
? { '--fui-line-clamp': lineClamp, ...style } as React.CSSProperties
|
|
50
|
+
: style;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Component ref={ref as React.Ref<never>} className={classes} style={lineClampStyle} {...htmlProps}>
|
|
54
|
+
{children}
|
|
55
|
+
</Component>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
);
|
|
@@ -3,7 +3,7 @@ import styles from './Textarea.module.scss';
|
|
|
3
3
|
// Import globals to ensure CSS variables are defined
|
|
4
4
|
import '../../styles/globals.scss';
|
|
5
5
|
|
|
6
|
-
export interface TextareaProps {
|
|
6
|
+
export interface TextareaProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'onBlur' | 'defaultValue'> {
|
|
7
7
|
/** Controlled value */
|
|
8
8
|
value?: string;
|
|
9
9
|
/** Default value for uncontrolled usage */
|
|
@@ -30,12 +30,8 @@ export interface TextareaProps {
|
|
|
30
30
|
onChange?: (value: string) => void;
|
|
31
31
|
/** Called when textarea loses focus */
|
|
32
32
|
onBlur?: () => void;
|
|
33
|
-
/** Additional class name */
|
|
34
|
-
className?: string;
|
|
35
33
|
/** Form field name */
|
|
36
34
|
name?: string;
|
|
37
|
-
/** Element ID */
|
|
38
|
-
id?: string;
|
|
39
35
|
/** Maximum character length */
|
|
40
36
|
maxLength?: number;
|
|
41
37
|
/** Required field */
|
|
@@ -63,6 +59,7 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
63
59
|
id,
|
|
64
60
|
maxLength,
|
|
65
61
|
required = false,
|
|
62
|
+
...htmlProps
|
|
66
63
|
},
|
|
67
64
|
ref
|
|
68
65
|
) {
|
|
@@ -73,7 +70,6 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
73
70
|
styles.textarea,
|
|
74
71
|
error && styles.error,
|
|
75
72
|
styles[`resize-${resize}`],
|
|
76
|
-
className,
|
|
77
73
|
]
|
|
78
74
|
.filter(Boolean)
|
|
79
75
|
.join(' ');
|
|
@@ -92,7 +88,7 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
92
88
|
}
|
|
93
89
|
|
|
94
90
|
return (
|
|
95
|
-
<div className={styles.wrapper}>
|
|
91
|
+
<div {...htmlProps} className={[styles.wrapper, className].filter(Boolean).join(' ')}>
|
|
96
92
|
{label && (
|
|
97
93
|
<label htmlFor={textareaId} className={styles.label}>
|
|
98
94
|
{label}
|