@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,103 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
.root {
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: flex-start;
|
|
7
|
+
gap: var(--fui-space-3, $fui-space-3);
|
|
8
|
+
cursor: pointer;
|
|
9
|
+
font-family: var(--fui-font-sans, $fui-font-sans);
|
|
10
|
+
|
|
11
|
+
&[data-disabled] {
|
|
12
|
+
cursor: not-allowed;
|
|
13
|
+
opacity: 0.5;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.track {
|
|
18
|
+
@include button-reset;
|
|
19
|
+
@include interactive-base;
|
|
20
|
+
|
|
21
|
+
position: relative;
|
|
22
|
+
flex-shrink: 0;
|
|
23
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
24
|
+
margin-top: 2px;
|
|
25
|
+
background-color: var(--fui-border-strong, $fui-border-strong);
|
|
26
|
+
|
|
27
|
+
&[data-checked] {
|
|
28
|
+
background-color: var(--fui-color-accent, $fui-color-accent);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&:hover:not(:disabled) {
|
|
32
|
+
opacity: 0.9;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.trackSm {
|
|
37
|
+
width: var(--fui-toggle-width-sm, $fui-toggle-width-sm);
|
|
38
|
+
height: var(--fui-toggle-height-sm, $fui-toggle-height-sm);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.trackMd {
|
|
42
|
+
width: var(--fui-toggle-width-md, $fui-toggle-width-md);
|
|
43
|
+
height: var(--fui-toggle-height-md, $fui-toggle-height-md);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.thumb {
|
|
47
|
+
position: absolute;
|
|
48
|
+
top: 2px;
|
|
49
|
+
left: 2px;
|
|
50
|
+
border-radius: 50%;
|
|
51
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
52
|
+
box-shadow: var(--fui-shadow-sm, $fui-shadow-sm);
|
|
53
|
+
transition: transform var(--fui-transition-normal, $fui-transition-normal);
|
|
54
|
+
|
|
55
|
+
[data-checked] > & {
|
|
56
|
+
// Move thumb to the right when checked
|
|
57
|
+
transform: translateX(calc(100%));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.thumbSm {
|
|
62
|
+
width: 14px;
|
|
63
|
+
height: 14px;
|
|
64
|
+
|
|
65
|
+
[data-checked] > & {
|
|
66
|
+
transform: translateX(14px);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.thumbMd {
|
|
71
|
+
width: 18px;
|
|
72
|
+
height: 18px;
|
|
73
|
+
|
|
74
|
+
[data-checked] > & {
|
|
75
|
+
transform: translateX(18px);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.content {
|
|
80
|
+
flex: 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.label {
|
|
84
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
85
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
86
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
87
|
+
line-height: var(--fui-line-height-tight, $fui-line-height-tight);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.labelSm {
|
|
91
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.description {
|
|
95
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
96
|
+
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
97
|
+
margin-top: 2px;
|
|
98
|
+
line-height: var(--fui-line-height-tight, $fui-line-height-tight);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.descriptionSm {
|
|
102
|
+
font-size: var(--fui-font-size-2xs, $fui-font-size-2xs);
|
|
103
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Switch } from '@base-ui/react/switch';
|
|
3
|
+
import styles from './Toggle.module.scss';
|
|
4
|
+
// Import globals to ensure CSS variables are defined
|
|
5
|
+
import '../../styles/globals.scss';
|
|
6
|
+
|
|
7
|
+
export interface ToggleProps {
|
|
8
|
+
checked?: boolean;
|
|
9
|
+
defaultChecked?: boolean;
|
|
10
|
+
onChange?: (checked: boolean) => void;
|
|
11
|
+
label?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
size?: 'sm' | 'md';
|
|
15
|
+
className?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Toggle = React.forwardRef<HTMLButtonElement, ToggleProps>(
|
|
20
|
+
function Toggle(
|
|
21
|
+
{
|
|
22
|
+
checked,
|
|
23
|
+
defaultChecked,
|
|
24
|
+
onChange,
|
|
25
|
+
label,
|
|
26
|
+
description,
|
|
27
|
+
disabled = false,
|
|
28
|
+
size = 'md',
|
|
29
|
+
className,
|
|
30
|
+
name,
|
|
31
|
+
},
|
|
32
|
+
ref
|
|
33
|
+
) {
|
|
34
|
+
const trackClasses = [
|
|
35
|
+
styles.track,
|
|
36
|
+
size === 'sm' ? styles.trackSm : styles.trackMd,
|
|
37
|
+
].join(' ');
|
|
38
|
+
|
|
39
|
+
const thumbClasses = [
|
|
40
|
+
styles.thumb,
|
|
41
|
+
size === 'sm' ? styles.thumbSm : styles.thumbMd,
|
|
42
|
+
].join(' ');
|
|
43
|
+
|
|
44
|
+
const labelClasses = [styles.label, size === 'sm' && styles.labelSm]
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.join(' ');
|
|
47
|
+
|
|
48
|
+
const descClasses = [
|
|
49
|
+
styles.description,
|
|
50
|
+
size === 'sm' && styles.descriptionSm,
|
|
51
|
+
]
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.join(' ');
|
|
54
|
+
|
|
55
|
+
const rootClasses = [styles.root, className].filter(Boolean).join(' ');
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Switch.Root
|
|
59
|
+
ref={ref}
|
|
60
|
+
checked={checked}
|
|
61
|
+
defaultChecked={defaultChecked}
|
|
62
|
+
onCheckedChange={onChange}
|
|
63
|
+
disabled={disabled}
|
|
64
|
+
name={name}
|
|
65
|
+
className={rootClasses}
|
|
66
|
+
>
|
|
67
|
+
<Switch.Thumb className={trackClasses}>
|
|
68
|
+
<span className={thumbClasses} aria-hidden="true" />
|
|
69
|
+
</Switch.Thumb>
|
|
70
|
+
|
|
71
|
+
{(label || description) && (
|
|
72
|
+
<div className={styles.content}>
|
|
73
|
+
{label && <span className={labelClasses}>{label}</span>}
|
|
74
|
+
{description && <span className={descClasses}>{description}</span>}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</Switch.Root>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Tooltip, TooltipProvider } from './index.js';
|
|
4
|
+
import { Button } from '../Button/index.js';
|
|
5
|
+
|
|
6
|
+
export default defineSegment({
|
|
7
|
+
component: Tooltip,
|
|
8
|
+
|
|
9
|
+
meta: {
|
|
10
|
+
name: 'Tooltip',
|
|
11
|
+
description: 'Contextual help text that appears on hover or focus. Perfect for explaining icons, truncated text, or providing additional context.',
|
|
12
|
+
category: 'overlays',
|
|
13
|
+
status: 'stable',
|
|
14
|
+
tags: ['tooltip', 'hint', 'help', 'hover', 'contextual'],
|
|
15
|
+
since: '0.1.0',
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
usage: {
|
|
19
|
+
when: [
|
|
20
|
+
'Explaining icon-only buttons',
|
|
21
|
+
'Showing full text for truncated content',
|
|
22
|
+
'Providing keyboard shortcuts',
|
|
23
|
+
'Brief contextual help that fits in one line',
|
|
24
|
+
],
|
|
25
|
+
whenNot: [
|
|
26
|
+
'Long-form help content (use Popover)',
|
|
27
|
+
'Critical information users must see (use Alert)',
|
|
28
|
+
'Interactive content (use Popover or Menu)',
|
|
29
|
+
'Mobile-primary interfaces (tooltips require hover)',
|
|
30
|
+
],
|
|
31
|
+
guidelines: [
|
|
32
|
+
'Keep tooltip text concise (under 80 characters)',
|
|
33
|
+
'Use sentence case, no period for single sentences',
|
|
34
|
+
'Avoid duplicating visible label text',
|
|
35
|
+
'Consider mobile users who cannot hover',
|
|
36
|
+
],
|
|
37
|
+
accessibility: [
|
|
38
|
+
'Accessible via focus as well as hover',
|
|
39
|
+
'Uses role="tooltip" with proper aria association',
|
|
40
|
+
'Delay prevents tooltips from appearing during navigation',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
props: {
|
|
45
|
+
children: {
|
|
46
|
+
type: 'element',
|
|
47
|
+
description: 'The element that triggers the tooltip',
|
|
48
|
+
required: true,
|
|
49
|
+
},
|
|
50
|
+
content: {
|
|
51
|
+
type: 'node',
|
|
52
|
+
description: 'Content to display in the tooltip',
|
|
53
|
+
required: true,
|
|
54
|
+
},
|
|
55
|
+
side: {
|
|
56
|
+
type: 'enum',
|
|
57
|
+
description: 'Which side to show the tooltip',
|
|
58
|
+
values: ['top', 'bottom', 'left', 'right'],
|
|
59
|
+
default: 'top',
|
|
60
|
+
},
|
|
61
|
+
align: {
|
|
62
|
+
type: 'enum',
|
|
63
|
+
description: 'Alignment along the side',
|
|
64
|
+
values: ['start', 'center', 'end'],
|
|
65
|
+
default: 'center',
|
|
66
|
+
},
|
|
67
|
+
sideOffset: {
|
|
68
|
+
type: 'number',
|
|
69
|
+
description: 'Distance from trigger in pixels',
|
|
70
|
+
default: '6',
|
|
71
|
+
},
|
|
72
|
+
delay: {
|
|
73
|
+
type: 'number',
|
|
74
|
+
description: 'Delay before showing (ms)',
|
|
75
|
+
default: '400',
|
|
76
|
+
},
|
|
77
|
+
arrow: {
|
|
78
|
+
type: 'boolean',
|
|
79
|
+
description: 'Show arrow pointing to trigger',
|
|
80
|
+
default: 'true',
|
|
81
|
+
},
|
|
82
|
+
disabled: {
|
|
83
|
+
type: 'boolean',
|
|
84
|
+
description: 'Disable the tooltip',
|
|
85
|
+
default: 'false',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
relations: [
|
|
90
|
+
{ component: 'Popover', relationship: 'alternative', note: 'Use Popover for interactive or longer content' },
|
|
91
|
+
{ component: 'Alert', relationship: 'alternative', note: 'Use Alert for critical information that must be visible' },
|
|
92
|
+
],
|
|
93
|
+
|
|
94
|
+
contract: {
|
|
95
|
+
propsSummary: [
|
|
96
|
+
'content: ReactNode - tooltip content',
|
|
97
|
+
'side: top|bottom|left|right - position',
|
|
98
|
+
'delay: number - show delay in ms (default: 400)',
|
|
99
|
+
'arrow: boolean - show arrow (default: true)',
|
|
100
|
+
],
|
|
101
|
+
scenarioTags: [
|
|
102
|
+
'help.tooltip',
|
|
103
|
+
'content.hint',
|
|
104
|
+
'action.explain',
|
|
105
|
+
],
|
|
106
|
+
a11yRules: ['A11Y_TOOLTIP_FOCUS', 'A11Y_TOOLTIP_ROLE'],
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
variants: [
|
|
110
|
+
{
|
|
111
|
+
name: 'Default',
|
|
112
|
+
description: 'Basic tooltip on hover',
|
|
113
|
+
render: () => (
|
|
114
|
+
<Tooltip content="Save your changes">
|
|
115
|
+
<Button>Save</Button>
|
|
116
|
+
</Tooltip>
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'Positions',
|
|
121
|
+
description: 'Tooltips on different sides',
|
|
122
|
+
render: () => (
|
|
123
|
+
<div style={{ display: 'flex', gap: '16px', padding: '40px' }}>
|
|
124
|
+
<Tooltip content="Top tooltip" side="top">
|
|
125
|
+
<Button variant="secondary">Top</Button>
|
|
126
|
+
</Tooltip>
|
|
127
|
+
<Tooltip content="Bottom tooltip" side="bottom">
|
|
128
|
+
<Button variant="secondary">Bottom</Button>
|
|
129
|
+
</Tooltip>
|
|
130
|
+
<Tooltip content="Left tooltip" side="left">
|
|
131
|
+
<Button variant="secondary">Left</Button>
|
|
132
|
+
</Tooltip>
|
|
133
|
+
<Tooltip content="Right tooltip" side="right">
|
|
134
|
+
<Button variant="secondary">Right</Button>
|
|
135
|
+
</Tooltip>
|
|
136
|
+
</div>
|
|
137
|
+
),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'With Shortcut',
|
|
141
|
+
description: 'Tooltip showing keyboard shortcut',
|
|
142
|
+
render: () => (
|
|
143
|
+
<Tooltip content="Undo (Ctrl+Z)">
|
|
144
|
+
<Button variant="ghost">Undo</Button>
|
|
145
|
+
</Tooltip>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'No Arrow',
|
|
150
|
+
description: 'Tooltip without arrow',
|
|
151
|
+
render: () => (
|
|
152
|
+
<Tooltip content="Clean tooltip" arrow={false}>
|
|
153
|
+
<Button variant="secondary">Hover me</Button>
|
|
154
|
+
</Tooltip>
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
// Positioner (handles placement)
|
|
5
|
+
.positioner {
|
|
6
|
+
z-index: 60;
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// The tooltip popup
|
|
11
|
+
.popup {
|
|
12
|
+
@include text-base;
|
|
13
|
+
|
|
14
|
+
padding: var(--fui-space-1, $fui-space-1) var(--fui-space-2, $fui-space-2);
|
|
15
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
16
|
+
line-height: var(--fui-line-height-tight, $fui-line-height-tight);
|
|
17
|
+
// Tooltips use inverted colors - always dark bg with light text
|
|
18
|
+
color: #f8fafc;
|
|
19
|
+
background-color: #1e293b;
|
|
20
|
+
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
21
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -2px rgba(0, 0, 0, 0.15);
|
|
22
|
+
max-width: 20rem; // 320px
|
|
23
|
+
word-wrap: break-word;
|
|
24
|
+
|
|
25
|
+
// Animation
|
|
26
|
+
opacity: 0;
|
|
27
|
+
transform: scale(0.95);
|
|
28
|
+
transform-origin: var(--transform-origin);
|
|
29
|
+
transition:
|
|
30
|
+
opacity var(--fui-transition-fast, $fui-transition-fast),
|
|
31
|
+
transform var(--fui-transition-fast, $fui-transition-fast);
|
|
32
|
+
|
|
33
|
+
&[data-open] {
|
|
34
|
+
opacity: 1;
|
|
35
|
+
transform: scale(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&[data-starting-style] {
|
|
39
|
+
opacity: 0;
|
|
40
|
+
transform: scale(0.95);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&[data-ending-style] {
|
|
44
|
+
opacity: 0;
|
|
45
|
+
transform: scale(0.95);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Instant transitions (when moving between tooltips)
|
|
49
|
+
&[data-instant] {
|
|
50
|
+
transition-duration: 0ms;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Arrow element
|
|
55
|
+
.arrow {
|
|
56
|
+
width: 8px;
|
|
57
|
+
height: 8px;
|
|
58
|
+
transform: rotate(45deg);
|
|
59
|
+
background-color: #1e293b;
|
|
60
|
+
|
|
61
|
+
// Position based on side
|
|
62
|
+
&[data-side='top'] {
|
|
63
|
+
bottom: -4px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&[data-side='bottom'] {
|
|
67
|
+
top: -4px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&[data-side='left'] {
|
|
71
|
+
right: -4px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&[data-side='right'] {
|
|
75
|
+
left: -4px;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Trigger wrapper (when using asChild)
|
|
80
|
+
.trigger {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
|
|
3
|
+
import styles from './Tooltip.module.scss';
|
|
4
|
+
// Import globals to ensure CSS variables are defined
|
|
5
|
+
import '../../styles/globals.scss';
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// Types
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
export type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
|
|
12
|
+
export type TooltipAlign = 'start' | 'center' | 'end';
|
|
13
|
+
|
|
14
|
+
export interface TooltipProps {
|
|
15
|
+
/** The element that triggers the tooltip */
|
|
16
|
+
children: React.ReactElement;
|
|
17
|
+
/** Content to display in the tooltip */
|
|
18
|
+
content: React.ReactNode;
|
|
19
|
+
/** Which side to show the tooltip */
|
|
20
|
+
side?: TooltipSide;
|
|
21
|
+
/** Alignment along the side */
|
|
22
|
+
align?: TooltipAlign;
|
|
23
|
+
/** Offset from the trigger (px) */
|
|
24
|
+
sideOffset?: number;
|
|
25
|
+
/** Delay before showing (ms) */
|
|
26
|
+
delay?: number;
|
|
27
|
+
/** Delay before hiding (ms) */
|
|
28
|
+
closeDelay?: number;
|
|
29
|
+
/** Whether the tooltip is disabled */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** Show arrow pointing to trigger */
|
|
32
|
+
arrow?: boolean;
|
|
33
|
+
/** Controlled open state */
|
|
34
|
+
open?: boolean;
|
|
35
|
+
/** Default open state */
|
|
36
|
+
defaultOpen?: boolean;
|
|
37
|
+
/** Callback when open state changes */
|
|
38
|
+
onOpenChange?: (open: boolean) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TooltipProviderProps {
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
/** Default delay for all tooltips (ms) */
|
|
44
|
+
delay?: number;
|
|
45
|
+
/** Default close delay for all tooltips (ms) */
|
|
46
|
+
closeDelay?: number;
|
|
47
|
+
/** Timeout for instant open when moving between tooltips (ms) */
|
|
48
|
+
timeout?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================
|
|
52
|
+
// Components
|
|
53
|
+
// ============================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Tooltip - Shows contextual information on hover/focus
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* <Tooltip content="Save your changes">
|
|
61
|
+
* <Button>Save</Button>
|
|
62
|
+
* </Tooltip>
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function Tooltip({
|
|
66
|
+
children,
|
|
67
|
+
content,
|
|
68
|
+
side = 'top',
|
|
69
|
+
align = 'center',
|
|
70
|
+
sideOffset = 6,
|
|
71
|
+
delay = 400,
|
|
72
|
+
closeDelay = 0,
|
|
73
|
+
disabled = false,
|
|
74
|
+
arrow = true,
|
|
75
|
+
open,
|
|
76
|
+
defaultOpen,
|
|
77
|
+
onOpenChange,
|
|
78
|
+
}: TooltipProps) {
|
|
79
|
+
if (disabled) {
|
|
80
|
+
return children;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<BaseTooltip.Provider delay={delay} closeDelay={closeDelay}>
|
|
85
|
+
<BaseTooltip.Root
|
|
86
|
+
open={open}
|
|
87
|
+
defaultOpen={defaultOpen}
|
|
88
|
+
onOpenChange={onOpenChange}
|
|
89
|
+
>
|
|
90
|
+
<BaseTooltip.Trigger render={(props) => React.cloneElement(children, props)} />
|
|
91
|
+
<BaseTooltip.Portal>
|
|
92
|
+
<BaseTooltip.Positioner
|
|
93
|
+
side={side}
|
|
94
|
+
align={align}
|
|
95
|
+
sideOffset={sideOffset}
|
|
96
|
+
className={styles.positioner}
|
|
97
|
+
>
|
|
98
|
+
<BaseTooltip.Popup className={styles.popup}>
|
|
99
|
+
{content}
|
|
100
|
+
{arrow && <BaseTooltip.Arrow className={styles.arrow} />}
|
|
101
|
+
</BaseTooltip.Popup>
|
|
102
|
+
</BaseTooltip.Positioner>
|
|
103
|
+
</BaseTooltip.Portal>
|
|
104
|
+
</BaseTooltip.Root>
|
|
105
|
+
</BaseTooltip.Provider>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* TooltipProvider - Manages shared delay behavior for multiple tooltips
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* <TooltipProvider delay={200}>
|
|
115
|
+
* <Tooltip content="First">...</Tooltip>
|
|
116
|
+
* <Tooltip content="Second">...</Tooltip>
|
|
117
|
+
* </TooltipProvider>
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function TooltipProvider({
|
|
121
|
+
children,
|
|
122
|
+
delay = 400,
|
|
123
|
+
closeDelay = 0,
|
|
124
|
+
timeout = 400,
|
|
125
|
+
}: TooltipProviderProps) {
|
|
126
|
+
return (
|
|
127
|
+
<BaseTooltip.Provider
|
|
128
|
+
delay={delay}
|
|
129
|
+
closeDelay={closeDelay}
|
|
130
|
+
timeout={timeout}
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</BaseTooltip.Provider>
|
|
134
|
+
);
|
|
135
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Import CSS variables and base styles
|
|
2
|
+
// This ensures --fui-* variables are available when using any component
|
|
3
|
+
import './styles/globals.scss';
|
|
4
|
+
|
|
5
|
+
// Core Components
|
|
6
|
+
export { Button, type ButtonProps } from './components/Button';
|
|
7
|
+
export { Input, type InputProps } from './components/Input';
|
|
8
|
+
export { Textarea, type TextareaProps } from './components/Textarea';
|
|
9
|
+
export { Card, type CardProps } from './components/Card';
|
|
10
|
+
export { Toggle, type ToggleProps } from './components/Toggle';
|
|
11
|
+
export { Alert, type AlertProps } from './components/Alert';
|
|
12
|
+
export { Badge, type BadgeProps } from './components/Badge';
|
|
13
|
+
export { Avatar, type AvatarProps, type AvatarGroupProps, type AvatarSize } from './components/Avatar';
|
|
14
|
+
|
|
15
|
+
// Dialog
|
|
16
|
+
export {
|
|
17
|
+
Dialog,
|
|
18
|
+
type DialogProps,
|
|
19
|
+
type DialogContentProps,
|
|
20
|
+
type DialogTitleProps,
|
|
21
|
+
type DialogDescriptionProps,
|
|
22
|
+
type DialogHeaderProps,
|
|
23
|
+
type DialogBodyProps,
|
|
24
|
+
type DialogFooterProps,
|
|
25
|
+
type DialogTriggerProps,
|
|
26
|
+
type DialogCloseProps,
|
|
27
|
+
} from './components/Dialog';
|
|
28
|
+
|
|
29
|
+
// Tabs
|
|
30
|
+
export {
|
|
31
|
+
Tabs,
|
|
32
|
+
type TabsProps,
|
|
33
|
+
type TabsListProps,
|
|
34
|
+
type TabProps,
|
|
35
|
+
type TabsPanelProps,
|
|
36
|
+
type TabValue,
|
|
37
|
+
} from './components/Tabs';
|
|
38
|
+
|
|
39
|
+
// Tooltip
|
|
40
|
+
export {
|
|
41
|
+
Tooltip,
|
|
42
|
+
TooltipProvider,
|
|
43
|
+
type TooltipProps,
|
|
44
|
+
type TooltipProviderProps,
|
|
45
|
+
type TooltipSide,
|
|
46
|
+
type TooltipAlign,
|
|
47
|
+
} from './components/Tooltip';
|
|
48
|
+
|
|
49
|
+
// Select
|
|
50
|
+
export {
|
|
51
|
+
Select,
|
|
52
|
+
type SelectProps,
|
|
53
|
+
type SelectTriggerProps,
|
|
54
|
+
type SelectContentProps,
|
|
55
|
+
type SelectItemProps,
|
|
56
|
+
type SelectGroupProps,
|
|
57
|
+
type SelectGroupLabelProps,
|
|
58
|
+
type SelectValue,
|
|
59
|
+
type SelectOption,
|
|
60
|
+
} from './components/Select';
|
|
61
|
+
|
|
62
|
+
// Menu
|
|
63
|
+
export {
|
|
64
|
+
Menu,
|
|
65
|
+
type MenuProps,
|
|
66
|
+
type MenuTriggerProps,
|
|
67
|
+
type MenuContentProps,
|
|
68
|
+
type MenuItemProps,
|
|
69
|
+
type MenuCheckboxItemProps,
|
|
70
|
+
type MenuRadioGroupProps,
|
|
71
|
+
type MenuRadioItemProps,
|
|
72
|
+
type MenuGroupProps,
|
|
73
|
+
type MenuGroupLabelProps,
|
|
74
|
+
type MenuSeparatorProps,
|
|
75
|
+
} from './components/Menu';
|
|
76
|
+
|
|
77
|
+
// Popover
|
|
78
|
+
export {
|
|
79
|
+
Popover,
|
|
80
|
+
type PopoverProps,
|
|
81
|
+
type PopoverTriggerProps,
|
|
82
|
+
type PopoverContentProps,
|
|
83
|
+
type PopoverTitleProps,
|
|
84
|
+
type PopoverDescriptionProps,
|
|
85
|
+
type PopoverBodyProps,
|
|
86
|
+
type PopoverFooterProps,
|
|
87
|
+
type PopoverCloseProps,
|
|
88
|
+
} from './components/Popover';
|
|
89
|
+
|
|
90
|
+
// Progress
|
|
91
|
+
export {
|
|
92
|
+
Progress,
|
|
93
|
+
CircularProgress,
|
|
94
|
+
type ProgressProps,
|
|
95
|
+
type CircularProgressProps,
|
|
96
|
+
} from './components/Progress';
|
|
97
|
+
|
|
98
|
+
// Checkbox
|
|
99
|
+
export { Checkbox, type CheckboxProps } from './components/Checkbox';
|
|
100
|
+
|
|
101
|
+
// RadioGroup
|
|
102
|
+
export {
|
|
103
|
+
RadioGroup,
|
|
104
|
+
type RadioGroupProps,
|
|
105
|
+
type RadioItemProps,
|
|
106
|
+
} from './components/RadioGroup';
|
|
107
|
+
|
|
108
|
+
// Separator
|
|
109
|
+
export { Separator, type SeparatorProps } from './components/Separator';
|
|
110
|
+
|
|
111
|
+
// Skeleton
|
|
112
|
+
export {
|
|
113
|
+
Skeleton,
|
|
114
|
+
type SkeletonProps,
|
|
115
|
+
type SkeletonTextProps,
|
|
116
|
+
type SkeletonVariant,
|
|
117
|
+
type SkeletonSize,
|
|
118
|
+
} from './components/Skeleton';
|
|
119
|
+
|
|
120
|
+
// Table
|
|
121
|
+
export {
|
|
122
|
+
Table,
|
|
123
|
+
createColumns,
|
|
124
|
+
type TableProps,
|
|
125
|
+
type TableColumn,
|
|
126
|
+
type ColumnDef,
|
|
127
|
+
type SortingState,
|
|
128
|
+
type RowSelectionState,
|
|
129
|
+
} from './components/Table';
|
|
130
|
+
|
|
131
|
+
// EmptyState
|
|
132
|
+
export {
|
|
133
|
+
EmptyState,
|
|
134
|
+
type EmptyStateProps,
|
|
135
|
+
type EmptyStateAction,
|
|
136
|
+
} from './components/EmptyState';
|
|
137
|
+
|
|
138
|
+
// Toast
|
|
139
|
+
export {
|
|
140
|
+
Toast,
|
|
141
|
+
ToastProvider,
|
|
142
|
+
useToast,
|
|
143
|
+
type ToastProps,
|
|
144
|
+
type ToastProviderProps,
|
|
145
|
+
type ToastData,
|
|
146
|
+
type ToastVariant,
|
|
147
|
+
type ToastPosition,
|
|
148
|
+
} from './components/Toast';
|
|
149
|
+
|
|
150
|
+
// Brand
|
|
151
|
+
export { BRAND, type Brand } from './brand';
|
package/src/scss.d.ts
ADDED