@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,166 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Checkbox } from './index.js';
|
|
4
|
+
|
|
5
|
+
// Stateful wrapper for interactive demos
|
|
6
|
+
function StatefulCheckbox(props: React.ComponentProps<typeof Checkbox>) {
|
|
7
|
+
const [checked, setChecked] = useState(props.checked ?? props.defaultChecked ?? false);
|
|
8
|
+
return <Checkbox {...props} checked={checked} onCheckedChange={setChecked} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default defineSegment({
|
|
12
|
+
component: Checkbox,
|
|
13
|
+
|
|
14
|
+
meta: {
|
|
15
|
+
name: 'Checkbox',
|
|
16
|
+
description: 'Binary toggle for form fields. Use for options that require explicit submission, unlike Toggle which takes effect immediately.',
|
|
17
|
+
category: 'forms',
|
|
18
|
+
status: 'stable',
|
|
19
|
+
tags: ['checkbox', 'form', 'boolean', 'selection', 'input'],
|
|
20
|
+
since: '0.1.0',
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
usage: {
|
|
24
|
+
when: [
|
|
25
|
+
'Form fields requiring explicit submission',
|
|
26
|
+
'Multi-select from a list of options',
|
|
27
|
+
'Terms and conditions acceptance',
|
|
28
|
+
'Filter or preference checklists',
|
|
29
|
+
],
|
|
30
|
+
whenNot: [
|
|
31
|
+
'Immediate effect settings (use Toggle)',
|
|
32
|
+
'Single selection from options (use RadioGroup)',
|
|
33
|
+
'Selecting from many options (use Select)',
|
|
34
|
+
],
|
|
35
|
+
guidelines: [
|
|
36
|
+
'Always include a visible label',
|
|
37
|
+
'Use description for additional context when needed',
|
|
38
|
+
'Group related checkboxes visually',
|
|
39
|
+
'Use indeterminate state for parent/child relationships',
|
|
40
|
+
],
|
|
41
|
+
accessibility: [
|
|
42
|
+
'Proper label association',
|
|
43
|
+
'Keyboard accessible (Space to toggle)',
|
|
44
|
+
'Visible focus indicator',
|
|
45
|
+
'Indeterminate state properly announced',
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
props: {
|
|
50
|
+
checked: {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
description: 'Controlled checked state',
|
|
53
|
+
},
|
|
54
|
+
defaultChecked: {
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
description: 'Default checked state (uncontrolled)',
|
|
57
|
+
},
|
|
58
|
+
onCheckedChange: {
|
|
59
|
+
type: 'function',
|
|
60
|
+
description: 'Called when checked state changes',
|
|
61
|
+
},
|
|
62
|
+
indeterminate: {
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
description: 'Indeterminate state (partial selection)',
|
|
65
|
+
default: 'false',
|
|
66
|
+
},
|
|
67
|
+
disabled: {
|
|
68
|
+
type: 'boolean',
|
|
69
|
+
description: 'Disable the checkbox',
|
|
70
|
+
default: 'false',
|
|
71
|
+
},
|
|
72
|
+
label: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'Label text',
|
|
75
|
+
},
|
|
76
|
+
description: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'Description text below the label',
|
|
79
|
+
},
|
|
80
|
+
size: {
|
|
81
|
+
type: 'enum',
|
|
82
|
+
description: 'Checkbox size',
|
|
83
|
+
values: ['sm', 'md', 'lg'],
|
|
84
|
+
default: 'md',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
relations: [
|
|
89
|
+
{ component: 'Toggle', relationship: 'alternative', note: 'Use Toggle for immediate-effect settings' },
|
|
90
|
+
{ component: 'Input', relationship: 'sibling', note: 'Checkbox handles boolean; Input handles text' },
|
|
91
|
+
],
|
|
92
|
+
|
|
93
|
+
contract: {
|
|
94
|
+
propsSummary: [
|
|
95
|
+
'checked: boolean - controlled checked state',
|
|
96
|
+
'onCheckedChange: (checked) => void - change handler',
|
|
97
|
+
'indeterminate: boolean - partial selection state',
|
|
98
|
+
'label: string - checkbox label',
|
|
99
|
+
'description: string - helper text',
|
|
100
|
+
],
|
|
101
|
+
scenarioTags: [
|
|
102
|
+
'form.checkbox',
|
|
103
|
+
'form.boolean',
|
|
104
|
+
'selection.multi',
|
|
105
|
+
],
|
|
106
|
+
a11yRules: ['A11Y_CHECKBOX_LABEL', 'A11Y_CHECKBOX_FOCUS'],
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
variants: [
|
|
110
|
+
{
|
|
111
|
+
name: 'Default',
|
|
112
|
+
description: 'Basic checkbox with label',
|
|
113
|
+
render: () => <StatefulCheckbox label="Accept terms and conditions" />,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'With Description',
|
|
117
|
+
description: 'Checkbox with helper text',
|
|
118
|
+
render: () => (
|
|
119
|
+
<StatefulCheckbox
|
|
120
|
+
label="Email notifications"
|
|
121
|
+
description="Receive email updates about your account activity"
|
|
122
|
+
/>
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'Checked',
|
|
127
|
+
description: 'Pre-checked checkbox',
|
|
128
|
+
render: () => <StatefulCheckbox defaultChecked label="Subscribe to newsletter" />,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'Indeterminate',
|
|
132
|
+
description: 'Partial selection state',
|
|
133
|
+
render: () => (
|
|
134
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
135
|
+
<Checkbox indeterminate label="Select all" />
|
|
136
|
+
<div style={{ marginLeft: '24px', display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
137
|
+
<StatefulCheckbox defaultChecked label="Option 1" />
|
|
138
|
+
<StatefulCheckbox label="Option 2" />
|
|
139
|
+
<StatefulCheckbox defaultChecked label="Option 3" />
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'Sizes',
|
|
146
|
+
description: 'Available size options',
|
|
147
|
+
render: () => (
|
|
148
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
149
|
+
<StatefulCheckbox size="sm" label="Small checkbox" />
|
|
150
|
+
<StatefulCheckbox size="md" label="Medium checkbox" />
|
|
151
|
+
<StatefulCheckbox size="lg" label="Large checkbox" />
|
|
152
|
+
</div>
|
|
153
|
+
),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'Disabled',
|
|
157
|
+
description: 'Non-interactive states',
|
|
158
|
+
render: () => (
|
|
159
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
160
|
+
<Checkbox disabled label="Disabled unchecked" />
|
|
161
|
+
<Checkbox disabled checked label="Disabled checked" />
|
|
162
|
+
</div>
|
|
163
|
+
),
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
// Wrapper for checkbox + label
|
|
5
|
+
.wrapper {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: flex-start;
|
|
8
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
font-family: var(--fui-font-sans, $fui-font-sans);
|
|
11
|
+
|
|
12
|
+
&[data-disabled] {
|
|
13
|
+
cursor: not-allowed;
|
|
14
|
+
opacity: 0.5;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// The checkbox box itself
|
|
19
|
+
.checkbox {
|
|
20
|
+
@include interactive-base;
|
|
21
|
+
|
|
22
|
+
position: relative;
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
width: 1rem;
|
|
28
|
+
height: 1rem;
|
|
29
|
+
margin-top: 2px; // Align with text baseline
|
|
30
|
+
background-color: var(--fui-bg-elevated, $fui-bg-elevated);
|
|
31
|
+
border: 1px solid var(--fui-border-strong, $fui-border-strong);
|
|
32
|
+
border-radius: var(--fui-radius-sm, $fui-radius-sm);
|
|
33
|
+
cursor: inherit;
|
|
34
|
+
|
|
35
|
+
&:hover:not([data-disabled]) {
|
|
36
|
+
border-color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&[data-checked],
|
|
40
|
+
&[data-indeterminate] {
|
|
41
|
+
background-color: var(--fui-color-accent, $fui-color-accent);
|
|
42
|
+
border-color: var(--fui-color-accent, $fui-color-accent);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&[data-checked]:hover:not([data-disabled]),
|
|
46
|
+
&[data-indeterminate]:hover:not([data-disabled]) {
|
|
47
|
+
background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
|
|
48
|
+
border-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&[data-invalid] {
|
|
52
|
+
border-color: var(--fui-color-danger, $fui-color-danger);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&[data-invalid][data-checked],
|
|
56
|
+
&[data-invalid][data-indeterminate] {
|
|
57
|
+
background-color: var(--fui-color-danger, $fui-color-danger);
|
|
58
|
+
border-color: var(--fui-color-danger, $fui-color-danger);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Size variants
|
|
63
|
+
.sm {
|
|
64
|
+
width: 0.875rem;
|
|
65
|
+
height: 0.875rem;
|
|
66
|
+
border-radius: 3px;
|
|
67
|
+
margin-top: 3px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.md {
|
|
71
|
+
// Default size
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.lg {
|
|
75
|
+
width: 1.25rem;
|
|
76
|
+
height: 1.25rem;
|
|
77
|
+
margin-top: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Indicator (checkmark or dash)
|
|
81
|
+
.indicator {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
color: var(--fui-text-inverse, $fui-text-inverse);
|
|
86
|
+
|
|
87
|
+
svg {
|
|
88
|
+
width: 0.75rem;
|
|
89
|
+
height: 0.75rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Animation
|
|
93
|
+
opacity: 0;
|
|
94
|
+
transform: scale(0.8);
|
|
95
|
+
transition:
|
|
96
|
+
opacity var(--fui-transition-fast, $fui-transition-fast),
|
|
97
|
+
transform var(--fui-transition-fast, $fui-transition-fast);
|
|
98
|
+
|
|
99
|
+
[data-checked] > &,
|
|
100
|
+
[data-indeterminate] > & {
|
|
101
|
+
opacity: 1;
|
|
102
|
+
transform: scale(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Size variant indicators
|
|
107
|
+
.sm .indicator svg {
|
|
108
|
+
width: 0.625rem;
|
|
109
|
+
height: 0.625rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.lg .indicator svg {
|
|
113
|
+
width: 0.875rem;
|
|
114
|
+
height: 0.875rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Label content
|
|
118
|
+
.content {
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-direction: column;
|
|
121
|
+
gap: 2px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.label {
|
|
125
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
126
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
127
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
128
|
+
line-height: var(--fui-line-height-tight, $fui-line-height-tight);
|
|
129
|
+
user-select: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.labelSm {
|
|
133
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.labelLg {
|
|
137
|
+
font-size: var(--fui-font-size-base, $fui-font-size-base);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.description {
|
|
141
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
142
|
+
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
143
|
+
line-height: var(--fui-line-height-normal, $fui-line-height-normal);
|
|
144
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
|
|
3
|
+
import styles from './Checkbox.module.scss';
|
|
4
|
+
// Import globals to ensure CSS variables are defined
|
|
5
|
+
import '../../styles/globals.scss';
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// Types
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
export interface CheckboxProps {
|
|
12
|
+
/** Whether the checkbox is checked */
|
|
13
|
+
checked?: boolean;
|
|
14
|
+
/** Default checked state (uncontrolled) */
|
|
15
|
+
defaultChecked?: boolean;
|
|
16
|
+
/** Callback when checked state changes */
|
|
17
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
18
|
+
/** Whether the checkbox is in an indeterminate state */
|
|
19
|
+
indeterminate?: boolean;
|
|
20
|
+
/** Whether the checkbox is disabled */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Whether the checkbox is required */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/** Size variant */
|
|
25
|
+
size?: 'sm' | 'md' | 'lg';
|
|
26
|
+
/** Label text */
|
|
27
|
+
label?: string;
|
|
28
|
+
/** Description text below the label */
|
|
29
|
+
description?: string;
|
|
30
|
+
/** Name attribute for form submission */
|
|
31
|
+
name?: string;
|
|
32
|
+
/** Value attribute for form submission */
|
|
33
|
+
value?: string;
|
|
34
|
+
/** Additional class name */
|
|
35
|
+
className?: string;
|
|
36
|
+
/** ID for the checkbox input */
|
|
37
|
+
id?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================
|
|
41
|
+
// Icons
|
|
42
|
+
// ============================================
|
|
43
|
+
|
|
44
|
+
function CheckIcon() {
|
|
45
|
+
return (
|
|
46
|
+
<svg
|
|
47
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
48
|
+
viewBox="0 0 24 24"
|
|
49
|
+
fill="none"
|
|
50
|
+
stroke="currentColor"
|
|
51
|
+
strokeWidth="3"
|
|
52
|
+
strokeLinecap="round"
|
|
53
|
+
strokeLinejoin="round"
|
|
54
|
+
aria-hidden="true"
|
|
55
|
+
>
|
|
56
|
+
<polyline points="20 6 9 17 4 12" />
|
|
57
|
+
</svg>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function MinusIcon() {
|
|
62
|
+
return (
|
|
63
|
+
<svg
|
|
64
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
+
viewBox="0 0 24 24"
|
|
66
|
+
fill="none"
|
|
67
|
+
stroke="currentColor"
|
|
68
|
+
strokeWidth="3"
|
|
69
|
+
strokeLinecap="round"
|
|
70
|
+
strokeLinejoin="round"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
>
|
|
73
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
74
|
+
</svg>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================
|
|
79
|
+
// Component
|
|
80
|
+
// ============================================
|
|
81
|
+
|
|
82
|
+
export const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
|
|
83
|
+
function Checkbox(
|
|
84
|
+
{
|
|
85
|
+
checked,
|
|
86
|
+
defaultChecked,
|
|
87
|
+
onCheckedChange,
|
|
88
|
+
indeterminate = false,
|
|
89
|
+
disabled = false,
|
|
90
|
+
required = false,
|
|
91
|
+
size = 'md',
|
|
92
|
+
label,
|
|
93
|
+
description,
|
|
94
|
+
name,
|
|
95
|
+
value,
|
|
96
|
+
className,
|
|
97
|
+
id,
|
|
98
|
+
},
|
|
99
|
+
ref
|
|
100
|
+
) {
|
|
101
|
+
const checkboxClasses = [
|
|
102
|
+
styles.checkbox,
|
|
103
|
+
size === 'sm' && styles.sm,
|
|
104
|
+
size === 'lg' && styles.lg,
|
|
105
|
+
].filter(Boolean).join(' ');
|
|
106
|
+
|
|
107
|
+
const labelClasses = [
|
|
108
|
+
styles.label,
|
|
109
|
+
size === 'sm' && styles.labelSm,
|
|
110
|
+
size === 'lg' && styles.labelLg,
|
|
111
|
+
].filter(Boolean).join(' ');
|
|
112
|
+
|
|
113
|
+
const wrapperClasses = [styles.wrapper, className].filter(Boolean).join(' ');
|
|
114
|
+
|
|
115
|
+
// If no label/description, render just the checkbox
|
|
116
|
+
if (!label && !description) {
|
|
117
|
+
return (
|
|
118
|
+
<BaseCheckbox.Root
|
|
119
|
+
ref={ref}
|
|
120
|
+
checked={checked}
|
|
121
|
+
defaultChecked={defaultChecked}
|
|
122
|
+
onCheckedChange={onCheckedChange}
|
|
123
|
+
indeterminate={indeterminate}
|
|
124
|
+
disabled={disabled}
|
|
125
|
+
required={required}
|
|
126
|
+
name={name}
|
|
127
|
+
value={value}
|
|
128
|
+
id={id}
|
|
129
|
+
className={[checkboxClasses, className].filter(Boolean).join(' ')}
|
|
130
|
+
>
|
|
131
|
+
<BaseCheckbox.Indicator className={styles.indicator} keepMounted>
|
|
132
|
+
{indeterminate ? <MinusIcon /> : <CheckIcon />}
|
|
133
|
+
</BaseCheckbox.Indicator>
|
|
134
|
+
</BaseCheckbox.Root>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<label className={wrapperClasses} data-disabled={disabled || undefined}>
|
|
140
|
+
<BaseCheckbox.Root
|
|
141
|
+
ref={ref}
|
|
142
|
+
checked={checked}
|
|
143
|
+
defaultChecked={defaultChecked}
|
|
144
|
+
onCheckedChange={onCheckedChange}
|
|
145
|
+
indeterminate={indeterminate}
|
|
146
|
+
disabled={disabled}
|
|
147
|
+
required={required}
|
|
148
|
+
name={name}
|
|
149
|
+
value={value}
|
|
150
|
+
id={id}
|
|
151
|
+
className={checkboxClasses}
|
|
152
|
+
>
|
|
153
|
+
<BaseCheckbox.Indicator className={styles.indicator} keepMounted>
|
|
154
|
+
{indeterminate ? <MinusIcon /> : <CheckIcon />}
|
|
155
|
+
</BaseCheckbox.Indicator>
|
|
156
|
+
</BaseCheckbox.Root>
|
|
157
|
+
<div className={styles.content}>
|
|
158
|
+
<span className={labelClasses}>{label}</span>
|
|
159
|
+
{description && (
|
|
160
|
+
<span className={styles.description}>{description}</span>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
</label>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Dialog } from './index.js';
|
|
4
|
+
import { Button } from '../Button/index.js';
|
|
5
|
+
|
|
6
|
+
export default defineSegment({
|
|
7
|
+
component: Dialog,
|
|
8
|
+
|
|
9
|
+
meta: {
|
|
10
|
+
name: 'Dialog',
|
|
11
|
+
description: 'Modal overlay for focused user interactions. Use for confirmations, forms, or content requiring full attention.',
|
|
12
|
+
category: 'overlays',
|
|
13
|
+
status: 'stable',
|
|
14
|
+
tags: ['modal', 'dialog', 'overlay', 'popup', 'confirmation'],
|
|
15
|
+
since: '0.1.0',
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
usage: {
|
|
19
|
+
when: [
|
|
20
|
+
'Confirming destructive actions (delete, discard changes)',
|
|
21
|
+
'Collecting focused input (forms, settings)',
|
|
22
|
+
'Displaying content that requires acknowledgment',
|
|
23
|
+
'Multi-step workflows that need isolation',
|
|
24
|
+
],
|
|
25
|
+
whenNot: [
|
|
26
|
+
'Simple tooltips or hints (use Tooltip)',
|
|
27
|
+
'Contextual menus (use Menu or Popover)',
|
|
28
|
+
'Non-blocking notifications (use Toast or Alert)',
|
|
29
|
+
'Simple confirmation that can be inline (use Alert)',
|
|
30
|
+
],
|
|
31
|
+
guidelines: [
|
|
32
|
+
'Keep dialog content focused on a single task',
|
|
33
|
+
'Provide clear primary and secondary actions',
|
|
34
|
+
'Use descriptive title that explains the purpose',
|
|
35
|
+
'Allow dismissal via backdrop click or close button for non-critical dialogs',
|
|
36
|
+
'Trap focus within the dialog for accessibility',
|
|
37
|
+
],
|
|
38
|
+
accessibility: [
|
|
39
|
+
'Automatically traps focus within the dialog',
|
|
40
|
+
'Closes on Escape key press',
|
|
41
|
+
'Returns focus to trigger element on close',
|
|
42
|
+
'Uses role="dialog" with proper aria attributes',
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
props: {
|
|
47
|
+
children: {
|
|
48
|
+
type: 'node',
|
|
49
|
+
description: 'Dialog content (use Dialog.Content, Dialog.Header, etc.)',
|
|
50
|
+
required: true,
|
|
51
|
+
},
|
|
52
|
+
open: {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
description: 'Controlled open state',
|
|
55
|
+
},
|
|
56
|
+
defaultOpen: {
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
description: 'Default open state (uncontrolled)',
|
|
59
|
+
default: 'false',
|
|
60
|
+
},
|
|
61
|
+
onOpenChange: {
|
|
62
|
+
type: 'function',
|
|
63
|
+
description: 'Called when open state changes',
|
|
64
|
+
},
|
|
65
|
+
modal: {
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
description: 'Whether to render as modal (blocks interaction with rest of page)',
|
|
68
|
+
default: 'true',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
relations: [
|
|
73
|
+
{ component: 'Popover', relationship: 'alternative', note: 'Use Popover for non-modal contextual content' },
|
|
74
|
+
{ component: 'Menu', relationship: 'alternative', note: 'Use Menu for action lists' },
|
|
75
|
+
{ component: 'Alert', relationship: 'sibling', note: 'Use Alert for inline notifications' },
|
|
76
|
+
],
|
|
77
|
+
|
|
78
|
+
contract: {
|
|
79
|
+
propsSummary: [
|
|
80
|
+
'open: boolean - controlled open state',
|
|
81
|
+
'onOpenChange: (open) => void - open state handler',
|
|
82
|
+
'modal: boolean - blocks page interaction (default: true)',
|
|
83
|
+
'Dialog.Content size: sm|md|lg|xl|full - dialog width',
|
|
84
|
+
],
|
|
85
|
+
scenarioTags: [
|
|
86
|
+
'overlay.modal',
|
|
87
|
+
'form.dialog',
|
|
88
|
+
'action.confirm',
|
|
89
|
+
'workflow.step',
|
|
90
|
+
],
|
|
91
|
+
a11yRules: ['A11Y_DIALOG_FOCUS', 'A11Y_DIALOG_ESCAPE', 'A11Y_DIALOG_LABEL'],
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
variants: [
|
|
95
|
+
{
|
|
96
|
+
name: 'Default',
|
|
97
|
+
description: 'Basic dialog with header, body, and footer',
|
|
98
|
+
render: () => (
|
|
99
|
+
<Dialog>
|
|
100
|
+
<Dialog.Trigger asChild>
|
|
101
|
+
<Button>Open Dialog</Button>
|
|
102
|
+
</Dialog.Trigger>
|
|
103
|
+
<Dialog.Content>
|
|
104
|
+
<Dialog.Close />
|
|
105
|
+
<Dialog.Header>
|
|
106
|
+
<Dialog.Title>Dialog Title</Dialog.Title>
|
|
107
|
+
<Dialog.Description>
|
|
108
|
+
A brief description of what this dialog is for.
|
|
109
|
+
</Dialog.Description>
|
|
110
|
+
</Dialog.Header>
|
|
111
|
+
<Dialog.Body>
|
|
112
|
+
<p>Dialog content goes here. You can include forms, text, or any other content.</p>
|
|
113
|
+
</Dialog.Body>
|
|
114
|
+
<Dialog.Footer>
|
|
115
|
+
<Dialog.Close asChild>
|
|
116
|
+
<Button variant="secondary">Cancel</Button>
|
|
117
|
+
</Dialog.Close>
|
|
118
|
+
<Button variant="primary">Confirm</Button>
|
|
119
|
+
</Dialog.Footer>
|
|
120
|
+
</Dialog.Content>
|
|
121
|
+
</Dialog>
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'Confirmation',
|
|
126
|
+
description: 'Destructive action confirmation',
|
|
127
|
+
render: () => (
|
|
128
|
+
<Dialog>
|
|
129
|
+
<Dialog.Trigger asChild>
|
|
130
|
+
<Button variant="danger">Delete Item</Button>
|
|
131
|
+
</Dialog.Trigger>
|
|
132
|
+
<Dialog.Content size="sm">
|
|
133
|
+
<Dialog.Header>
|
|
134
|
+
<Dialog.Title>Delete item?</Dialog.Title>
|
|
135
|
+
<Dialog.Description>
|
|
136
|
+
This action cannot be undone. The item will be permanently removed.
|
|
137
|
+
</Dialog.Description>
|
|
138
|
+
</Dialog.Header>
|
|
139
|
+
<Dialog.Footer>
|
|
140
|
+
<Dialog.Close asChild>
|
|
141
|
+
<Button variant="secondary">Cancel</Button>
|
|
142
|
+
</Dialog.Close>
|
|
143
|
+
<Button variant="danger">Delete</Button>
|
|
144
|
+
</Dialog.Footer>
|
|
145
|
+
</Dialog.Content>
|
|
146
|
+
</Dialog>
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'Large',
|
|
151
|
+
description: 'Large dialog for complex content',
|
|
152
|
+
render: () => (
|
|
153
|
+
<Dialog>
|
|
154
|
+
<Dialog.Trigger asChild>
|
|
155
|
+
<Button>Open Large Dialog</Button>
|
|
156
|
+
</Dialog.Trigger>
|
|
157
|
+
<Dialog.Content size="lg">
|
|
158
|
+
<Dialog.Close />
|
|
159
|
+
<Dialog.Header>
|
|
160
|
+
<Dialog.Title>Settings</Dialog.Title>
|
|
161
|
+
<Dialog.Description>
|
|
162
|
+
Configure your application preferences.
|
|
163
|
+
</Dialog.Description>
|
|
164
|
+
</Dialog.Header>
|
|
165
|
+
<Dialog.Body>
|
|
166
|
+
<p>This dialog has more space for complex forms or content layouts.</p>
|
|
167
|
+
</Dialog.Body>
|
|
168
|
+
<Dialog.Footer>
|
|
169
|
+
<Dialog.Close asChild>
|
|
170
|
+
<Button variant="secondary">Cancel</Button>
|
|
171
|
+
</Dialog.Close>
|
|
172
|
+
<Button variant="primary">Save Changes</Button>
|
|
173
|
+
</Dialog.Footer>
|
|
174
|
+
</Dialog.Content>
|
|
175
|
+
</Dialog>
|
|
176
|
+
),
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
});
|