@flood-md/react 0.2.2 → 0.2.3
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/README.md +453 -3
- package/dist/choice-group.d.ts +16 -0
- package/dist/choice-group.js +52 -0
- package/dist/disclosure.d.ts +12 -0
- package/dist/disclosure.js +20 -0
- package/dist/field-state.d.ts +6 -0
- package/dist/field-state.js +8 -0
- package/dist/foundations.css +272 -7
- package/dist/index.d.ts +231 -3
- package/dist/index.js +174 -15
- package/dist/navigation.d.ts +4 -0
- package/dist/navigation.js +68 -0
- package/dist/overlay-runtime.d.ts +38 -0
- package/dist/overlay-runtime.js +104 -0
- package/dist/overlays.d.ts +7 -0
- package/dist/overlays.js +170 -0
- package/dist/page-window.d.ts +1 -0
- package/dist/page-window.js +15 -0
- package/dist/scroll-area.d.ts +3 -0
- package/dist/scroll-area.js +220 -0
- package/dist/select.d.ts +3 -0
- package/dist/select.js +284 -0
- package/dist/styles.css +2 -0
- package/package.json +2 -2
package/dist/select.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { resolveFieldStatus } from "./field-state.js";
|
|
3
|
+
import { Children, Fragment, forwardRef, isValidElement, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
|
|
4
|
+
import { OverlayScrollArea } from "./scroll-area.js";
|
|
5
|
+
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
6
|
+
const text = (children) => Children.toArray(children).map((child) => typeof child === "string" || typeof child === "number" ? String(child) : isValidElement(child) ? text(child.props.children) : "").join("");
|
|
7
|
+
function initialChoices(children, group, groupDisabled = false, groupHidden = false) {
|
|
8
|
+
return Children.toArray(children).flatMap((child) => {
|
|
9
|
+
if (!isValidElement(child))
|
|
10
|
+
return [];
|
|
11
|
+
if (child.type === Fragment)
|
|
12
|
+
return initialChoices(child.props.children, group, groupDisabled, groupHidden);
|
|
13
|
+
if (child.type === "optgroup")
|
|
14
|
+
return initialChoices(child.props.children, child.props.label, groupDisabled || Boolean(child.props.disabled), groupHidden || Boolean(child.props.hidden));
|
|
15
|
+
if (child.type !== "option")
|
|
16
|
+
return [];
|
|
17
|
+
const label = text(child.props.children);
|
|
18
|
+
return [{ value: String(child.props.value ?? label), label: child.props.label ?? label, disabled: groupDisabled || Boolean(child.props.disabled), hidden: groupHidden || Boolean(child.props.hidden), group }];
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** Internal implementation. The public entry retains the native SelectProps/ref contract. */
|
|
22
|
+
export const SingleSelect = forwardRef(function SingleSelect({ children, value, defaultValue, onChange, onInvalid, onFocus, onBlur, onKeyDown, onKeyUp, onClick, disabled, required, id, className, style, title, autoFocus, invalid, status, controlSize = "md", "aria-label": ariaLabel, "aria-labelledby": labelledBy, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...nativeProps }, forwardedRef) {
|
|
23
|
+
const generatedId = useId();
|
|
24
|
+
const triggerId = id ?? `flood-select-${generatedId}`;
|
|
25
|
+
const menuId = `${triggerId}-options`, validationId = `${triggerId}-validation`;
|
|
26
|
+
const native = useRef(null), trigger = useRef(null), menu = useRef(null);
|
|
27
|
+
const scrollViewport = useRef(null);
|
|
28
|
+
const [choices, setChoices] = useState(() => initialChoices(children));
|
|
29
|
+
const [selected, setSelected] = useState(() => String(defaultValue ?? initialChoices(children).find((choice) => !choice.disabled && !choice.hidden)?.value ?? ""));
|
|
30
|
+
const [open, setOpen] = useState(false), [active, setActive] = useState(-1);
|
|
31
|
+
const [validation, setValidation] = useState("");
|
|
32
|
+
const [position, setPosition] = useState({ left: 0, top: 0, width: 0, maxHeight: 0, viewportHeight: 0 });
|
|
33
|
+
const controlled = value !== undefined;
|
|
34
|
+
const currentValue = controlled ? String(value) : selected;
|
|
35
|
+
const current = useRef({ controlled, value: currentValue });
|
|
36
|
+
current.current = { controlled, value: currentValue };
|
|
37
|
+
const search = useRef({ value: "", time: 0 });
|
|
38
|
+
useImperativeHandle(forwardedRef, () => native.current, []);
|
|
39
|
+
function readChoices() {
|
|
40
|
+
if (!native.current)
|
|
41
|
+
return choices;
|
|
42
|
+
return Array.from(native.current.options).map((option) => {
|
|
43
|
+
const parent = option.parentElement;
|
|
44
|
+
const group = parent?.tagName === "OPTGROUP" ? parent : undefined;
|
|
45
|
+
return { value: option.value, label: option.label, disabled: option.disabled || Boolean(group?.disabled), hidden: Boolean(option.hidden) || Boolean(group?.hidden), group: group?.label };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
useBrowserLayoutEffect(() => {
|
|
49
|
+
setChoices(readChoices());
|
|
50
|
+
if (!controlled && native.current)
|
|
51
|
+
setSelected(native.current.value);
|
|
52
|
+
}, [children, value]);
|
|
53
|
+
useBrowserLayoutEffect(() => {
|
|
54
|
+
const control = native.current;
|
|
55
|
+
if (!control)
|
|
56
|
+
return;
|
|
57
|
+
const focus = control.focus;
|
|
58
|
+
control.focus = (options) => trigger.current?.focus(options);
|
|
59
|
+
return () => { control.focus = focus; };
|
|
60
|
+
}, []);
|
|
61
|
+
useEffect(() => { if (autoFocus)
|
|
62
|
+
trigger.current?.focus(); }, [autoFocus]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const form = native.current?.form;
|
|
65
|
+
if (!form)
|
|
66
|
+
return;
|
|
67
|
+
const reset = (event) => setTimeout(() => {
|
|
68
|
+
if (event.defaultPrevented || !native.current)
|
|
69
|
+
return;
|
|
70
|
+
if (current.current.controlled)
|
|
71
|
+
native.current.value = current.current.value;
|
|
72
|
+
else
|
|
73
|
+
setSelected(native.current.value);
|
|
74
|
+
setChoices(readChoices());
|
|
75
|
+
setValidation("");
|
|
76
|
+
setOpen(false);
|
|
77
|
+
}, 0);
|
|
78
|
+
form.addEventListener("reset", reset);
|
|
79
|
+
return () => form.removeEventListener("reset", reset);
|
|
80
|
+
}, [nativeProps.form]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const observer = new MutationObserver(() => { if (trigger.current?.matches(":disabled"))
|
|
83
|
+
setOpen(false); });
|
|
84
|
+
let parent = trigger.current?.parentElement;
|
|
85
|
+
while (parent) {
|
|
86
|
+
if (parent.tagName === "FIELDSET")
|
|
87
|
+
observer.observe(parent, { attributes: true, attributeFilter: ["disabled"] });
|
|
88
|
+
parent = parent.parentElement;
|
|
89
|
+
}
|
|
90
|
+
return () => observer.disconnect();
|
|
91
|
+
}, []);
|
|
92
|
+
useEffect(() => { if (disabled || nativeProps.hidden)
|
|
93
|
+
setOpen(false); }, [disabled, nativeProps.hidden]);
|
|
94
|
+
function enabled(choice) { return !choice.disabled && !choice.hidden; }
|
|
95
|
+
function openMenu(index) {
|
|
96
|
+
if (trigger.current?.matches(":disabled"))
|
|
97
|
+
return;
|
|
98
|
+
const fresh = readChoices();
|
|
99
|
+
setChoices(fresh);
|
|
100
|
+
const nativeValue = !current.current.controlled && native.current ? native.current.value : current.current.value;
|
|
101
|
+
if (!current.current.controlled)
|
|
102
|
+
setSelected(nativeValue);
|
|
103
|
+
const selectedIndex = fresh.findIndex((choice) => choice.value === nativeValue && enabled(choice));
|
|
104
|
+
setActive(index ?? (selectedIndex >= 0 ? selectedIndex : fresh.findIndex(enabled)));
|
|
105
|
+
search.current = { value: "", time: 0 };
|
|
106
|
+
setOpen(true);
|
|
107
|
+
}
|
|
108
|
+
function commit(index) {
|
|
109
|
+
const choice = choices[index], control = native.current;
|
|
110
|
+
if (!choice || !enabled(choice) || !control || trigger.current?.matches(":disabled")) {
|
|
111
|
+
setOpen(false);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (choice.value !== control.value) {
|
|
115
|
+
control.value = choice.value;
|
|
116
|
+
control.dispatchEvent(new Event("input", { bubbles: true }));
|
|
117
|
+
control.dispatchEvent(new Event("change", { bubbles: true }));
|
|
118
|
+
}
|
|
119
|
+
setOpen(false);
|
|
120
|
+
}
|
|
121
|
+
function change(event) {
|
|
122
|
+
const next = event.currentTarget.value;
|
|
123
|
+
onChange?.(event);
|
|
124
|
+
if (!current.current.controlled)
|
|
125
|
+
setSelected(next);
|
|
126
|
+
queueMicrotask(() => {
|
|
127
|
+
if (!native.current)
|
|
128
|
+
return;
|
|
129
|
+
if (current.current.controlled)
|
|
130
|
+
native.current.value = current.current.value;
|
|
131
|
+
if (native.current.validity.valid)
|
|
132
|
+
setValidation("");
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Native callbacks receive events dispatched on the actual select, never a cast button event.
|
|
136
|
+
function mirror(event) { return native.current?.dispatchEvent(event) ?? true; }
|
|
137
|
+
function navigate(delta) {
|
|
138
|
+
const fresh = readChoices();
|
|
139
|
+
if (!open) {
|
|
140
|
+
openMenu();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
let index = active;
|
|
144
|
+
for (let step = 0; step < fresh.length; step++) {
|
|
145
|
+
const next = index + delta;
|
|
146
|
+
if (next < 0 || next >= fresh.length)
|
|
147
|
+
break;
|
|
148
|
+
index = next;
|
|
149
|
+
if (enabled(fresh[index])) {
|
|
150
|
+
setActive(index);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
useBrowserLayoutEffect(() => {
|
|
156
|
+
if (!open || !menu.current || !trigger.current)
|
|
157
|
+
return;
|
|
158
|
+
const popup = menu.current, button = trigger.current;
|
|
159
|
+
if (typeof popup.showPopover === "function" && !popup.matches(":popover-open"))
|
|
160
|
+
popup.showPopover();
|
|
161
|
+
const rect = button.getBoundingClientRect();
|
|
162
|
+
const rootSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
163
|
+
const length = (name) => { const raw = getComputedStyle(button).getPropertyValue(name).trim(); return parseFloat(raw) * (raw.endsWith("rem") ? rootSize : 1); };
|
|
164
|
+
const gap = length("--flood-sys-space-2") || 0;
|
|
165
|
+
const below = Math.max(0, innerHeight - rect.bottom - gap * 2), above = Math.max(0, rect.top - gap * 2);
|
|
166
|
+
const maxHeight = Math.min(length("--flood-sys-control-md-height") * 8, Math.max(below, above));
|
|
167
|
+
const width = Math.min(rect.width, Math.max(0, innerWidth - gap * 2));
|
|
168
|
+
popup.style.width = `${width}px`;
|
|
169
|
+
popup.style.maxHeight = `${maxHeight}px`;
|
|
170
|
+
const popupStyle = getComputedStyle(popup);
|
|
171
|
+
const inset = parseFloat(popupStyle.paddingTop) + parseFloat(popupStyle.paddingBottom) + parseFloat(popupStyle.borderTopWidth) + parseFloat(popupStyle.borderBottomWidth);
|
|
172
|
+
const viewportHeight = Math.max(0, maxHeight - inset);
|
|
173
|
+
if (scrollViewport.current)
|
|
174
|
+
scrollViewport.current.style.maxHeight = `${viewportHeight}px`;
|
|
175
|
+
const height = popup.getBoundingClientRect().height;
|
|
176
|
+
const top = below >= height || below >= above ? rect.bottom + gap : Math.max(gap, rect.top - gap - height);
|
|
177
|
+
setPosition({ left: Math.max(gap, Math.min(rect.left, innerWidth - gap - width)), top, width, maxHeight, viewportHeight });
|
|
178
|
+
}, [open, choices]);
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (!open || (choices[active] && enabled(choices[active])))
|
|
181
|
+
return;
|
|
182
|
+
const selectedIndex = choices.findIndex((choice) => choice.value === currentValue && enabled(choice));
|
|
183
|
+
setActive(selectedIndex >= 0 ? selectedIndex : choices.findIndex(enabled));
|
|
184
|
+
}, [choices, open, active, currentValue]);
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!open)
|
|
187
|
+
return;
|
|
188
|
+
menu.current?.querySelector(`[data-option-index="${active}"]`)?.scrollIntoView({ block: "nearest" });
|
|
189
|
+
}, [open, active]);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!open)
|
|
192
|
+
return;
|
|
193
|
+
const outside = (event) => { if (event.target instanceof Node && !trigger.current?.contains(event.target) && !menu.current?.contains(event.target))
|
|
194
|
+
setOpen(false); };
|
|
195
|
+
const scroll = (event) => { if (event.target instanceof Node && menu.current?.contains(event.target))
|
|
196
|
+
return; setOpen(false); };
|
|
197
|
+
const resize = () => setOpen(false);
|
|
198
|
+
document.addEventListener("pointerdown", outside, true);
|
|
199
|
+
document.addEventListener("scroll", scroll, true);
|
|
200
|
+
window.addEventListener("resize", resize);
|
|
201
|
+
return () => { document.removeEventListener("pointerdown", outside, true); document.removeEventListener("scroll", scroll, true); window.removeEventListener("resize", resize); };
|
|
202
|
+
}, [open]);
|
|
203
|
+
const displayed = choices.find((choice) => choice.value === currentValue)?.label ?? "Select an option";
|
|
204
|
+
const descriptions = [describedBy, validation && validationId].filter(Boolean).join(" ") || undefined;
|
|
205
|
+
const visibleAttributes = Object.fromEntries(Object.entries(nativeProps).filter(([key]) => key.startsWith("data-") || key.startsWith("aria-")));
|
|
206
|
+
const nativeAttributes = Object.fromEntries(Object.entries(nativeProps).filter(([key]) => !key.startsWith("data-") && !key.startsWith("aria-") && !["tabIndex", "accessKey", "dir", "lang", "hidden"].includes(key)));
|
|
207
|
+
const groups = [];
|
|
208
|
+
choices.forEach((choice, index) => {
|
|
209
|
+
if (choice.hidden)
|
|
210
|
+
return;
|
|
211
|
+
const previous = groups[groups.length - 1];
|
|
212
|
+
if (previous && previous.label === choice.group)
|
|
213
|
+
previous.items.push({ choice, index });
|
|
214
|
+
else
|
|
215
|
+
groups.push({ label: choice.group, items: [{ choice, index }] });
|
|
216
|
+
});
|
|
217
|
+
const renderOption = ({ choice, index }) => _jsxs("div", { id: `${menuId}-${index}`, role: "option", "aria-selected": choice.value === currentValue, "aria-disabled": choice.disabled || undefined, className: "flood-select__option", "data-active": index === active || undefined, "data-option-index": index, onPointerDown: (event) => event.preventDefault(), onPointerMove: () => { if (enabled(choice))
|
|
218
|
+
setActive(index); }, onClick: () => { if (enabled(choice)) {
|
|
219
|
+
commit(index);
|
|
220
|
+
trigger.current?.focus();
|
|
221
|
+
} }, children: [_jsx("span", { children: choice.label }), _jsx("span", { className: "flood-select__selected", "aria-hidden": "true", children: choice.value === currentValue ? "✓" : "" })] }, `${index}-${choice.value}`);
|
|
222
|
+
return _jsxs("span", { className: `flood-select flood-select--custom flood-control--${controlSize}`, dir: nativeProps.dir, lang: nativeProps.lang, hidden: nativeProps.hidden, children: [_jsx("button", { ...visibleAttributes, ref: trigger, id: triggerId, type: "button", role: "combobox", disabled: disabled, tabIndex: nativeProps.tabIndex, accessKey: nativeProps.accessKey, dir: nativeProps.dir, lang: nativeProps.lang, hidden: nativeProps.hidden, className: ["flood-input flood-select__control", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), style: style, title: title, "aria-label": ariaLabel, "aria-labelledby": labelledBy, "aria-describedby": descriptions, "data-status": resolveFieldStatus(status, invalid || Boolean(validation), ariaInvalid), "aria-invalid": invalid || status === "error" || ariaInvalid || Boolean(validation) || undefined, "aria-required": required || undefined, "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": menuId, "aria-activedescendant": open && active >= 0 && choices[active] && !choices[active].hidden ? `${menuId}-${active}` : undefined, onFocus: (event) => { if (onFocus)
|
|
223
|
+
mirror(new FocusEvent("focusin", { bubbles: true, relatedTarget: event.relatedTarget })); }, onBlur: (event) => { if (onBlur)
|
|
224
|
+
mirror(new FocusEvent("focusout", { bubbles: true, relatedTarget: event.relatedTarget })); setOpen(false); }, onClick: () => { if (onClick && !mirror(new MouseEvent("click", { bubbles: true, cancelable: true })))
|
|
225
|
+
return; if (open)
|
|
226
|
+
setOpen(false);
|
|
227
|
+
else
|
|
228
|
+
openMenu(); }, onKeyUp: (event) => { if (onKeyUp)
|
|
229
|
+
mirror(new KeyboardEvent("keyup", { key: event.key, bubbles: true, cancelable: true })); }, onKeyDown: (event) => {
|
|
230
|
+
if (onKeyDown && !mirror(new KeyboardEvent("keydown", { key: event.key, code: event.code, bubbles: true, cancelable: true, ctrlKey: event.ctrlKey, shiftKey: event.shiftKey, altKey: event.altKey, metaKey: event.metaKey }))) {
|
|
231
|
+
event.preventDefault();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
navigate(event.key === "ArrowDown" ? 1 : -1);
|
|
237
|
+
}
|
|
238
|
+
else if (event.key === "Home" || event.key === "End") {
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
const fresh = readChoices();
|
|
241
|
+
const index = event.key === "Home" ? fresh.findIndex(enabled) : fresh.reduce((last, choice, index) => enabled(choice) ? index : last, -1);
|
|
242
|
+
if (open)
|
|
243
|
+
setActive(index);
|
|
244
|
+
else
|
|
245
|
+
openMenu(index);
|
|
246
|
+
}
|
|
247
|
+
else if (event.key === "Enter" || event.key === " ") {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
if (open)
|
|
250
|
+
commit(active);
|
|
251
|
+
else
|
|
252
|
+
openMenu();
|
|
253
|
+
}
|
|
254
|
+
else if (event.key === "Escape" && open) {
|
|
255
|
+
event.preventDefault();
|
|
256
|
+
setOpen(false);
|
|
257
|
+
}
|
|
258
|
+
else if (event.key === "Tab" && open)
|
|
259
|
+
commit(active);
|
|
260
|
+
else if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
|
261
|
+
event.preventDefault();
|
|
262
|
+
const now = Date.now();
|
|
263
|
+
const fresh = readChoices();
|
|
264
|
+
const query = (now - search.current.time > 700 ? "" : search.current.value) + event.key.toLocaleLowerCase();
|
|
265
|
+
search.current = { value: query, time: now };
|
|
266
|
+
const repeated = [...query].every((character) => character === query[0]);
|
|
267
|
+
const needle = repeated ? query[0] : query;
|
|
268
|
+
const from = repeated ? active + 1 : Math.max(0, active);
|
|
269
|
+
const index = fresh.map((_, offset) => (from + offset + fresh.length) % fresh.length).find((candidate) => enabled(fresh[candidate]) && fresh[candidate].label.toLocaleLowerCase().startsWith(needle));
|
|
270
|
+
if (!open) {
|
|
271
|
+
setChoices(fresh);
|
|
272
|
+
setOpen(true);
|
|
273
|
+
}
|
|
274
|
+
if (index !== undefined)
|
|
275
|
+
setActive(index);
|
|
276
|
+
}
|
|
277
|
+
}, children: _jsx("span", { className: "flood-select__value", children: displayed }) }), _jsx("select", { ...nativeAttributes, ref: native, id: `${triggerId}-native`, className: "flood-select__native", hidden: true, "aria-hidden": "true", tabIndex: -1, disabled: disabled, required: required, value: value, defaultValue: defaultValue, onChange: change, onFocus: onFocus, onBlur: onBlur,
|
|
278
|
+
// This bridge exists only to give the callback its native select target.
|
|
279
|
+
// Ancestors must receive the original trigger event after Select handles it.
|
|
280
|
+
onKeyDown: (event) => { onKeyDown?.(event); event.stopPropagation(); }, onKeyUp: onKeyUp, onClick: onClick, onInvalid: (event) => { onInvalid?.(event); const prevented = event.defaultPrevented; event.preventDefault(); if (!prevented) {
|
|
281
|
+
setValidation(event.currentTarget.validationMessage);
|
|
282
|
+
trigger.current?.focus();
|
|
283
|
+
} }, children: children }), open && _jsx("div", { ref: menu, id: menuId, role: "listbox", "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : labelledBy ?? triggerId, popover: "manual", className: "flood-select__menu", onPointerDown: (event) => event.preventDefault(), onClick: (event) => event.preventDefault(), style: { left: position.left, top: position.top, width: position.width, maxHeight: position.maxHeight || undefined }, children: _jsxs(OverlayScrollArea, { ref: scrollViewport, orientation: "vertical", tabIndex: -1, className: "flood-select__viewport", style: { maxHeight: position.viewportHeight || undefined }, children: [!choices.some((choice) => !choice.hidden) && _jsx("div", { className: "flood-select__empty", children: "No options" }), groups.map((group, index) => group.label ? _jsxs("div", { role: "group", "aria-label": group.label, children: [_jsx("div", { className: "flood-select__group", "aria-hidden": "true", children: group.label }), group.items.map(renderOption)] }, index) : _jsx(Fragment, { children: group.items.map(renderOption) }, index))] }) }), validation && _jsx("span", { className: "flood-select__validation", id: validationId, role: "alert", children: validation })] });
|
|
284
|
+
});
|
package/dist/styles.css
CHANGED
|
@@ -160,6 +160,8 @@
|
|
|
160
160
|
text-underline-offset: 4px;
|
|
161
161
|
text-decoration-thickness: 1px;
|
|
162
162
|
}
|
|
163
|
+
.flood-link--button { text-decoration: none; }
|
|
164
|
+
.flood-link--button:where(.flood-button--solid) { color: var(--flood-action-text); }
|
|
163
165
|
.flood-spinner {
|
|
164
166
|
width: 12px;
|
|
165
167
|
height: 12px;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flood-md/react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Original Flood components for React and Next.js.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@flood-md/tokens": "0.2.
|
|
33
|
+
"@flood-md/tokens": "0.2.3"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|