@oxyhq/bloom 0.10.0 → 0.12.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/lib/commonjs/button/Button.js +11 -5
- package/lib/commonjs/button/Button.js.map +1 -1
- package/lib/commonjs/button/Button.web.js +9 -5
- package/lib/commonjs/button/Button.web.js.map +1 -1
- package/lib/commonjs/fab/Fab.js +199 -0
- package/lib/commonjs/fab/Fab.js.map +1 -0
- package/lib/commonjs/fab/Fab.web.js +246 -0
- package/lib/commonjs/fab/Fab.web.js.map +1 -0
- package/lib/commonjs/fab/index.js +13 -0
- package/lib/commonjs/fab/index.js.map +1 -0
- package/lib/commonjs/fab/index.web.js +13 -0
- package/lib/commonjs/fab/index.web.js.map +1 -0
- package/lib/commonjs/fab/types.js +6 -0
- package/lib/commonjs/fab/types.js.map +1 -0
- package/lib/commonjs/index.js +8 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +24 -16
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/module/button/Button.js +11 -5
- package/lib/module/button/Button.js.map +1 -1
- package/lib/module/button/Button.web.js +9 -5
- package/lib/module/button/Button.web.js.map +1 -1
- package/lib/module/fab/Fab.js +194 -0
- package/lib/module/fab/Fab.js.map +1 -0
- package/lib/module/fab/Fab.web.js +241 -0
- package/lib/module/fab/Fab.web.js.map +1 -0
- package/lib/module/fab/index.js +4 -0
- package/lib/module/fab/index.js.map +1 -0
- package/lib/module/fab/index.web.js +14 -0
- package/lib/module/fab/index.web.js.map +1 -0
- package/lib/module/fab/types.js +4 -0
- package/lib/module/fab/types.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/typescript/commonjs/button/Button.d.ts.map +1 -1
- package/lib/typescript/commonjs/button/Button.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/fab/Fab.d.ts +5 -0
- package/lib/typescript/commonjs/fab/Fab.d.ts.map +1 -0
- package/lib/typescript/commonjs/fab/Fab.web.d.ts +5 -0
- package/lib/typescript/commonjs/fab/Fab.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/fab/index.d.ts +3 -0
- package/lib/typescript/commonjs/fab/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/fab/index.web.d.ts +3 -0
- package/lib/typescript/commonjs/fab/index.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/fab/types.d.ts +99 -0
- package/lib/typescript/commonjs/fab/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +2 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +2 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/module/button/Button.d.ts.map +1 -1
- package/lib/typescript/module/button/Button.web.d.ts.map +1 -1
- package/lib/typescript/module/fab/Fab.d.ts +5 -0
- package/lib/typescript/module/fab/Fab.d.ts.map +1 -0
- package/lib/typescript/module/fab/Fab.web.d.ts +5 -0
- package/lib/typescript/module/fab/Fab.web.d.ts.map +1 -0
- package/lib/typescript/module/fab/index.d.ts +3 -0
- package/lib/typescript/module/fab/index.d.ts.map +1 -0
- package/lib/typescript/module/fab/index.web.d.ts +3 -0
- package/lib/typescript/module/fab/index.web.d.ts.map +1 -0
- package/lib/typescript/module/fab/types.d.ts +99 -0
- package/lib/typescript/module/fab/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +2 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +2 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/package.json +17 -1
- package/src/__tests__/Fab.test.tsx +76 -0
- package/src/__tests__/Fab.web.test.tsx +114 -0
- package/src/button/Button.tsx +11 -5
- package/src/button/Button.web.tsx +8 -5
- package/src/fab/Fab.stories.tsx +71 -0
- package/src/fab/Fab.tsx +189 -0
- package/src/fab/Fab.web.tsx +258 -0
- package/src/fab/index.ts +2 -0
- package/src/fab/index.web.ts +12 -0
- package/src/fab/types.ts +125 -0
- package/src/index.ts +2 -0
- package/src/index.web.ts +2 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
memo,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useId,
|
|
6
|
+
useMemo,
|
|
7
|
+
type CSSProperties,
|
|
8
|
+
type MouseEvent,
|
|
9
|
+
} from 'react';
|
|
10
|
+
|
|
11
|
+
import { useTheme } from '../theme/use-theme';
|
|
12
|
+
import type { Theme } from '../theme/types';
|
|
13
|
+
import type { FabPlacement, FabProps, FabSize, FabVariant } from './types';
|
|
14
|
+
|
|
15
|
+
export type { FabProps, FabVariant, FabSize, FabPlacement } from './types';
|
|
16
|
+
|
|
17
|
+
const SIZE_CONFIG: Record<FabSize, { diameter: number; iconBox: number; fontSize: number }> = {
|
|
18
|
+
small: { diameter: 40, iconBox: 20, fontSize: 14 },
|
|
19
|
+
medium: { diameter: 56, iconBox: 24, fontSize: 15 },
|
|
20
|
+
large: { diameter: 64, iconBox: 28, fontSize: 16 },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const PILL_RADIUS = 999;
|
|
24
|
+
const PRESS_SCALE = 0.94;
|
|
25
|
+
const DEFAULT_OFFSET = 16;
|
|
26
|
+
const DEFAULT_Z_INDEX = 50;
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Per-state CSS injection
|
|
30
|
+
//
|
|
31
|
+
// Inline styles cannot express `:hover` / `:focus-visible` / `:active`. We
|
|
32
|
+
// inject one static stylesheet (keyed by id, once) defining the interaction
|
|
33
|
+
// behavior for the base `bloom-fab` class; per-instance resolved colors stay
|
|
34
|
+
// inline. The focus-ring color is read from a CSS custom property the
|
|
35
|
+
// component sets inline (`--bloom-fab-ring`).
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
const STYLE_ID = 'bloom-fab-web-css';
|
|
39
|
+
|
|
40
|
+
const BLOOM_FAB_CSS = `
|
|
41
|
+
.bloom-fab {
|
|
42
|
+
appearance: none;
|
|
43
|
+
-webkit-appearance: none;
|
|
44
|
+
box-sizing: border-box;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
flex-direction: row;
|
|
49
|
+
gap: 8px;
|
|
50
|
+
margin: 0;
|
|
51
|
+
border: none;
|
|
52
|
+
font-family: inherit;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
user-select: none;
|
|
55
|
+
outline: none;
|
|
56
|
+
transition: opacity 120ms ease, transform 120ms ease, box-shadow 160ms ease, background-color 120ms ease;
|
|
57
|
+
}
|
|
58
|
+
.bloom-fab:disabled,
|
|
59
|
+
.bloom-fab[aria-disabled="true"] {
|
|
60
|
+
cursor: default;
|
|
61
|
+
opacity: 0.5;
|
|
62
|
+
}
|
|
63
|
+
.bloom-fab:not(:disabled):not([aria-disabled="true"]):hover {
|
|
64
|
+
box-shadow: var(--bloom-fab-shadow-hover);
|
|
65
|
+
}
|
|
66
|
+
.bloom-fab:not(:disabled):not([aria-disabled="true"]):active {
|
|
67
|
+
transform: scale(var(--bloom-fab-press-scale, 1));
|
|
68
|
+
}
|
|
69
|
+
.bloom-fab:focus-visible {
|
|
70
|
+
outline: 2px solid var(--bloom-fab-ring, currentColor);
|
|
71
|
+
outline-offset: 3px;
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
function useFabCss(): void {
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (typeof document === 'undefined') return;
|
|
78
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
79
|
+
const style = document.createElement('style');
|
|
80
|
+
style.id = STYLE_ID;
|
|
81
|
+
style.textContent = BLOOM_FAB_CSS;
|
|
82
|
+
document.head.appendChild(style);
|
|
83
|
+
}, []);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Positioning for a placement on web.
|
|
88
|
+
*
|
|
89
|
+
* CRITICAL: the positioned placements use `position: sticky`, NOT `fixed`. A
|
|
90
|
+
* sticky element is positioned relative to its nearest scrolling ancestor and
|
|
91
|
+
* stays within its containing block — so the FAB tracks the bottom-right of the
|
|
92
|
+
* CENTRAL CONTENT COLUMN as it scrolls and never escapes to the viewport edge /
|
|
93
|
+
* over a side rail in a constrained multi-column app layout.
|
|
94
|
+
*
|
|
95
|
+
* To pin a sticky element to the BOTTOM of a column it must sit at the end of
|
|
96
|
+
* the scroll content with `align-self: flex-end` (consumer column should be a
|
|
97
|
+
* flex column) — documented in the component's usage story. We set `bottom`
|
|
98
|
+
* and a self-alignment so the common case works with zero consumer CSS beyond a
|
|
99
|
+
* `flex-col` column.
|
|
100
|
+
*/
|
|
101
|
+
function placementStyle(placement: FabPlacement, offset: number): CSSProperties {
|
|
102
|
+
if (placement === 'static') return {};
|
|
103
|
+
const style: CSSProperties = { position: 'sticky' };
|
|
104
|
+
if (placement === 'bottom-right' || placement === 'bottom-left') {
|
|
105
|
+
style.bottom = offset;
|
|
106
|
+
} else {
|
|
107
|
+
style.top = offset;
|
|
108
|
+
}
|
|
109
|
+
// Sticky elements can't be pushed horizontally by left/right the way absolute
|
|
110
|
+
// ones are; we self-align within the (flex column) container instead, and add
|
|
111
|
+
// a horizontal margin for the offset so the FAB sits `offset` px from the
|
|
112
|
+
// column's inline edge.
|
|
113
|
+
if (placement === 'bottom-right' || placement === 'top-right') {
|
|
114
|
+
style.alignSelf = 'flex-end';
|
|
115
|
+
style.marginRight = offset;
|
|
116
|
+
} else {
|
|
117
|
+
style.alignSelf = 'flex-start';
|
|
118
|
+
style.marginLeft = offset;
|
|
119
|
+
}
|
|
120
|
+
return style;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveVariant(
|
|
124
|
+
variant: FabVariant,
|
|
125
|
+
c: Theme['colors'],
|
|
126
|
+
): { background: string; foreground: string; ring: string } {
|
|
127
|
+
switch (variant) {
|
|
128
|
+
case 'secondary':
|
|
129
|
+
case 'surface':
|
|
130
|
+
return { background: c.card, foreground: c.text, ring: c.primary };
|
|
131
|
+
case 'primary':
|
|
132
|
+
default:
|
|
133
|
+
return { background: c.primary, foreground: c.primaryForeground, ring: c.primary };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const FabWebComponent: React.FC<FabProps> = ({
|
|
138
|
+
onPress,
|
|
139
|
+
onClick,
|
|
140
|
+
icon,
|
|
141
|
+
children,
|
|
142
|
+
label,
|
|
143
|
+
variant = 'primary',
|
|
144
|
+
size = 'medium',
|
|
145
|
+
placement = 'bottom-right',
|
|
146
|
+
offset = DEFAULT_OFFSET,
|
|
147
|
+
disabled = false,
|
|
148
|
+
accessibilityLabel,
|
|
149
|
+
'aria-label': ariaLabelProp,
|
|
150
|
+
accessibilityHint,
|
|
151
|
+
style,
|
|
152
|
+
labelStyle,
|
|
153
|
+
className,
|
|
154
|
+
testID,
|
|
155
|
+
zIndex = DEFAULT_Z_INDEX,
|
|
156
|
+
id,
|
|
157
|
+
title,
|
|
158
|
+
type = 'button',
|
|
159
|
+
}) => {
|
|
160
|
+
useFabCss();
|
|
161
|
+
const theme = useTheme();
|
|
162
|
+
const reactId = useId();
|
|
163
|
+
const resolvedId = id ?? `bloom-fab-${reactId}`;
|
|
164
|
+
|
|
165
|
+
const sizeConfig = SIZE_CONFIG[size];
|
|
166
|
+
const isExtended = label != null && label.length > 0;
|
|
167
|
+
const content = icon ?? children;
|
|
168
|
+
const variantColors = useMemo(() => resolveVariant(variant, theme.colors), [variant, theme.colors]);
|
|
169
|
+
|
|
170
|
+
// Resolved-token shadows. The token is already a full color (per the Bloom
|
|
171
|
+
// web CSS-var contract), so it is used directly — never wrapped in hsl().
|
|
172
|
+
const restShadow = `0 4px 12px ${theme.colors.shadow}`;
|
|
173
|
+
const hoverShadow = `0 8px 20px ${theme.colors.shadow}`;
|
|
174
|
+
|
|
175
|
+
const containerStyle = useMemo((): CSSProperties => {
|
|
176
|
+
const base: CSSProperties = {
|
|
177
|
+
backgroundColor: variantColors.background,
|
|
178
|
+
color: variantColors.foreground,
|
|
179
|
+
borderRadius: PILL_RADIUS,
|
|
180
|
+
height: sizeConfig.diameter,
|
|
181
|
+
zIndex,
|
|
182
|
+
boxShadow: restShadow,
|
|
183
|
+
fontSize: sizeConfig.fontSize,
|
|
184
|
+
fontWeight: 600,
|
|
185
|
+
['--bloom-fab-ring' as string]: variantColors.ring,
|
|
186
|
+
['--bloom-fab-shadow-hover' as string]: hoverShadow,
|
|
187
|
+
['--bloom-fab-press-scale' as string]: PRESS_SCALE,
|
|
188
|
+
...placementStyle(placement, offset),
|
|
189
|
+
};
|
|
190
|
+
if (isExtended) {
|
|
191
|
+
base.paddingLeft = size === 'small' ? 14 : 20;
|
|
192
|
+
base.paddingRight = size === 'small' ? 14 : 20;
|
|
193
|
+
} else {
|
|
194
|
+
base.width = sizeConfig.diameter;
|
|
195
|
+
}
|
|
196
|
+
return base;
|
|
197
|
+
}, [
|
|
198
|
+
variantColors,
|
|
199
|
+
sizeConfig.diameter,
|
|
200
|
+
sizeConfig.fontSize,
|
|
201
|
+
zIndex,
|
|
202
|
+
restShadow,
|
|
203
|
+
hoverShadow,
|
|
204
|
+
placement,
|
|
205
|
+
offset,
|
|
206
|
+
isExtended,
|
|
207
|
+
size,
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
const handleClick = useCallback(
|
|
211
|
+
(event: MouseEvent<HTMLButtonElement>) => {
|
|
212
|
+
if (disabled) {
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
onClick?.(event);
|
|
217
|
+
onPress?.();
|
|
218
|
+
},
|
|
219
|
+
[disabled, onClick, onPress],
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const ariaLabel = ariaLabelProp ?? accessibilityLabel ?? label;
|
|
223
|
+
const composedClassName = ['bloom-fab'].concat(className ? [className] : []).join(' ');
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<button
|
|
227
|
+
id={resolvedId}
|
|
228
|
+
type={type}
|
|
229
|
+
className={composedClassName}
|
|
230
|
+
style={{ ...containerStyle, ...(style as CSSProperties) }}
|
|
231
|
+
onClick={handleClick}
|
|
232
|
+
disabled={disabled}
|
|
233
|
+
aria-disabled={disabled || undefined}
|
|
234
|
+
aria-label={ariaLabel}
|
|
235
|
+
title={title ?? accessibilityHint}
|
|
236
|
+
data-testid={testID}
|
|
237
|
+
>
|
|
238
|
+
{content != null && (
|
|
239
|
+
<span
|
|
240
|
+
aria-hidden={isExtended ? 'true' : undefined}
|
|
241
|
+
style={{
|
|
242
|
+
display: 'inline-flex',
|
|
243
|
+
alignItems: 'center',
|
|
244
|
+
justifyContent: 'center',
|
|
245
|
+
width: sizeConfig.iconBox,
|
|
246
|
+
height: sizeConfig.iconBox,
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
{content}
|
|
250
|
+
</span>
|
|
251
|
+
)}
|
|
252
|
+
{isExtended && <span style={labelStyle as CSSProperties}>{label}</span>}
|
|
253
|
+
</button>
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export const Fab = memo(FabWebComponent);
|
|
258
|
+
Fab.displayName = 'Fab';
|
package/src/fab/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Web variant of the `./fab` barrel.
|
|
2
|
+
//
|
|
3
|
+
// The default barrel (`./index.ts`) re-exports the React Native (`Pressable`)
|
|
4
|
+
// implementation, whose `placement` anchoring uses `position: absolute` within
|
|
5
|
+
// the nearest positioned ancestor. The web fork (`./Fab.web`) renders a real
|
|
6
|
+
// HTML `<button>` and uses `position: sticky` for its anchored placements so
|
|
7
|
+
// the FAB tracks the bottom-right of its CONTAINING content column while
|
|
8
|
+
// scrolling — never the viewport edge (the bug that motivated moving the FAB
|
|
9
|
+
// into Bloom). Web bundlers select this file via the `"browser"` export
|
|
10
|
+
// condition in `package.json`'s `exports['./fab']`.
|
|
11
|
+
export { Fab } from './Fab.web';
|
|
12
|
+
export type { FabProps, FabVariant, FabSize, FabPlacement } from './types';
|
package/src/fab/types.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Visual style of the FAB.
|
|
5
|
+
*
|
|
6
|
+
* - `primary` — filled with the brand `primary` token + `primaryForeground`
|
|
7
|
+
* icon/label. The default; the high-emphasis floating action.
|
|
8
|
+
* - `secondary`— filled with the `card`/surface token + `text` foreground.
|
|
9
|
+
* A lower-emphasis FAB that still reads as elevated.
|
|
10
|
+
* - `surface` — alias of `secondary`, named for Material-style "surface" FABs.
|
|
11
|
+
*/
|
|
12
|
+
export type FabVariant = 'primary' | 'secondary' | 'surface';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* FAB sizes, matching Material-ish conventions but in Bloom's scale:
|
|
16
|
+
* - `small` — 40px (compact / dense layouts)
|
|
17
|
+
* - `medium` — 56px (the canonical FAB; the default)
|
|
18
|
+
* - `large` — 64px (high-prominence primary action)
|
|
19
|
+
*
|
|
20
|
+
* In the `extended` variant the height tracks the size but the FAB grows
|
|
21
|
+
* horizontally to fit its icon + label as a pill.
|
|
22
|
+
*/
|
|
23
|
+
export type FabSize = 'small' | 'medium' | 'large';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Where the FAB anchors itself.
|
|
27
|
+
*
|
|
28
|
+
* - `bottom-right` (default) / `bottom-left` / `top-right` / `top-left` —
|
|
29
|
+
* the FAB pins itself to that corner of its CONTAINING block. On web this
|
|
30
|
+
* uses `position: sticky` so it tracks the bottom of the scrolling column
|
|
31
|
+
* (NOT the viewport edge), which is exactly what a constrained multi-column
|
|
32
|
+
* app layout needs. On native it uses `position: absolute` within the
|
|
33
|
+
* nearest positioned ancestor.
|
|
34
|
+
* - `static` — no positioning at all. The FAB is laid out inline and the
|
|
35
|
+
* consumer is fully responsible for placement (e.g. wrapping it in their
|
|
36
|
+
* own absolutely/sticky-positioned container). Use this when you need
|
|
37
|
+
* custom containment.
|
|
38
|
+
*
|
|
39
|
+
* CRITICAL: the positioned placements DO NOT use web `position: fixed` and are
|
|
40
|
+
* never anchored to the viewport. They stay inside the containing column so the
|
|
41
|
+
* FAB never escapes a constrained 3-column app layout and sits over a side rail.
|
|
42
|
+
*/
|
|
43
|
+
export type FabPlacement =
|
|
44
|
+
| 'bottom-right'
|
|
45
|
+
| 'bottom-left'
|
|
46
|
+
| 'top-right'
|
|
47
|
+
| 'top-left'
|
|
48
|
+
| 'static';
|
|
49
|
+
|
|
50
|
+
export interface FabProps {
|
|
51
|
+
/** Press handler. */
|
|
52
|
+
onPress?: () => void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The icon to render inside the FAB. Typically a Bloom icon element
|
|
56
|
+
* (`<Icons.Plus .../>`). For an icon-only FAB this is the whole content; for
|
|
57
|
+
* the extended variant it sits to the left of the `label`.
|
|
58
|
+
*
|
|
59
|
+
* `children` is accepted as an alias/override of `icon` for ergonomics — if
|
|
60
|
+
* both are given, `icon` wins and `children` is ignored.
|
|
61
|
+
*/
|
|
62
|
+
icon?: React.ReactNode;
|
|
63
|
+
children?: React.ReactNode;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* When provided, renders the EXTENDED FAB: a pill with the icon followed by
|
|
67
|
+
* this text label. Omit for the canonical circular icon FAB.
|
|
68
|
+
*/
|
|
69
|
+
label?: string;
|
|
70
|
+
|
|
71
|
+
variant?: FabVariant;
|
|
72
|
+
size?: FabSize;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Corner of the containing block to anchor to. Defaults to `'bottom-right'`.
|
|
76
|
+
* See {@link FabPlacement}. The DEFAULT never escapes the containing column
|
|
77
|
+
* (web uses `position: sticky`, native uses `position: absolute`).
|
|
78
|
+
*/
|
|
79
|
+
placement?: FabPlacement;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Distance (in px) from the anchored edges. Applies to whichever edges the
|
|
83
|
+
* `placement` pins to. Defaults to `16`.
|
|
84
|
+
*/
|
|
85
|
+
offset?: number;
|
|
86
|
+
|
|
87
|
+
disabled?: boolean;
|
|
88
|
+
|
|
89
|
+
/** Required for icon-only FABs so screen readers announce the action. */
|
|
90
|
+
accessibilityLabel?: string;
|
|
91
|
+
accessibilityHint?: string;
|
|
92
|
+
|
|
93
|
+
/** Container style override (merged last). */
|
|
94
|
+
style?: StyleProp<ViewStyle>;
|
|
95
|
+
/** Extended-FAB label text style override. */
|
|
96
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
97
|
+
/** NativeWind / DOM className passthrough. */
|
|
98
|
+
className?: string;
|
|
99
|
+
testID?: string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Hard `zIndex` override. Defaults to a high value so the FAB floats above
|
|
103
|
+
* scrolling content.
|
|
104
|
+
*/
|
|
105
|
+
zIndex?: number;
|
|
106
|
+
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// Web-only props
|
|
109
|
+
//
|
|
110
|
+
// No-ops in the native (`Pressable`) implementation — present on the shared
|
|
111
|
+
// type so a single `<Fab>` call site type-checks on both platforms. The web
|
|
112
|
+
// fork (`Fab.web.tsx`) renders a real DOM element and consumes them.
|
|
113
|
+
// -------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
/** Web click handler. Fires together with `onPress`. */
|
|
116
|
+
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
117
|
+
/** `aria-label` (web only). Falls back to `accessibilityLabel`. */
|
|
118
|
+
'aria-label'?: string;
|
|
119
|
+
/** `title` tooltip attribute (web only). */
|
|
120
|
+
title?: string;
|
|
121
|
+
/** Inline DOM `id` (web only). */
|
|
122
|
+
id?: string;
|
|
123
|
+
/** HTML button `type` (web only). Defaults to `'button'`. */
|
|
124
|
+
type?: 'button' | 'submit' | 'reset';
|
|
125
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -38,6 +38,8 @@ export type {
|
|
|
38
38
|
DialogProps,
|
|
39
39
|
} from './dialog';
|
|
40
40
|
export * from './button';
|
|
41
|
+
export { Fab } from './fab';
|
|
42
|
+
export type { FabProps, FabVariant, FabSize, FabPlacement } from './fab';
|
|
41
43
|
export * from './grouped-buttons';
|
|
42
44
|
export * from './divider';
|
|
43
45
|
export * from './radio-indicator';
|
package/src/index.web.ts
CHANGED
|
@@ -43,6 +43,8 @@ export type {
|
|
|
43
43
|
DialogProps,
|
|
44
44
|
} from './dialog/index.web';
|
|
45
45
|
export * from './button/index.web';
|
|
46
|
+
export { Fab } from './fab/index.web';
|
|
47
|
+
export type { FabProps, FabVariant, FabSize, FabPlacement } from './fab/index.web';
|
|
46
48
|
export * from './grouped-buttons';
|
|
47
49
|
export * from './divider';
|
|
48
50
|
export * from './radio-indicator';
|