@fragments-sdk/ui 0.1.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 +44 -0
- package/src/brand.ts +15 -0
- package/src/components/Alert/Alert.fragment.tsx +163 -0
- package/src/components/Alert/Alert.module.scss +116 -0
- package/src/components/Alert/index.tsx +95 -0
- package/src/components/Avatar/Avatar.fragment.tsx +147 -0
- package/src/components/Avatar/Avatar.module.scss +136 -0
- package/src/components/Avatar/index.tsx +177 -0
- package/src/components/Badge/Badge.fragment.tsx +151 -0
- package/src/components/Badge/Badge.module.scss +87 -0
- package/src/components/Badge/index.tsx +55 -0
- package/src/components/Button/Button.fragment.tsx +159 -0
- package/src/components/Button/Button.module.scss +97 -0
- package/src/components/Button/index.tsx +51 -0
- package/src/components/Card/Card.fragment.tsx +156 -0
- package/src/components/Card/Card.module.scss +86 -0
- package/src/components/Card/index.tsx +79 -0
- package/src/components/Checkbox/Checkbox.fragment.tsx +166 -0
- package/src/components/Checkbox/Checkbox.module.scss +144 -0
- package/src/components/Checkbox/index.tsx +166 -0
- package/src/components/Dialog/Dialog.fragment.tsx +179 -0
- package/src/components/Dialog/Dialog.module.scss +158 -0
- package/src/components/Dialog/index.tsx +230 -0
- package/src/components/EmptyState/EmptyState.fragment.tsx +222 -0
- package/src/components/EmptyState/EmptyState.module.scss +120 -0
- package/src/components/EmptyState/index.tsx +80 -0
- package/src/components/Input/Input.fragment.tsx +174 -0
- package/src/components/Input/Input.module.scss +64 -0
- package/src/components/Input/index.tsx +76 -0
- package/src/components/Menu/Menu.fragment.tsx +168 -0
- package/src/components/Menu/Menu.module.scss +190 -0
- package/src/components/Menu/index.tsx +318 -0
- package/src/components/Popover/Popover.fragment.tsx +178 -0
- package/src/components/Popover/Popover.module.scss +165 -0
- package/src/components/Popover/index.tsx +229 -0
- package/src/components/Progress/Progress.fragment.tsx +142 -0
- package/src/components/Progress/Progress.module.scss +185 -0
- package/src/components/Progress/index.tsx +196 -0
- package/src/components/RadioGroup/RadioGroup.fragment.tsx +188 -0
- package/src/components/RadioGroup/RadioGroup.module.scss +155 -0
- package/src/components/RadioGroup/index.tsx +166 -0
- package/src/components/Select/Select.fragment.tsx +173 -0
- package/src/components/Select/Select.module.scss +187 -0
- package/src/components/Select/index.tsx +233 -0
- package/src/components/Separator/Separator.fragment.tsx +148 -0
- package/src/components/Separator/Separator.module.scss +92 -0
- package/src/components/Separator/index.tsx +89 -0
- package/src/components/Skeleton/Skeleton.fragment.tsx +147 -0
- package/src/components/Skeleton/Skeleton.module.scss +166 -0
- package/src/components/Skeleton/index.tsx +185 -0
- package/src/components/Table/Table.fragment.tsx +193 -0
- package/src/components/Table/Table.module.scss +152 -0
- package/src/components/Table/index.tsx +266 -0
- package/src/components/Tabs/Tabs.fragment.tsx +155 -0
- package/src/components/Tabs/Tabs.module.scss +142 -0
- package/src/components/Tabs/index.tsx +142 -0
- package/src/components/Textarea/Textarea.fragment.tsx +171 -0
- package/src/components/Textarea/Textarea.module.scss +89 -0
- package/src/components/Textarea/index.tsx +128 -0
- package/src/components/Toast/Toast.fragment.tsx +210 -0
- package/src/components/Toast/Toast.module.scss +227 -0
- package/src/components/Toast/index.tsx +315 -0
- package/src/components/Toggle/Toggle.fragment.tsx +174 -0
- package/src/components/Toggle/Toggle.module.scss +103 -0
- package/src/components/Toggle/index.tsx +80 -0
- package/src/components/Tooltip/Tooltip.fragment.tsx +158 -0
- package/src/components/Tooltip/Tooltip.module.scss +82 -0
- package/src/components/Tooltip/index.tsx +135 -0
- package/src/index.ts +151 -0
- package/src/scss.d.ts +4 -0
- package/src/styles/globals.scss +17 -0
- package/src/tokens/_mixins.scss +93 -0
- package/src/tokens/_variables.scss +276 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Textarea } from './index.js';
|
|
4
|
+
|
|
5
|
+
export default defineSegment({
|
|
6
|
+
component: Textarea,
|
|
7
|
+
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'Textarea',
|
|
10
|
+
description: 'Multi-line text input for longer form content',
|
|
11
|
+
category: 'forms',
|
|
12
|
+
status: 'stable',
|
|
13
|
+
tags: ['input', 'text', 'form', 'multiline'],
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
usage: {
|
|
17
|
+
when: [
|
|
18
|
+
'Collecting multi-line text (comments, descriptions)',
|
|
19
|
+
'Free-form text input that may span multiple lines',
|
|
20
|
+
'Message composition fields',
|
|
21
|
+
'Code or content editing',
|
|
22
|
+
],
|
|
23
|
+
whenNot: [
|
|
24
|
+
'Single-line input (use Input)',
|
|
25
|
+
'Rich text editing (use rich text editor)',
|
|
26
|
+
'Selecting from predefined options (use Select)',
|
|
27
|
+
],
|
|
28
|
+
guidelines: [
|
|
29
|
+
'Set appropriate rows for expected content length',
|
|
30
|
+
'Use placeholder to show example format',
|
|
31
|
+
'Show character count when maxLength is set',
|
|
32
|
+
'Consider auto-resize for better UX',
|
|
33
|
+
],
|
|
34
|
+
accessibility: [
|
|
35
|
+
'Always provide a visible label',
|
|
36
|
+
'Use helperText for format hints',
|
|
37
|
+
'Error messages should be descriptive',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
props: {
|
|
42
|
+
value: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Controlled value',
|
|
45
|
+
},
|
|
46
|
+
placeholder: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Placeholder text',
|
|
49
|
+
},
|
|
50
|
+
rows: {
|
|
51
|
+
type: 'number',
|
|
52
|
+
default: 3,
|
|
53
|
+
description: 'Number of visible text rows',
|
|
54
|
+
},
|
|
55
|
+
label: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Label text above the textarea',
|
|
58
|
+
},
|
|
59
|
+
helperText: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Helper text below the textarea',
|
|
62
|
+
},
|
|
63
|
+
error: {
|
|
64
|
+
type: 'boolean',
|
|
65
|
+
default: false,
|
|
66
|
+
description: 'Error state',
|
|
67
|
+
},
|
|
68
|
+
disabled: {
|
|
69
|
+
type: 'boolean',
|
|
70
|
+
default: false,
|
|
71
|
+
description: 'Disabled state',
|
|
72
|
+
},
|
|
73
|
+
resize: {
|
|
74
|
+
type: 'enum',
|
|
75
|
+
values: ['none', 'vertical', 'horizontal', 'both'],
|
|
76
|
+
default: 'vertical',
|
|
77
|
+
description: 'Resize behavior',
|
|
78
|
+
},
|
|
79
|
+
maxLength: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Maximum character length',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
relations: [
|
|
86
|
+
{
|
|
87
|
+
component: 'Input',
|
|
88
|
+
relationship: 'alternative',
|
|
89
|
+
note: 'Use Input for single-line text',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
|
|
93
|
+
contract: {
|
|
94
|
+
propsSummary: [
|
|
95
|
+
'value: string - controlled value',
|
|
96
|
+
'rows: number - visible rows (default: 3)',
|
|
97
|
+
'label: string - label text',
|
|
98
|
+
'error: boolean - error state',
|
|
99
|
+
'disabled: boolean - disabled state',
|
|
100
|
+
'resize: none|vertical|horizontal|both',
|
|
101
|
+
],
|
|
102
|
+
scenarioTags: [
|
|
103
|
+
'form.textarea',
|
|
104
|
+
'form.comment',
|
|
105
|
+
'form.description',
|
|
106
|
+
],
|
|
107
|
+
a11yRules: [
|
|
108
|
+
'A11Y_LABEL_REQUIRED',
|
|
109
|
+
],
|
|
110
|
+
bans: [],
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
variants: [
|
|
114
|
+
{
|
|
115
|
+
name: 'Default',
|
|
116
|
+
description: 'Basic textarea with label',
|
|
117
|
+
render: () => (
|
|
118
|
+
<Textarea
|
|
119
|
+
label="Description"
|
|
120
|
+
placeholder="Enter a description..."
|
|
121
|
+
/>
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'With Helper Text',
|
|
126
|
+
description: 'Textarea with additional guidance',
|
|
127
|
+
render: () => (
|
|
128
|
+
<Textarea
|
|
129
|
+
label="Bio"
|
|
130
|
+
placeholder="Tell us about yourself..."
|
|
131
|
+
helperText="Max 500 characters"
|
|
132
|
+
maxLength={500}
|
|
133
|
+
/>
|
|
134
|
+
),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'Error State',
|
|
138
|
+
description: 'Textarea showing validation error',
|
|
139
|
+
render: () => (
|
|
140
|
+
<Textarea
|
|
141
|
+
label="Comments"
|
|
142
|
+
placeholder="Add your comments..."
|
|
143
|
+
error
|
|
144
|
+
helperText="This field is required"
|
|
145
|
+
/>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'Disabled',
|
|
150
|
+
description: 'Non-interactive textarea',
|
|
151
|
+
render: () => (
|
|
152
|
+
<Textarea
|
|
153
|
+
label="Notes"
|
|
154
|
+
placeholder="Cannot edit..."
|
|
155
|
+
disabled
|
|
156
|
+
/>
|
|
157
|
+
),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'Custom Rows',
|
|
161
|
+
description: 'Textarea with more visible rows',
|
|
162
|
+
render: () => (
|
|
163
|
+
<Textarea
|
|
164
|
+
label="Long Description"
|
|
165
|
+
placeholder="Enter detailed information..."
|
|
166
|
+
rows={6}
|
|
167
|
+
/>
|
|
168
|
+
),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
.wrapper {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.label {
|
|
10
|
+
@include label-text;
|
|
11
|
+
margin-bottom: var(--fui-space-1, $fui-space-1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.required {
|
|
15
|
+
color: var(--fui-color-danger, $fui-color-danger);
|
|
16
|
+
margin-left: 2px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.textarea {
|
|
20
|
+
@include text-base;
|
|
21
|
+
@include interactive-base;
|
|
22
|
+
|
|
23
|
+
display: block;
|
|
24
|
+
width: 100%;
|
|
25
|
+
min-height: var(--fui-input-height, $fui-input-height);
|
|
26
|
+
padding: var(--fui-space-2, $fui-space-2) var(--fui-space-3, $fui-space-3);
|
|
27
|
+
background-color: var(--fui-bg-elevated, $fui-bg-elevated);
|
|
28
|
+
border: 1px solid var(--fui-border-strong, $fui-border-strong);
|
|
29
|
+
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
30
|
+
line-height: $fui-line-height-normal;
|
|
31
|
+
font-family: inherit;
|
|
32
|
+
|
|
33
|
+
&::placeholder {
|
|
34
|
+
color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:hover:not(:disabled):not(:focus) {
|
|
38
|
+
border-color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&:focus {
|
|
42
|
+
@include focus-ring;
|
|
43
|
+
border-color: var(--fui-color-accent, $fui-color-accent);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:disabled,
|
|
47
|
+
&[data-disabled] {
|
|
48
|
+
background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
|
|
49
|
+
color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Resize variants
|
|
54
|
+
.resize-none {
|
|
55
|
+
resize: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.resize-vertical {
|
|
59
|
+
resize: vertical;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.resize-horizontal {
|
|
63
|
+
resize: horizontal;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.resize-both {
|
|
67
|
+
resize: both;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Error state
|
|
71
|
+
.error {
|
|
72
|
+
border-color: var(--fui-color-danger, $fui-color-danger);
|
|
73
|
+
|
|
74
|
+
&:focus {
|
|
75
|
+
border-color: var(--fui-color-danger, $fui-color-danger);
|
|
76
|
+
box-shadow:
|
|
77
|
+
0 0 0 var(--fui-focus-ring-offset, $fui-focus-ring-offset) var(--fui-bg-primary, $fui-bg-primary),
|
|
78
|
+
0 0 0 calc(var(--fui-focus-ring-offset, $fui-focus-ring-offset) + var(--fui-focus-ring-width, $fui-focus-ring-width)) var(--fui-color-danger, $fui-color-danger);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.helper {
|
|
83
|
+
@include helper-text;
|
|
84
|
+
margin-top: var(--fui-space-1, $fui-space-1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.helperError {
|
|
88
|
+
color: var(--fui-color-danger, $fui-color-danger);
|
|
89
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styles from './Textarea.module.scss';
|
|
3
|
+
// Import globals to ensure CSS variables are defined
|
|
4
|
+
import '../../styles/globals.scss';
|
|
5
|
+
|
|
6
|
+
export interface TextareaProps {
|
|
7
|
+
/** Controlled value */
|
|
8
|
+
value?: string;
|
|
9
|
+
/** Default value for uncontrolled usage */
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
/** Placeholder text */
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
/** Number of visible text rows */
|
|
14
|
+
rows?: number;
|
|
15
|
+
/** Minimum number of rows (for auto-resize) */
|
|
16
|
+
minRows?: number;
|
|
17
|
+
/** Maximum number of rows (for auto-resize) */
|
|
18
|
+
maxRows?: number;
|
|
19
|
+
/** Allow user to resize the textarea */
|
|
20
|
+
resize?: 'none' | 'vertical' | 'horizontal' | 'both';
|
|
21
|
+
/** Disabled state */
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
/** Error state */
|
|
24
|
+
error?: boolean;
|
|
25
|
+
/** Label text above the textarea */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Helper text below the textarea */
|
|
28
|
+
helperText?: string;
|
|
29
|
+
/** Called when value changes */
|
|
30
|
+
onChange?: (value: string) => void;
|
|
31
|
+
/** Called when textarea loses focus */
|
|
32
|
+
onBlur?: () => void;
|
|
33
|
+
/** Additional class name */
|
|
34
|
+
className?: string;
|
|
35
|
+
/** Form field name */
|
|
36
|
+
name?: string;
|
|
37
|
+
/** Element ID */
|
|
38
|
+
id?: string;
|
|
39
|
+
/** Maximum character length */
|
|
40
|
+
maxLength?: number;
|
|
41
|
+
/** Required field */
|
|
42
|
+
required?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
46
|
+
function Textarea(
|
|
47
|
+
{
|
|
48
|
+
value,
|
|
49
|
+
defaultValue,
|
|
50
|
+
placeholder,
|
|
51
|
+
rows = 3,
|
|
52
|
+
minRows,
|
|
53
|
+
maxRows,
|
|
54
|
+
resize = 'vertical',
|
|
55
|
+
disabled = false,
|
|
56
|
+
error = false,
|
|
57
|
+
label,
|
|
58
|
+
helperText,
|
|
59
|
+
onChange,
|
|
60
|
+
onBlur,
|
|
61
|
+
className,
|
|
62
|
+
name,
|
|
63
|
+
id,
|
|
64
|
+
maxLength,
|
|
65
|
+
required = false,
|
|
66
|
+
},
|
|
67
|
+
ref
|
|
68
|
+
) {
|
|
69
|
+
const textareaId = id || React.useId();
|
|
70
|
+
const helperId = `${textareaId}-helper`;
|
|
71
|
+
|
|
72
|
+
const textareaClasses = [
|
|
73
|
+
styles.textarea,
|
|
74
|
+
error && styles.error,
|
|
75
|
+
styles[`resize-${resize}`],
|
|
76
|
+
className,
|
|
77
|
+
]
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join(' ');
|
|
80
|
+
|
|
81
|
+
const helperClasses = [styles.helper, error && styles.helperError]
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.join(' ');
|
|
84
|
+
|
|
85
|
+
// Calculate min/max height based on rows
|
|
86
|
+
const style: React.CSSProperties = {};
|
|
87
|
+
if (minRows) {
|
|
88
|
+
style.minHeight = `calc(${minRows} * 1.5em + 1.5rem)`;
|
|
89
|
+
}
|
|
90
|
+
if (maxRows) {
|
|
91
|
+
style.maxHeight = `calc(${maxRows} * 1.5em + 1.5rem)`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className={styles.wrapper}>
|
|
96
|
+
{label && (
|
|
97
|
+
<label htmlFor={textareaId} className={styles.label}>
|
|
98
|
+
{label}
|
|
99
|
+
{required && <span className={styles.required}>*</span>}
|
|
100
|
+
</label>
|
|
101
|
+
)}
|
|
102
|
+
<textarea
|
|
103
|
+
ref={ref}
|
|
104
|
+
id={textareaId}
|
|
105
|
+
value={value}
|
|
106
|
+
defaultValue={defaultValue}
|
|
107
|
+
placeholder={placeholder}
|
|
108
|
+
rows={rows}
|
|
109
|
+
name={name}
|
|
110
|
+
maxLength={maxLength}
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
required={required}
|
|
113
|
+
aria-invalid={error || undefined}
|
|
114
|
+
aria-describedby={helperText ? helperId : undefined}
|
|
115
|
+
onChange={(e) => onChange?.(e.target.value)}
|
|
116
|
+
onBlur={onBlur}
|
|
117
|
+
className={textareaClasses}
|
|
118
|
+
style={Object.keys(style).length > 0 ? style : undefined}
|
|
119
|
+
/>
|
|
120
|
+
{helperText && (
|
|
121
|
+
<span id={helperId} className={helperClasses}>
|
|
122
|
+
{helperText}
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Toast, ToastProvider, useToast } from './index.js';
|
|
4
|
+
|
|
5
|
+
// Demo component that triggers toasts
|
|
6
|
+
function ToastDemo() {
|
|
7
|
+
const { success, error, warning, info, toast } = useToast();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
|
11
|
+
<button
|
|
12
|
+
onClick={() => success('Success!', 'Your changes have been saved.')}
|
|
13
|
+
style={{ padding: '8px 16px', borderRadius: '6px', border: '1px solid #10b981', background: '#ecfdf5', color: '#065f46', cursor: 'pointer' }}
|
|
14
|
+
>
|
|
15
|
+
Success Toast
|
|
16
|
+
</button>
|
|
17
|
+
<button
|
|
18
|
+
onClick={() => error('Error', 'Something went wrong. Please try again.')}
|
|
19
|
+
style={{ padding: '8px 16px', borderRadius: '6px', border: '1px solid #ef4444', background: '#fef2f2', color: '#991b1b', cursor: 'pointer' }}
|
|
20
|
+
>
|
|
21
|
+
Error Toast
|
|
22
|
+
</button>
|
|
23
|
+
<button
|
|
24
|
+
onClick={() => warning('Warning', 'This action cannot be undone.')}
|
|
25
|
+
style={{ padding: '8px 16px', borderRadius: '6px', border: '1px solid #f59e0b', background: '#fffbeb', color: '#92400e', cursor: 'pointer' }}
|
|
26
|
+
>
|
|
27
|
+
Warning Toast
|
|
28
|
+
</button>
|
|
29
|
+
<button
|
|
30
|
+
onClick={() => info('Info', 'New features are available.')}
|
|
31
|
+
style={{ padding: '8px 16px', borderRadius: '6px', border: '1px solid #3b82f6', background: '#eff6ff', color: '#1e40af', cursor: 'pointer' }}
|
|
32
|
+
>
|
|
33
|
+
Info Toast
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Wrapped demo with provider
|
|
40
|
+
function ToastDemoWrapper() {
|
|
41
|
+
return (
|
|
42
|
+
<ToastProvider position="bottom-right" duration={5000}>
|
|
43
|
+
<ToastDemo />
|
|
44
|
+
</ToastProvider>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default defineSegment({
|
|
49
|
+
component: Toast,
|
|
50
|
+
|
|
51
|
+
meta: {
|
|
52
|
+
name: 'Toast',
|
|
53
|
+
description: 'Brief, non-blocking notification messages',
|
|
54
|
+
category: 'feedback',
|
|
55
|
+
status: 'stable',
|
|
56
|
+
tags: ['notification', 'alert', 'message', 'feedback'],
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
usage: {
|
|
60
|
+
when: [
|
|
61
|
+
'Providing feedback after an action',
|
|
62
|
+
'Showing success/error status of operations',
|
|
63
|
+
'Non-critical information that doesn\'t require action',
|
|
64
|
+
'Temporary messages that auto-dismiss',
|
|
65
|
+
],
|
|
66
|
+
whenNot: [
|
|
67
|
+
'Critical errors requiring user action (use Dialog)',
|
|
68
|
+
'Persistent information (use Alert)',
|
|
69
|
+
'Inline validation (use form error states)',
|
|
70
|
+
'System-wide announcements (use Banner)',
|
|
71
|
+
],
|
|
72
|
+
guidelines: [
|
|
73
|
+
'Keep messages brief and actionable',
|
|
74
|
+
'Use appropriate variant for the message type',
|
|
75
|
+
'Auto-dismiss after reasonable duration (3-5s)',
|
|
76
|
+
'Allow manual dismissal for longer messages',
|
|
77
|
+
'Limit number of simultaneous toasts',
|
|
78
|
+
],
|
|
79
|
+
accessibility: [
|
|
80
|
+
'Use role="alert" for important messages',
|
|
81
|
+
'Ensure sufficient display time for reading',
|
|
82
|
+
'Don\'t rely solely on color for meaning',
|
|
83
|
+
'Provide dismiss button with accessible label',
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
props: {
|
|
88
|
+
title: {
|
|
89
|
+
type: 'string',
|
|
90
|
+
description: 'Toast title',
|
|
91
|
+
},
|
|
92
|
+
description: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
description: 'Additional message content',
|
|
95
|
+
},
|
|
96
|
+
variant: {
|
|
97
|
+
type: 'enum',
|
|
98
|
+
values: ['default', 'success', 'error', 'warning', 'info'],
|
|
99
|
+
default: 'default',
|
|
100
|
+
description: 'Visual variant indicating message type',
|
|
101
|
+
},
|
|
102
|
+
duration: {
|
|
103
|
+
type: 'number',
|
|
104
|
+
default: 5000,
|
|
105
|
+
description: 'Auto-dismiss duration in ms (0 = no auto-dismiss)',
|
|
106
|
+
},
|
|
107
|
+
action: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
description: 'Optional action button { label, onClick }',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
relations: [
|
|
114
|
+
{
|
|
115
|
+
component: 'Alert',
|
|
116
|
+
relationship: 'alternative',
|
|
117
|
+
note: 'Use Alert for persistent inline messages',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
component: 'Dialog',
|
|
121
|
+
relationship: 'alternative',
|
|
122
|
+
note: 'Use Dialog for messages requiring user action',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
|
|
126
|
+
contract: {
|
|
127
|
+
propsSummary: [
|
|
128
|
+
'title: string - toast title',
|
|
129
|
+
'description: string - additional message',
|
|
130
|
+
'variant: default|success|error|warning|info',
|
|
131
|
+
'duration: number - ms before auto-dismiss',
|
|
132
|
+
'action: { label, onClick } - optional action',
|
|
133
|
+
],
|
|
134
|
+
scenarioTags: [
|
|
135
|
+
'feedback.success',
|
|
136
|
+
'feedback.error',
|
|
137
|
+
'feedback.notification',
|
|
138
|
+
],
|
|
139
|
+
a11yRules: [
|
|
140
|
+
'A11Y_ALERT_ROLE',
|
|
141
|
+
],
|
|
142
|
+
bans: [],
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
variants: [
|
|
146
|
+
{
|
|
147
|
+
name: 'Default',
|
|
148
|
+
description: 'Interactive toast demo - click buttons to trigger toasts',
|
|
149
|
+
render: () => <ToastDemoWrapper />,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'Success',
|
|
153
|
+
description: 'Success message variant',
|
|
154
|
+
render: () => (
|
|
155
|
+
<Toast
|
|
156
|
+
title="Success!"
|
|
157
|
+
description="Your changes have been saved."
|
|
158
|
+
variant="success"
|
|
159
|
+
/>
|
|
160
|
+
),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'Error',
|
|
164
|
+
description: 'Error message variant',
|
|
165
|
+
render: () => (
|
|
166
|
+
<Toast
|
|
167
|
+
title="Error"
|
|
168
|
+
description="Failed to save changes. Please try again."
|
|
169
|
+
variant="error"
|
|
170
|
+
/>
|
|
171
|
+
),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'Warning',
|
|
175
|
+
description: 'Warning message variant',
|
|
176
|
+
render: () => (
|
|
177
|
+
<Toast
|
|
178
|
+
title="Warning"
|
|
179
|
+
description="This action cannot be undone."
|
|
180
|
+
variant="warning"
|
|
181
|
+
/>
|
|
182
|
+
),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'Info',
|
|
186
|
+
description: 'Informational message variant',
|
|
187
|
+
render: () => (
|
|
188
|
+
<Toast
|
|
189
|
+
title="New Update"
|
|
190
|
+
description="Version 2.0 is now available."
|
|
191
|
+
variant="info"
|
|
192
|
+
/>
|
|
193
|
+
),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: 'With Action',
|
|
197
|
+
description: 'Toast with an action button',
|
|
198
|
+
render: () => (
|
|
199
|
+
<Toast
|
|
200
|
+
title="File deleted"
|
|
201
|
+
description="The file has been moved to trash."
|
|
202
|
+
action={{
|
|
203
|
+
label: 'Undo',
|
|
204
|
+
onClick: () => console.log('Undo clicked'),
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
),
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
});
|