@fpkit/acss 6.1.0 → 6.3.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/libs/chunk-25KCUE3R.cjs +17 -0
- package/libs/chunk-25KCUE3R.cjs.map +1 -0
- package/libs/chunk-34NWHFHP.js +10 -0
- package/libs/chunk-34NWHFHP.js.map +1 -0
- package/libs/{chunk-SQ44OCJ2.js → chunk-6NMLU5FA.js} +2 -2
- package/libs/{chunk-GVVCXXKI.cjs → chunk-6YVR4TDM.cjs} +3 -3
- package/libs/chunk-DSQ2TUCR.js +7 -0
- package/libs/chunk-DSQ2TUCR.js.map +1 -0
- package/libs/{chunk-H6A2CUWA.js → chunk-VQTCTLFN.js} +2 -2
- package/libs/chunk-ZJ4RUKI2.cjs +14 -0
- package/libs/chunk-ZJ4RUKI2.cjs.map +1 -0
- package/libs/{chunk-H4JRUNKU.cjs → chunk-ZOPHCNFD.cjs} +3 -3
- package/libs/components/button.cjs +3 -3
- package/libs/components/button.d.cts +34 -1
- package/libs/components/button.d.ts +34 -1
- package/libs/components/button.js +1 -1
- package/libs/components/buttons/button.css +1 -1
- package/libs/components/buttons/button.css.map +1 -1
- package/libs/components/buttons/button.min.css +2 -2
- package/libs/components/buttons/icon-button.css +1 -0
- package/libs/components/buttons/icon-button.css.map +1 -0
- package/libs/components/buttons/icon-button.min.css +3 -0
- package/libs/components/dialog/dialog.cjs +4 -4
- package/libs/components/dialog/dialog.js +2 -2
- package/libs/components/icons/icon.d.cts +1 -1
- package/libs/components/icons/icon.d.ts +1 -1
- package/libs/components/layout/landmarks.css +1 -1
- package/libs/components/layout/landmarks.css.map +1 -1
- package/libs/components/layout/landmarks.min.css +2 -2
- package/libs/components/link/link.css +1 -1
- package/libs/components/link/link.min.css +1 -1
- package/libs/components/modal.cjs +3 -3
- package/libs/components/modal.js +2 -2
- package/libs/components/popover/popover.cjs +3 -8
- package/libs/components/popover/popover.css +1 -0
- package/libs/components/popover/popover.css.map +1 -0
- package/libs/components/popover/popover.d.cts +54 -26
- package/libs/components/popover/popover.d.ts +54 -26
- package/libs/components/popover/popover.js +1 -2
- package/libs/components/popover/popover.min.css +3 -0
- package/libs/hooks.cjs +3 -6
- package/libs/hooks.cjs.map +1 -1
- package/libs/hooks.d.cts +30 -10
- package/libs/hooks.d.ts +30 -10
- package/libs/hooks.js +5 -1
- package/libs/hooks.js.map +1 -1
- package/libs/{icons-48788561.d.ts → icons-2c09535c.d.ts} +32 -32
- package/libs/icons.d.cts +1 -1
- package/libs/icons.d.ts +1 -1
- package/libs/index.cjs +41 -40
- package/libs/index.cjs.map +1 -1
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/libs/index.d.cts +101 -5
- package/libs/index.d.ts +101 -5
- package/libs/index.js +14 -15
- package/libs/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/buttons/README.mdx +107 -11
- package/src/components/buttons/STYLES.mdx +182 -47
- package/src/components/buttons/button.scss +93 -16
- package/src/components/buttons/button.stories.tsx +149 -0
- package/src/components/buttons/button.test.tsx +12 -0
- package/src/components/buttons/button.tsx +50 -6
- package/src/components/buttons/icon-button.scss +45 -0
- package/src/components/buttons/icon-button.stories.tsx +200 -0
- package/src/components/buttons/icon-button.test.tsx +132 -0
- package/src/components/buttons/icon-button.tsx +72 -0
- package/src/components/form/select.tsx +55 -51
- package/src/components/layout/README.mdx +1117 -0
- package/src/components/layout/STYLES.mdx +159 -4
- package/src/components/layout/fieldset.stories.tsx +387 -0
- package/src/components/layout/landmarks.scss +115 -2
- package/src/components/layout/landmarks.stories.tsx +2 -6
- package/src/components/layout/landmarks.tsx +96 -27
- package/src/components/link/link.scss +2 -2
- package/src/components/popover/README.mdx +478 -0
- package/src/components/popover/STYLES.mdx +389 -0
- package/src/components/popover/index.ts +3 -0
- package/src/components/popover/popover.scss +249 -0
- package/src/components/popover/popover.stories.tsx +315 -15
- package/src/components/popover/popover.test.tsx +249 -37
- package/src/components/popover/popover.tsx +165 -62
- package/src/hooks/popover/popover.tsx +26 -10
- package/src/hooks/popover/use-popover.tsx +30 -10
- package/src/hooks.ts +5 -0
- package/src/index.scss +1 -0
- package/src/index.ts +1 -0
- package/src/styles/buttons/button.css +78 -16
- package/src/styles/buttons/button.css.map +1 -1
- package/src/styles/buttons/icon-button.css +32 -0
- package/src/styles/buttons/icon-button.css.map +1 -0
- package/src/styles/index.css +350 -18
- package/src/styles/index.css.map +1 -1
- package/src/styles/layout/landmarks.css +83 -0
- package/src/styles/layout/landmarks.css.map +1 -1
- package/src/styles/link/link.css +2 -2
- package/src/styles/popover/popover.css +190 -0
- package/src/styles/popover/popover.css.map +1 -0
- package/src/types/popover.d.ts +64 -0
- package/libs/chunk-4I5MF54P.js +0 -8
- package/libs/chunk-4I5MF54P.js.map +0 -1
- package/libs/chunk-GCGKYLDG.js +0 -7
- package/libs/chunk-GCGKYLDG.js.map +0 -1
- package/libs/chunk-NZVSXRTB.cjs +0 -16
- package/libs/chunk-NZVSXRTB.cjs.map +0 -1
- package/libs/chunk-PDD4N5P5.cjs +0 -10
- package/libs/chunk-PDD4N5P5.cjs.map +0 -1
- package/libs/chunk-S7NIA6PI.cjs +0 -17
- package/libs/chunk-S7NIA6PI.cjs.map +0 -1
- package/libs/chunk-X2RDXWH5.js +0 -10
- package/libs/chunk-X2RDXWH5.js.map +0 -1
- /package/libs/{chunk-SQ44OCJ2.js.map → chunk-6NMLU5FA.js.map} +0 -0
- /package/libs/{chunk-GVVCXXKI.cjs.map → chunk-6YVR4TDM.cjs.map} +0 -0
- /package/libs/{chunk-H6A2CUWA.js.map → chunk-VQTCTLFN.js.map} +0 -0
- /package/libs/{chunk-H4JRUNKU.cjs.map → chunk-ZOPHCNFD.cjs.map} +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import UI from
|
|
2
|
-
import React, { ReactNode } from
|
|
1
|
+
import UI from "../ui";
|
|
2
|
+
import React, { ReactNode } from "react";
|
|
3
3
|
|
|
4
|
-
type ComponentProps = React.ComponentProps<typeof UI
|
|
4
|
+
type ComponentProps = React.ComponentProps<typeof UI>;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Renders children elements without any wrapping component.
|
|
8
8
|
* Can be used as a placeholder when no semantic landmark is needed.
|
|
9
9
|
*/
|
|
10
|
-
export const Landmarks = (children?: React.FC) => <>{children}
|
|
10
|
+
export const Landmarks = (children?: React.FC) => <>{children}</>;
|
|
11
11
|
|
|
12
12
|
type HeaderProps = {
|
|
13
|
-
headerBackground?: ReactNode
|
|
14
|
-
} & ComponentProps
|
|
13
|
+
headerBackground?: ReactNode;
|
|
14
|
+
} & ComponentProps;
|
|
15
15
|
/**
|
|
16
16
|
* Header component.
|
|
17
17
|
*
|
|
@@ -34,8 +34,8 @@ export const Header = ({
|
|
|
34
34
|
{headerBackground}
|
|
35
35
|
<UI as="section">{children}</UI>
|
|
36
36
|
</UI>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Main component.
|
|
@@ -57,8 +57,8 @@ export const Main = ({
|
|
|
57
57
|
<UI as="main" id={id} styles={styles} {...props} className={classes}>
|
|
58
58
|
{children}
|
|
59
59
|
</UI>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Footer component that renders a footer element with a section element inside.
|
|
@@ -76,10 +76,10 @@ export const Footer = ({
|
|
|
76
76
|
}: ComponentProps) => {
|
|
77
77
|
return (
|
|
78
78
|
<UI as="footer" id={id} className={classes} styles={styles} {...props}>
|
|
79
|
-
<UI as="section">{children ||
|
|
79
|
+
<UI as="section">{children || "Copyright © 2022"}</UI>
|
|
80
80
|
</UI>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
83
|
|
|
84
84
|
export const Aside = ({
|
|
85
85
|
id,
|
|
@@ -92,8 +92,8 @@ export const Aside = ({
|
|
|
92
92
|
<UI as="aside" id={id} styles={styles} className={classes} {...props}>
|
|
93
93
|
<UI as="section">{children}</UI>
|
|
94
94
|
</UI>
|
|
95
|
-
)
|
|
96
|
-
}
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* Section component that renders a section element.
|
|
@@ -113,8 +113,8 @@ export const Section = ({
|
|
|
113
113
|
<UI as="section" id={id} styles={styles} className={classes} {...props}>
|
|
114
114
|
{children}
|
|
115
115
|
</UI>
|
|
116
|
-
)
|
|
117
|
-
}
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
120
|
* Article component renders an HTML <article> element.
|
|
@@ -135,15 +135,84 @@ export const Article = ({
|
|
|
135
135
|
<UI as="article" id={id} styles={styles} className={classes} {...props}>
|
|
136
136
|
{children}
|
|
137
137
|
</UI>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
type FieldsetProps = {
|
|
142
|
+
/**
|
|
143
|
+
* Optional legend content displayed as the fieldset's accessible name
|
|
144
|
+
*/
|
|
145
|
+
legend?: ReactNode;
|
|
146
|
+
/**
|
|
147
|
+
* Additional description text for complex fieldsets
|
|
148
|
+
* Enhances accessibility via aria-describedby
|
|
149
|
+
*/
|
|
150
|
+
description?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Custom ID for the description element
|
|
153
|
+
* Auto-generated if description is provided without custom ID
|
|
154
|
+
*/
|
|
155
|
+
descriptionId?: string;
|
|
156
|
+
} & ComponentProps;
|
|
142
157
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Fieldset landmark for semantic content grouping.
|
|
160
|
+
* Provides WCAG 2.1 Level AA compliant form grouping with optional descriptions.
|
|
161
|
+
*
|
|
162
|
+
* @param legend - Optional legend content (accessible name)
|
|
163
|
+
* @param description - Optional description for additional context
|
|
164
|
+
* @param descriptionId - Custom ID for description element
|
|
165
|
+
* @param children - Content inside fieldset section
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```tsx
|
|
169
|
+
* <Fieldset
|
|
170
|
+
* legend="Shipping Address"
|
|
171
|
+
* description="This address will be used for delivery"
|
|
172
|
+
* >
|
|
173
|
+
* {/* form controls *\/}
|
|
174
|
+
* </Fieldset>
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export const Fieldset = ({
|
|
178
|
+
id,
|
|
179
|
+
children,
|
|
180
|
+
legend,
|
|
181
|
+
description,
|
|
182
|
+
descriptionId,
|
|
183
|
+
styles,
|
|
184
|
+
classes,
|
|
185
|
+
...props
|
|
186
|
+
}: FieldsetProps) => {
|
|
187
|
+
const descId = descriptionId || (description ? `${id}-desc` : undefined);
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<UI
|
|
191
|
+
as="fieldset"
|
|
192
|
+
id={id}
|
|
193
|
+
styles={styles}
|
|
194
|
+
className={classes}
|
|
195
|
+
aria-describedby={descId}
|
|
196
|
+
{...props}
|
|
197
|
+
>
|
|
198
|
+
{legend && <UI as="legend">{legend}</UI>}
|
|
199
|
+
{description && (
|
|
200
|
+
<p id={descId} className="fieldset-description">
|
|
201
|
+
{description}
|
|
202
|
+
</p>
|
|
203
|
+
)}
|
|
204
|
+
{children}
|
|
205
|
+
</UI>
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export default Landmarks;
|
|
210
|
+
|
|
211
|
+
Landmarks.displayName = "Landmarks";
|
|
212
|
+
Landmarks.Header = Header;
|
|
213
|
+
Landmarks.Main = Main;
|
|
214
|
+
Landmarks.Footer = Footer;
|
|
215
|
+
Landmarks.Aside = Aside;
|
|
216
|
+
Landmarks.Section = Section;
|
|
217
|
+
Landmarks.Article = Article;
|
|
218
|
+
Landmarks.Fieldset = Fieldset;
|
|
@@ -98,7 +98,7 @@ a[href] {
|
|
|
98
98
|
font-size: var(--link-fs);
|
|
99
99
|
padding-inline: var(--link-fs);
|
|
100
100
|
padding-block: calc(var(--link-fs) - 0.4rem);
|
|
101
|
-
border-radius: var(--link-radius,
|
|
101
|
+
border-radius: var(--link-radius, 100vw);
|
|
102
102
|
display: inline-flex;
|
|
103
103
|
align-items: center;
|
|
104
104
|
justify-content: center;
|
|
@@ -126,7 +126,7 @@ a[href] {
|
|
|
126
126
|
// Pill variant (rounded corners)
|
|
127
127
|
&[data-link~="pill"],
|
|
128
128
|
&:has(> i) {
|
|
129
|
-
--link-radius:
|
|
129
|
+
--link-radius: 100vw;
|
|
130
130
|
--link-decoration: none;
|
|
131
131
|
font-style: normal;
|
|
132
132
|
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { Meta } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="FP.React Components/Popover/Readme" />
|
|
4
|
+
|
|
5
|
+
# Popover
|
|
6
|
+
|
|
7
|
+
The Popover component uses the native HTML Popover API to display floating content relative to a trigger element. It provides automatic top-layer rendering, light dismiss behavior, and built-in accessibility features.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Native API**: Uses HTML `popover` attribute for automatic layer management
|
|
12
|
+
- **Dismiss Modes**: Auto (light dismiss) or manual (explicit close required)
|
|
13
|
+
- **Positioning**: CSS anchor positioning with placement hints
|
|
14
|
+
- **Accessibility**: Built-in focus management, Escape key handling, and ARIA support
|
|
15
|
+
- **Customizable**: CSS custom properties for complete theming control
|
|
16
|
+
- **TypeScript**: Full type safety with comprehensive prop types
|
|
17
|
+
|
|
18
|
+
## Browser Requirements
|
|
19
|
+
|
|
20
|
+
- Chrome 125+
|
|
21
|
+
- Edge 125+
|
|
22
|
+
- Safari 17.4+
|
|
23
|
+
|
|
24
|
+
Requires native Popover API and CSS anchor positioning support.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @fpkit/acss
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Basic Usage
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { Popover } from '@fpkit/acss';
|
|
36
|
+
import '@fpkit/acss/styles';
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
return (
|
|
40
|
+
<Popover id="my-popover" triggerLabel="Open Menu">
|
|
41
|
+
<h3>Menu</h3>
|
|
42
|
+
<p>Popover content here</p>
|
|
43
|
+
</Popover>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
### Default (Auto Mode)
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Popover id="default-popover" triggerLabel="Open Popover">
|
|
54
|
+
<h3>Popover Title</h3>
|
|
55
|
+
<p>
|
|
56
|
+
This popover dismisses automatically when you click outside or press
|
|
57
|
+
Escape.
|
|
58
|
+
</p>
|
|
59
|
+
</Popover>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Manual Mode
|
|
63
|
+
|
|
64
|
+
Requires explicit close action and shows a backdrop overlay.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<Popover
|
|
68
|
+
id="manual-popover"
|
|
69
|
+
triggerLabel="Open Manual Popover"
|
|
70
|
+
mode="manual"
|
|
71
|
+
>
|
|
72
|
+
<h3>Manual Popover</h3>
|
|
73
|
+
<p>
|
|
74
|
+
This popover requires clicking the close button or trigger to dismiss.
|
|
75
|
+
It includes a backdrop overlay.
|
|
76
|
+
</p>
|
|
77
|
+
</Popover>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Placement Options
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
{/* Top placement */}
|
|
84
|
+
<Popover id="top-popover" triggerLabel="Open Above" placement="top">
|
|
85
|
+
<p>This popover appears above the trigger</p>
|
|
86
|
+
</Popover>
|
|
87
|
+
|
|
88
|
+
{/* Left placement */}
|
|
89
|
+
<Popover id="left-popover" triggerLabel="Open Left" placement="left">
|
|
90
|
+
<p>This popover appears to the left</p>
|
|
91
|
+
</Popover>
|
|
92
|
+
|
|
93
|
+
{/* Right placement */}
|
|
94
|
+
<Popover id="right-popover" triggerLabel="Open Right" placement="right">
|
|
95
|
+
<p>This popover appears to the right</p>
|
|
96
|
+
</Popover>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Custom Trigger
|
|
100
|
+
|
|
101
|
+
Use any React element as the trigger.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<Popover
|
|
105
|
+
id="custom-trigger-popover"
|
|
106
|
+
trigger={
|
|
107
|
+
<button style={{ padding: "0.5rem 1rem", borderRadius: "2rem" }}>
|
|
108
|
+
🎨 Custom
|
|
109
|
+
</button>
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
<h4>Custom Trigger</h4>
|
|
113
|
+
<p>You can use any React element as trigger</p>
|
|
114
|
+
</Popover>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Custom Styling
|
|
118
|
+
|
|
119
|
+
Theme the popover using CSS custom properties.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<Popover
|
|
123
|
+
id="custom-styled-popover"
|
|
124
|
+
triggerLabel="Custom Style"
|
|
125
|
+
styles={{
|
|
126
|
+
'--popover-bg': '#1a1a2e',
|
|
127
|
+
'--popover-border': '0.125rem solid #16213e',
|
|
128
|
+
'--popover-border-radius': '0.75rem',
|
|
129
|
+
'--popover-padding': '1.5rem',
|
|
130
|
+
'--popover-shadow': '0 0.5rem 1rem rgba(0, 0, 0, 0.3)',
|
|
131
|
+
color: '#eee',
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<h3 style={{ color: '#0f3' }}>Dark Theme</h3>
|
|
135
|
+
<p>Customize appearance using CSS custom properties</p>
|
|
136
|
+
</Popover>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Controlled State
|
|
140
|
+
|
|
141
|
+
Manage popover state externally.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { useState } from 'react';
|
|
145
|
+
|
|
146
|
+
function ControlledExample() {
|
|
147
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<>
|
|
151
|
+
<Popover
|
|
152
|
+
id="controlled-popover"
|
|
153
|
+
triggerLabel="Toggle Popover"
|
|
154
|
+
isOpen={isOpen}
|
|
155
|
+
onToggle={setIsOpen}
|
|
156
|
+
>
|
|
157
|
+
<h4>Controlled Popover</h4>
|
|
158
|
+
<p>State is managed externally</p>
|
|
159
|
+
<button onClick={() => setIsOpen(false)}>
|
|
160
|
+
Close via State
|
|
161
|
+
</button>
|
|
162
|
+
</Popover>
|
|
163
|
+
|
|
164
|
+
<div>
|
|
165
|
+
<p>Current state: {isOpen ? "Open" : "Closed"}</p>
|
|
166
|
+
<button onClick={() => setIsOpen(!isOpen)}>
|
|
167
|
+
External Toggle
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
</>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### With Form Content
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<Popover
|
|
179
|
+
id="form-popover"
|
|
180
|
+
triggerLabel="Show Form"
|
|
181
|
+
mode="manual"
|
|
182
|
+
>
|
|
183
|
+
<form
|
|
184
|
+
onSubmit={(e) => {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
alert('Form submitted!');
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
<h4>Contact Form</h4>
|
|
190
|
+
<input type="text" placeholder="Name" />
|
|
191
|
+
<input type="email" placeholder="Email" />
|
|
192
|
+
<textarea placeholder="Message" rows={3} />
|
|
193
|
+
<button type="submit">Submit</button>
|
|
194
|
+
</form>
|
|
195
|
+
</Popover>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## API Reference
|
|
199
|
+
|
|
200
|
+
### Props
|
|
201
|
+
|
|
202
|
+
| Prop | Type | Default | Description |
|
|
203
|
+
|------|------|---------|-------------|
|
|
204
|
+
| `id` | `string` | auto-generated | Unique identifier for popover |
|
|
205
|
+
| `children` | `ReactNode` | required | Content to display in popover |
|
|
206
|
+
| `trigger` | `ReactNode` | - | Custom trigger element |
|
|
207
|
+
| `triggerLabel` | `string` | `"Open"` | Aria-label for default trigger |
|
|
208
|
+
| `mode` | `"auto" \| "manual"` | `"auto"` | Dismiss behavior |
|
|
209
|
+
| `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Preferred position |
|
|
210
|
+
| `isOpen` | `boolean` | - | Controlled open state |
|
|
211
|
+
| `onToggle` | `(open: boolean) => void` | - | Toggle callback |
|
|
212
|
+
| `showCloseButton` | `boolean` | `true` for manual | Show close button |
|
|
213
|
+
| `closeButtonLabel` | `string` | `"Close"` | Aria-label for close button |
|
|
214
|
+
| `showArrow` | `boolean` | `true` | Show positioning arrow |
|
|
215
|
+
| `className` | `string` | - | Custom CSS class |
|
|
216
|
+
| `styles` | `CSSProperties` | - | Inline CSS variables |
|
|
217
|
+
|
|
218
|
+
### Modes
|
|
219
|
+
|
|
220
|
+
**Auto Mode** (default)
|
|
221
|
+
- Light dismiss: closes on Escape or outside click
|
|
222
|
+
- No backdrop overlay
|
|
223
|
+
- Close button hidden by default
|
|
224
|
+
|
|
225
|
+
**Manual Mode**
|
|
226
|
+
- Requires explicit close action
|
|
227
|
+
- Shows backdrop overlay
|
|
228
|
+
- Close button shown by default
|
|
229
|
+
|
|
230
|
+
## Styling
|
|
231
|
+
|
|
232
|
+
The Popover component uses CSS custom properties for theming. See the **Styling** page in Storybook for complete styling documentation.
|
|
233
|
+
|
|
234
|
+
### Quick Theme Example
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
<Popover
|
|
238
|
+
id="themed-popover"
|
|
239
|
+
styles={{
|
|
240
|
+
'--popover-bg': '#1a1a2e',
|
|
241
|
+
'--popover-border': '2px solid #16213e',
|
|
242
|
+
'--popover-border-radius': '12px',
|
|
243
|
+
'--popover-padding': '24px',
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
<p>Themed popover</p>
|
|
247
|
+
</Popover>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Accessibility
|
|
251
|
+
|
|
252
|
+
- **Keyboard Navigation**: Escape key closes auto-mode popovers
|
|
253
|
+
- **Focus Management**: Automatically managed by browser
|
|
254
|
+
- **ARIA Labels**: Proper labeling for triggers and close buttons
|
|
255
|
+
- **Screen Readers**: Semantic HTML with proper attributes
|
|
256
|
+
|
|
257
|
+
## Best Practices
|
|
258
|
+
|
|
259
|
+
1. **Always provide an ID**: While auto-generated IDs work, explicit IDs are more predictable
|
|
260
|
+
2. **Use meaningful trigger labels**: Ensure `triggerLabel` or custom trigger has clear purpose
|
|
261
|
+
3. **Choose appropriate mode**: Use auto for menus/tooltips, manual for forms/important content
|
|
262
|
+
4. **Test across browsers**: Verify popover support in target browsers
|
|
263
|
+
5. **Consider mobile**: Test touch interactions and viewport positioning
|
|
264
|
+
|
|
265
|
+
## Controlled vs Uncontrolled
|
|
266
|
+
|
|
267
|
+
### Uncontrolled (Default)
|
|
268
|
+
|
|
269
|
+
Let the browser manage state automatically:
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
<Popover id="uncontrolled" triggerLabel="Open">
|
|
273
|
+
<p>Content</p>
|
|
274
|
+
</Popover>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Controlled
|
|
278
|
+
|
|
279
|
+
Manage state externally for complex scenarios:
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
283
|
+
|
|
284
|
+
<Popover
|
|
285
|
+
id="controlled"
|
|
286
|
+
isOpen={isOpen}
|
|
287
|
+
onToggle={setIsOpen}
|
|
288
|
+
triggerLabel="Open"
|
|
289
|
+
>
|
|
290
|
+
<p>Content</p>
|
|
291
|
+
</Popover>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Related Components
|
|
295
|
+
|
|
296
|
+
- **Dialog**: For modal overlays requiring user response
|
|
297
|
+
- **Tooltip**: For brief contextual information (consider Popover for richer content)
|
|
298
|
+
- **Dropdown**: For selection lists (can be built with Popover)
|
|
299
|
+
|
|
300
|
+
## Migration from Old Popover
|
|
301
|
+
|
|
302
|
+
If upgrading from the previous hook-based Popover implementation (`usePopover` or the old `Popover` component from `@fpkit/acss/hooks`), follow this guide:
|
|
303
|
+
|
|
304
|
+
### Breaking Changes
|
|
305
|
+
|
|
306
|
+
The legacy Popover has been replaced with a new implementation using the native HTML Popover API. The old implementation is **deprecated** and will be removed in **v3.0.0**.
|
|
307
|
+
|
|
308
|
+
### Component Migration
|
|
309
|
+
|
|
310
|
+
**Before (Legacy - Deprecated):**
|
|
311
|
+
```tsx
|
|
312
|
+
// Old hook-based approach
|
|
313
|
+
import { Popover } from '@fpkit/acss/hooks'; // ❌ Deprecated
|
|
314
|
+
// or
|
|
315
|
+
import { usePopover } from '@fpkit/acss/hooks'; // ❌ Deprecated
|
|
316
|
+
|
|
317
|
+
<Popover popoverTrigger={<button>Hover me</button>}>
|
|
318
|
+
<p>Hover-based popover content</p>
|
|
319
|
+
</Popover>
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**After (New - Recommended):**
|
|
323
|
+
```tsx
|
|
324
|
+
// New native Popover API approach
|
|
325
|
+
import { Popover } from '@fpkit/acss'; // ✅ Recommended
|
|
326
|
+
|
|
327
|
+
<Popover
|
|
328
|
+
id="my-popover"
|
|
329
|
+
trigger={<button>Click me</button>}
|
|
330
|
+
>
|
|
331
|
+
<p>Click-based popover content</p>
|
|
332
|
+
</Popover>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Hook Migration
|
|
336
|
+
|
|
337
|
+
If you were using the `usePopover` hook directly for custom implementations:
|
|
338
|
+
|
|
339
|
+
**Before (Legacy - Deprecated):**
|
|
340
|
+
```tsx
|
|
341
|
+
import { usePopover } from '@fpkit/acss/hooks'; // ❌ Deprecated
|
|
342
|
+
|
|
343
|
+
function CustomPopover() {
|
|
344
|
+
const hoverRef = useRef(null);
|
|
345
|
+
const popoverRef = useRef(null);
|
|
346
|
+
const { isVisible, popoverPosition, handlePointerEvent, handlePointerLeave } =
|
|
347
|
+
usePopover(hoverRef, popoverRef, 10);
|
|
348
|
+
|
|
349
|
+
return (
|
|
350
|
+
<div>
|
|
351
|
+
<div
|
|
352
|
+
ref={hoverRef}
|
|
353
|
+
onPointerEnter={handlePointerEvent}
|
|
354
|
+
onPointerLeave={handlePointerLeave}
|
|
355
|
+
>
|
|
356
|
+
Hover trigger
|
|
357
|
+
</div>
|
|
358
|
+
{isVisible && (
|
|
359
|
+
<div
|
|
360
|
+
ref={popoverRef}
|
|
361
|
+
style={{
|
|
362
|
+
position: 'absolute',
|
|
363
|
+
top: popoverPosition.top,
|
|
364
|
+
left: popoverPosition.left,
|
|
365
|
+
}}
|
|
366
|
+
>
|
|
367
|
+
Popover content
|
|
368
|
+
</div>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**After (New - Recommended):**
|
|
376
|
+
```tsx
|
|
377
|
+
import { Popover } from '@fpkit/acss'; // ✅ Recommended
|
|
378
|
+
|
|
379
|
+
function CustomPopover() {
|
|
380
|
+
return (
|
|
381
|
+
<Popover
|
|
382
|
+
id="custom-popover"
|
|
383
|
+
trigger={<button>Click trigger</button>}
|
|
384
|
+
placement="bottom"
|
|
385
|
+
>
|
|
386
|
+
<p>Popover content</p>
|
|
387
|
+
</Popover>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Key Behavioral Changes
|
|
393
|
+
|
|
394
|
+
| Feature | Old (Legacy) | New (Native API) |
|
|
395
|
+
|---------|-------------|-----------------|
|
|
396
|
+
| **Trigger Method** | Hover/pointer events | Click (default native behavior) |
|
|
397
|
+
| **Positioning** | Manual calculation | Native CSS anchor positioning |
|
|
398
|
+
| **Layer Management** | z-index stacking | Automatic top-layer rendering |
|
|
399
|
+
| **Dismiss Behavior** | Manual pointer leave | Native light dismiss (Escape, outside click) |
|
|
400
|
+
| **Focus Management** | Manual implementation | Automatic browser handling |
|
|
401
|
+
| **Accessibility** | Limited ARIA support | Full native accessibility |
|
|
402
|
+
| **Browser Support** | All modern browsers | Chrome 125+, Safari 17.4+, Edge 125+ |
|
|
403
|
+
|
|
404
|
+
### Prop Mapping
|
|
405
|
+
|
|
406
|
+
| Old Prop | New Prop | Notes |
|
|
407
|
+
|----------|----------|-------|
|
|
408
|
+
| `popoverTrigger` | `trigger` | Custom trigger element |
|
|
409
|
+
| `content` | `children` | Popover content |
|
|
410
|
+
| N/A | `id` | Required for native API (auto-generated if omitted) |
|
|
411
|
+
| N/A | `mode` | `"auto"` or `"manual"` dismiss behavior |
|
|
412
|
+
| N/A | `placement` | `"top"`, `"bottom"`, `"left"`, `"right"` |
|
|
413
|
+
| N/A | `isOpen` | Controlled state support |
|
|
414
|
+
| N/A | `onToggle` | Callback when popover opens/closes |
|
|
415
|
+
| N/A | `showArrow` | Show positioning arrow indicator |
|
|
416
|
+
| N/A | `styles` | CSS custom properties for theming |
|
|
417
|
+
|
|
418
|
+
### Benefits of Migration
|
|
419
|
+
|
|
420
|
+
✅ **Better Accessibility**: Native focus management and keyboard navigation
|
|
421
|
+
✅ **Simpler API**: No manual positioning calculations required
|
|
422
|
+
✅ **Better Performance**: Browser-native layer management
|
|
423
|
+
✅ **Future-Proof**: Uses web platform standards
|
|
424
|
+
✅ **Rich Features**: Built-in animations, backdrops, and light dismiss
|
|
425
|
+
✅ **Type Safety**: Full TypeScript support with comprehensive prop types
|
|
426
|
+
|
|
427
|
+
### Migration Checklist
|
|
428
|
+
|
|
429
|
+
- [ ] Replace `import { Popover } from '@fpkit/acss/hooks'` with `import { Popover } from '@fpkit/acss'`
|
|
430
|
+
- [ ] Replace `import { usePopover }` with the new Popover component
|
|
431
|
+
- [ ] Change `popoverTrigger` prop to `trigger`
|
|
432
|
+
- [ ] Move popover content to `children` prop
|
|
433
|
+
- [ ] Add unique `id` prop (recommended but optional)
|
|
434
|
+
- [ ] Choose `mode="auto"` (default) or `mode="manual"`
|
|
435
|
+
- [ ] Test browser compatibility (Chrome 125+, Safari 17.4+, Edge 125+)
|
|
436
|
+
- [ ] Update any custom styling to use CSS custom properties
|
|
437
|
+
- [ ] Test keyboard navigation (Escape key, Tab order)
|
|
438
|
+
- [ ] Verify accessibility with screen readers
|
|
439
|
+
|
|
440
|
+
### Fallback for Older Browsers
|
|
441
|
+
|
|
442
|
+
The native Popover API is not supported in older browsers. Consider:
|
|
443
|
+
|
|
444
|
+
1. **Feature Detection**: Check for popover support before using
|
|
445
|
+
2. **Polyfill**: Use a popover polyfill for older browser support
|
|
446
|
+
3. **Progressive Enhancement**: Provide fallback UI for unsupported browsers
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
// Feature detection example
|
|
450
|
+
if (!HTMLElement.prototype.hasOwnProperty('popover')) {
|
|
451
|
+
console.warn('Popover API not supported - consider polyfill');
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Need Help?
|
|
456
|
+
|
|
457
|
+
- Review the **Examples** section above for common use cases
|
|
458
|
+
- Check the **API Reference** for complete prop documentation
|
|
459
|
+
- See the **Styling** page for theming and customization options
|
|
460
|
+
- Open an issue on GitHub if you encounter migration problems
|
|
461
|
+
|
|
462
|
+
## Troubleshooting
|
|
463
|
+
|
|
464
|
+
### Popover not appearing
|
|
465
|
+
|
|
466
|
+
Check browser support and console for errors about popover API.
|
|
467
|
+
|
|
468
|
+
### Positioning issues
|
|
469
|
+
|
|
470
|
+
CSS anchor positioning requires Chrome 125+. Older browsers use fallback positioning.
|
|
471
|
+
|
|
472
|
+
### Animation not working
|
|
473
|
+
|
|
474
|
+
Ensure `@starting-style` is supported or provide fallback transitions.
|
|
475
|
+
|
|
476
|
+
### Close button not showing
|
|
477
|
+
|
|
478
|
+
For auto mode, close button is hidden by default. Set `showCloseButton={true}` to show.
|