@papernote/ui 1.10.14 → 1.10.16
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/dist/components/CollapsibleSection.d.ts +68 -0
- package/dist/components/CollapsibleSection.d.ts.map +1 -0
- package/dist/components/HelpTooltip.d.ts +15 -0
- package/dist/components/HelpTooltip.d.ts.map +1 -0
- package/dist/components/PriorityAlertBanner.d.ts +58 -0
- package/dist/components/PriorityAlertBanner.d.ts.map +1 -0
- package/dist/components/SummaryCard.d.ts +88 -0
- package/dist/components/SummaryCard.d.ts.map +1 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +225 -2
- package/dist/index.esm.js +386 -70
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +387 -67
- package/dist/index.js.map +1 -1
- package/dist/styles.css +24 -0
- package/package.json +1 -1
- package/src/components/CollapsibleSection.tsx +219 -0
- package/src/components/HelpTooltip.stories.tsx +200 -0
- package/src/components/HelpTooltip.tsx +45 -0
- package/src/components/PriorityAlertBanner.tsx +219 -0
- package/src/components/SummaryCard.tsx +255 -0
- package/src/components/index.ts +12 -0
package/dist/styles.css
CHANGED
|
@@ -2734,6 +2734,10 @@ input:checked + .slider:before{
|
|
|
2734
2734
|
cursor: grabbing;
|
|
2735
2735
|
}
|
|
2736
2736
|
|
|
2737
|
+
.cursor-help{
|
|
2738
|
+
cursor: help;
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2737
2741
|
.cursor-move{
|
|
2738
2742
|
cursor: move;
|
|
2739
2743
|
}
|
|
@@ -5501,6 +5505,11 @@ input:checked + .slider:before{
|
|
|
5501
5505
|
background-color: rgb(239 246 255 / var(--tw-bg-opacity, 1));
|
|
5502
5506
|
}
|
|
5503
5507
|
|
|
5508
|
+
.hover\:bg-error-100:hover{
|
|
5509
|
+
--tw-bg-opacity: 1;
|
|
5510
|
+
background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
|
|
5511
|
+
}
|
|
5512
|
+
|
|
5504
5513
|
.hover\:bg-error-200:hover{
|
|
5505
5514
|
--tw-bg-opacity: 1;
|
|
5506
5515
|
background-color: rgb(254 202 202 / var(--tw-bg-opacity, 1));
|
|
@@ -5576,6 +5585,11 @@ input:checked + .slider:before{
|
|
|
5576
5585
|
background-color: rgb(250 250 249 / var(--tw-bg-opacity, 1));
|
|
5577
5586
|
}
|
|
5578
5587
|
|
|
5588
|
+
.hover\:bg-primary-100:hover{
|
|
5589
|
+
--tw-bg-opacity: 1;
|
|
5590
|
+
background-color: rgb(241 245 249 / var(--tw-bg-opacity, 1));
|
|
5591
|
+
}
|
|
5592
|
+
|
|
5579
5593
|
.hover\:bg-primary-200:hover{
|
|
5580
5594
|
--tw-bg-opacity: 1;
|
|
5581
5595
|
background-color: rgb(226 232 240 / var(--tw-bg-opacity, 1));
|
|
@@ -5636,6 +5650,11 @@ input:checked + .slider:before{
|
|
|
5636
5650
|
background-color: rgb(4 120 87 / var(--tw-bg-opacity, 1));
|
|
5637
5651
|
}
|
|
5638
5652
|
|
|
5653
|
+
.hover\:bg-warning-100:hover{
|
|
5654
|
+
--tw-bg-opacity: 1;
|
|
5655
|
+
background-color: rgb(254 243 199 / var(--tw-bg-opacity, 1));
|
|
5656
|
+
}
|
|
5657
|
+
|
|
5639
5658
|
.hover\:bg-warning-200:hover{
|
|
5640
5659
|
--tw-bg-opacity: 1;
|
|
5641
5660
|
background-color: rgb(253 230 138 / var(--tw-bg-opacity, 1));
|
|
@@ -5798,6 +5817,11 @@ input:checked + .slider:before{
|
|
|
5798
5817
|
color: rgb(127 29 29 / var(--tw-text-opacity, 1));
|
|
5799
5818
|
}
|
|
5800
5819
|
|
|
5820
|
+
.hover\:text-warning-700:hover{
|
|
5821
|
+
--tw-text-opacity: 1;
|
|
5822
|
+
color: rgb(180 83 9 / var(--tw-text-opacity, 1));
|
|
5823
|
+
}
|
|
5824
|
+
|
|
5801
5825
|
.hover\:underline:hover{
|
|
5802
5826
|
text-decoration-line: underline;
|
|
5803
5827
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect, ReactNode } from 'react';
|
|
2
|
+
import { ChevronDown, ExternalLink } from 'lucide-react';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
import Badge from './Badge';
|
|
5
|
+
|
|
6
|
+
export interface CollapsibleSectionProps {
|
|
7
|
+
/** Section title displayed in the header */
|
|
8
|
+
title: string;
|
|
9
|
+
/** Optional icon displayed before the title */
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
/** Badge content shown next to title (typically a count) */
|
|
12
|
+
badge?: number | string;
|
|
13
|
+
/** Badge color variant */
|
|
14
|
+
badgeVariant?: 'success' | 'warning' | 'error' | 'info' | 'neutral';
|
|
15
|
+
/** URL for "View All" link (uses react-router Link) */
|
|
16
|
+
viewAllHref?: string;
|
|
17
|
+
/** Custom label for the view all link */
|
|
18
|
+
viewAllLabel?: string;
|
|
19
|
+
/** Callback when "View All" is clicked (alternative to href) */
|
|
20
|
+
onViewAll?: () => void;
|
|
21
|
+
/** Initial open state (uncontrolled) */
|
|
22
|
+
defaultOpen?: boolean;
|
|
23
|
+
/** Controlled open state */
|
|
24
|
+
open?: boolean;
|
|
25
|
+
/** Callback when open state changes (for persistence) */
|
|
26
|
+
onOpenChange?: (open: boolean) => void;
|
|
27
|
+
/** Section content */
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
/** Additional CSS classes for the container */
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* CollapsibleSection - A card-style collapsible container for dashboard sections
|
|
35
|
+
*
|
|
36
|
+
* Wraps content with a styled header that includes title, optional icon, badge count,
|
|
37
|
+
* and "View All" navigation. Supports both controlled and uncontrolled modes for
|
|
38
|
+
* localStorage persistence via onOpenChange callback.
|
|
39
|
+
*
|
|
40
|
+
* @example Basic usage
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <CollapsibleSection
|
|
43
|
+
* title="Upcoming Bills"
|
|
44
|
+
* badge={5}
|
|
45
|
+
* badgeVariant="warning"
|
|
46
|
+
* viewAllHref="/bills"
|
|
47
|
+
* >
|
|
48
|
+
* <BillsList bills={upcomingBills} />
|
|
49
|
+
* </CollapsibleSection>
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example With localStorage persistence
|
|
53
|
+
* ```tsx
|
|
54
|
+
* const [isOpen, setIsOpen] = useState(() =>
|
|
55
|
+
* localStorage.getItem('section-open') !== 'false'
|
|
56
|
+
* );
|
|
57
|
+
*
|
|
58
|
+
* <CollapsibleSection
|
|
59
|
+
* title="Pending Items"
|
|
60
|
+
* badge={pendingCount}
|
|
61
|
+
* open={isOpen}
|
|
62
|
+
* onOpenChange={(open) => {
|
|
63
|
+
* setIsOpen(open);
|
|
64
|
+
* localStorage.setItem('section-open', String(open));
|
|
65
|
+
* }}
|
|
66
|
+
* >
|
|
67
|
+
* {children}
|
|
68
|
+
* </CollapsibleSection>
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function CollapsibleSection({
|
|
72
|
+
title,
|
|
73
|
+
icon,
|
|
74
|
+
badge,
|
|
75
|
+
badgeVariant = 'neutral',
|
|
76
|
+
viewAllHref,
|
|
77
|
+
viewAllLabel = 'View All',
|
|
78
|
+
onViewAll,
|
|
79
|
+
defaultOpen = true,
|
|
80
|
+
open: controlledOpen,
|
|
81
|
+
onOpenChange,
|
|
82
|
+
children,
|
|
83
|
+
className = '',
|
|
84
|
+
}: CollapsibleSectionProps) {
|
|
85
|
+
const isControlled = controlledOpen !== undefined;
|
|
86
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
87
|
+
const isOpen = isControlled ? controlledOpen : internalOpen;
|
|
88
|
+
|
|
89
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
90
|
+
const [height, setHeight] = useState<number>(0);
|
|
91
|
+
|
|
92
|
+
const handleToggle = () => {
|
|
93
|
+
const newOpen = !isOpen;
|
|
94
|
+
|
|
95
|
+
if (!isControlled) {
|
|
96
|
+
setInternalOpen(newOpen);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onOpenChange?.(newOpen);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleViewAllClick = (e: React.MouseEvent) => {
|
|
103
|
+
if (onViewAll) {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
onViewAll();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Update height when content changes or open state changes
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!contentRef.current) return;
|
|
112
|
+
|
|
113
|
+
if (isOpen) {
|
|
114
|
+
const contentHeight = contentRef.current.scrollHeight;
|
|
115
|
+
setHeight(contentHeight);
|
|
116
|
+
} else {
|
|
117
|
+
setHeight(0);
|
|
118
|
+
}
|
|
119
|
+
}, [isOpen, children]);
|
|
120
|
+
|
|
121
|
+
// Recalculate height when window resizes
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!isOpen) return;
|
|
124
|
+
|
|
125
|
+
const handleResize = () => {
|
|
126
|
+
if (contentRef.current) {
|
|
127
|
+
setHeight(contentRef.current.scrollHeight);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
window.addEventListener('resize', handleResize);
|
|
132
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
133
|
+
}, [isOpen]);
|
|
134
|
+
|
|
135
|
+
const ViewAllContent = (
|
|
136
|
+
<>
|
|
137
|
+
<span>{viewAllLabel}</span>
|
|
138
|
+
<ExternalLink className="h-3.5 w-3.5" />
|
|
139
|
+
</>
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div
|
|
144
|
+
className={`
|
|
145
|
+
bg-white bg-subtle-grain border-2 border-paper-300 rounded-xl shadow-sm
|
|
146
|
+
${className}
|
|
147
|
+
`}
|
|
148
|
+
>
|
|
149
|
+
{/* Header */}
|
|
150
|
+
<div className="flex items-center justify-between px-5 py-4 border-b border-paper-200">
|
|
151
|
+
{/* Left side: Toggle + Icon + Title + Badge */}
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={handleToggle}
|
|
155
|
+
className="flex items-center gap-3 text-left flex-1 min-w-0 group"
|
|
156
|
+
aria-expanded={isOpen}
|
|
157
|
+
>
|
|
158
|
+
<ChevronDown
|
|
159
|
+
className={`
|
|
160
|
+
h-5 w-5 text-ink-400 transition-transform duration-200 flex-shrink-0
|
|
161
|
+
group-hover:text-ink-600
|
|
162
|
+
${isOpen ? 'rotate-0' : '-rotate-90'}
|
|
163
|
+
`}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
{icon && (
|
|
167
|
+
<span className="flex-shrink-0 text-ink-500">
|
|
168
|
+
{icon}
|
|
169
|
+
</span>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
<span className="font-medium text-ink-900 truncate">
|
|
173
|
+
{title}
|
|
174
|
+
</span>
|
|
175
|
+
|
|
176
|
+
{badge !== undefined && (
|
|
177
|
+
<Badge variant={badgeVariant} size="sm" pill>
|
|
178
|
+
{badge}
|
|
179
|
+
</Badge>
|
|
180
|
+
)}
|
|
181
|
+
</button>
|
|
182
|
+
|
|
183
|
+
{/* Right side: View All link */}
|
|
184
|
+
{(viewAllHref || onViewAll) && (
|
|
185
|
+
viewAllHref ? (
|
|
186
|
+
<Link
|
|
187
|
+
to={viewAllHref}
|
|
188
|
+
onClick={onViewAll ? handleViewAllClick : undefined}
|
|
189
|
+
className="flex items-center gap-1.5 text-sm text-primary-600 hover:text-primary-700 font-medium flex-shrink-0 ml-4"
|
|
190
|
+
>
|
|
191
|
+
{ViewAllContent}
|
|
192
|
+
</Link>
|
|
193
|
+
) : (
|
|
194
|
+
<button
|
|
195
|
+
onClick={onViewAll}
|
|
196
|
+
className="flex items-center gap-1.5 text-sm text-primary-600 hover:text-primary-700 font-medium flex-shrink-0 ml-4"
|
|
197
|
+
>
|
|
198
|
+
{ViewAllContent}
|
|
199
|
+
</button>
|
|
200
|
+
)
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
{/* Content */}
|
|
205
|
+
<div
|
|
206
|
+
ref={contentRef}
|
|
207
|
+
className="overflow-hidden transition-all duration-300 ease-in-out"
|
|
208
|
+
style={{ height: `${height}px` }}
|
|
209
|
+
aria-hidden={!isOpen}
|
|
210
|
+
>
|
|
211
|
+
<div className="p-5">
|
|
212
|
+
{children}
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export default CollapsibleSection;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import HelpTooltip from './HelpTooltip';
|
|
3
|
+
import Input from './Input';
|
|
4
|
+
import Stack from './Stack';
|
|
5
|
+
import Text from './Text';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Feedback/HelpTooltip',
|
|
9
|
+
component: HelpTooltip,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component: `
|
|
15
|
+
A convenience component that combines a help icon with a tooltip for providing contextual help.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
- **Two icon variants**: HelpCircle (?) or Info (i) icon
|
|
19
|
+
- **Three sizes**: sm, md, lg
|
|
20
|
+
- **Accessible**: Includes proper ARIA labels and keyboard focus
|
|
21
|
+
- **Hover states**: Subtle color transition on hover
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
\`\`\`tsx
|
|
26
|
+
import { HelpTooltip, Text, Stack } from 'notebook-ui';
|
|
27
|
+
|
|
28
|
+
// Basic usage
|
|
29
|
+
<Stack direction="horizontal" gap="sm" align="center">
|
|
30
|
+
<Text weight="medium">Email</Text>
|
|
31
|
+
<HelpTooltip content="We'll never share your email" />
|
|
32
|
+
</Stack>
|
|
33
|
+
|
|
34
|
+
// With info icon variant
|
|
35
|
+
<HelpTooltip content="Additional information" icon="info" />
|
|
36
|
+
|
|
37
|
+
// Different sizes
|
|
38
|
+
<HelpTooltip content="Small help" size="sm" />
|
|
39
|
+
<HelpTooltip content="Large help" size="lg" />
|
|
40
|
+
\`\`\`
|
|
41
|
+
`,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
tags: ['autodocs'],
|
|
46
|
+
argTypes: {
|
|
47
|
+
content: {
|
|
48
|
+
control: 'text',
|
|
49
|
+
description: 'The help text to display in the tooltip',
|
|
50
|
+
table: {
|
|
51
|
+
type: { summary: 'React.ReactNode' },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
icon: {
|
|
55
|
+
control: 'select',
|
|
56
|
+
options: ['help', 'info'],
|
|
57
|
+
description: 'Icon variant to display',
|
|
58
|
+
table: {
|
|
59
|
+
type: { summary: 'help | info' },
|
|
60
|
+
defaultValue: { summary: 'help' },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
size: {
|
|
64
|
+
control: 'select',
|
|
65
|
+
options: ['sm', 'md', 'lg'],
|
|
66
|
+
description: 'Size of the icon',
|
|
67
|
+
table: {
|
|
68
|
+
type: { summary: 'sm | md | lg' },
|
|
69
|
+
defaultValue: { summary: 'md' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
position: {
|
|
73
|
+
control: 'select',
|
|
74
|
+
options: ['top', 'bottom', 'left', 'right'],
|
|
75
|
+
description: 'Position of the tooltip relative to the icon',
|
|
76
|
+
table: {
|
|
77
|
+
type: { summary: 'top | bottom | left | right' },
|
|
78
|
+
defaultValue: { summary: 'top' },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
} satisfies Meta<typeof HelpTooltip>;
|
|
83
|
+
|
|
84
|
+
export default meta;
|
|
85
|
+
type Story = StoryObj<typeof meta>;
|
|
86
|
+
|
|
87
|
+
export const Default: Story = {
|
|
88
|
+
args: {
|
|
89
|
+
content: 'This is helpful information',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const HelpIcon: Story = {
|
|
94
|
+
args: {
|
|
95
|
+
content: 'Need help? This explains the feature.',
|
|
96
|
+
icon: 'help',
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const InfoIcon: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
content: 'Additional information about this field.',
|
|
103
|
+
icon: 'info',
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const Sizes: Story = {
|
|
108
|
+
render: () => (
|
|
109
|
+
<Stack direction="horizontal" gap="lg" align="center">
|
|
110
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
111
|
+
<Text size="sm">Small</Text>
|
|
112
|
+
<HelpTooltip content="Small size tooltip" size="sm" />
|
|
113
|
+
</Stack>
|
|
114
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
115
|
+
<Text size="sm">Medium</Text>
|
|
116
|
+
<HelpTooltip content="Medium size tooltip" size="md" />
|
|
117
|
+
</Stack>
|
|
118
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
119
|
+
<Text size="sm">Large</Text>
|
|
120
|
+
<HelpTooltip content="Large size tooltip" size="lg" />
|
|
121
|
+
</Stack>
|
|
122
|
+
</Stack>
|
|
123
|
+
),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const Positions: Story = {
|
|
127
|
+
render: () => (
|
|
128
|
+
<Stack direction="horizontal" gap="xl" align="center">
|
|
129
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
130
|
+
<Text size="sm">Top</Text>
|
|
131
|
+
<HelpTooltip content="Tooltip on top" position="top" />
|
|
132
|
+
</Stack>
|
|
133
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
134
|
+
<Text size="sm">Bottom</Text>
|
|
135
|
+
<HelpTooltip content="Tooltip on bottom" position="bottom" />
|
|
136
|
+
</Stack>
|
|
137
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
138
|
+
<Text size="sm">Left</Text>
|
|
139
|
+
<HelpTooltip content="Tooltip on left" position="left" />
|
|
140
|
+
</Stack>
|
|
141
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
142
|
+
<Text size="sm">Right</Text>
|
|
143
|
+
<HelpTooltip content="Tooltip on right" position="right" />
|
|
144
|
+
</Stack>
|
|
145
|
+
</Stack>
|
|
146
|
+
),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const WithFormField: Story = {
|
|
150
|
+
render: () => (
|
|
151
|
+
<Stack gap="md" style={{ width: '300px' }}>
|
|
152
|
+
<Stack gap="xs">
|
|
153
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
154
|
+
<Text as="label" size="sm" weight="medium">
|
|
155
|
+
Email Address
|
|
156
|
+
</Text>
|
|
157
|
+
<HelpTooltip
|
|
158
|
+
content="We'll never share your email with anyone else"
|
|
159
|
+
size="sm"
|
|
160
|
+
/>
|
|
161
|
+
</Stack>
|
|
162
|
+
<Input placeholder="you@example.com" />
|
|
163
|
+
</Stack>
|
|
164
|
+
<Stack gap="xs">
|
|
165
|
+
<Stack direction="horizontal" gap="xs" align="center">
|
|
166
|
+
<Text as="label" size="sm" weight="medium">
|
|
167
|
+
API Key
|
|
168
|
+
</Text>
|
|
169
|
+
<HelpTooltip
|
|
170
|
+
content="Your API key is used for authentication. Keep it secret!"
|
|
171
|
+
icon="info"
|
|
172
|
+
size="sm"
|
|
173
|
+
/>
|
|
174
|
+
</Stack>
|
|
175
|
+
<Input type="password" placeholder="sk-..." />
|
|
176
|
+
</Stack>
|
|
177
|
+
</Stack>
|
|
178
|
+
),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const InlineWithText: Story = {
|
|
182
|
+
render: () => (
|
|
183
|
+
<Text>
|
|
184
|
+
This feature requires a subscription
|
|
185
|
+
<HelpTooltip
|
|
186
|
+
content="Premium subscriptions include unlimited access to all features"
|
|
187
|
+
size="sm"
|
|
188
|
+
className="ml-1"
|
|
189
|
+
/>
|
|
190
|
+
</Text>
|
|
191
|
+
),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const LongContent: Story = {
|
|
195
|
+
args: {
|
|
196
|
+
content:
|
|
197
|
+
'This is a longer help message that provides more detailed information about the feature. It can span multiple lines if needed.',
|
|
198
|
+
position: 'right',
|
|
199
|
+
},
|
|
200
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { HelpCircle, Info } from 'lucide-react';
|
|
3
|
+
import Tooltip from './Tooltip';
|
|
4
|
+
|
|
5
|
+
export interface HelpTooltipProps {
|
|
6
|
+
/** The help text to display in the tooltip */
|
|
7
|
+
content: React.ReactNode;
|
|
8
|
+
/** Icon variant to display */
|
|
9
|
+
icon?: 'help' | 'info';
|
|
10
|
+
/** Size of the icon */
|
|
11
|
+
size?: 'sm' | 'md' | 'lg';
|
|
12
|
+
/** Position of the tooltip relative to the icon */
|
|
13
|
+
position?: 'top' | 'bottom' | 'left' | 'right';
|
|
14
|
+
/** Additional CSS classes for the icon container */
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sizeClasses = {
|
|
19
|
+
sm: 'h-3.5 w-3.5',
|
|
20
|
+
md: 'h-4 w-4',
|
|
21
|
+
lg: 'h-5 w-5',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default function HelpTooltip({
|
|
25
|
+
content,
|
|
26
|
+
icon = 'help',
|
|
27
|
+
size = 'md',
|
|
28
|
+
position = 'top',
|
|
29
|
+
className = '',
|
|
30
|
+
}: HelpTooltipProps) {
|
|
31
|
+
const IconComponent = icon === 'info' ? Info : HelpCircle;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Tooltip content={content} position={position}>
|
|
35
|
+
<span
|
|
36
|
+
className={`inline-flex items-center justify-center text-ink-400 hover:text-ink-600 cursor-help transition-colors ${className}`}
|
|
37
|
+
role="button"
|
|
38
|
+
aria-label="Help"
|
|
39
|
+
tabIndex={0}
|
|
40
|
+
>
|
|
41
|
+
<IconComponent className={sizeClasses[size]} />
|
|
42
|
+
</span>
|
|
43
|
+
</Tooltip>
|
|
44
|
+
);
|
|
45
|
+
}
|