@swan-io/lake 18.1.1 → 18.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { StyleSheet, unstable_createElement, View, } from "react-native";
|
|
|
4
4
|
import { match } from "ts-pattern";
|
|
5
5
|
import { commonStyles } from "../constants/commonStyles";
|
|
6
6
|
import { colors, fonts, spacings, texts } from "../constants/design";
|
|
7
|
+
import { getLabelId } from "../utils/a11y";
|
|
7
8
|
import { isNotNullish } from "../utils/nullish";
|
|
8
9
|
import { Box } from "./Box";
|
|
9
10
|
import { LakeText } from "./LakeText";
|
|
@@ -44,7 +45,7 @@ export const LakeLabel = ({ label, optionalLabel, description, extra, readOnly =
|
|
|
44
45
|
target === null || target === void 0 ? void 0 : target.focus();
|
|
45
46
|
}, [id]);
|
|
46
47
|
const renderActions = () => isNotNullish(actions) && (_jsxs(_Fragment, { children: [_jsx(Space, { width: 16 }), actionsAlign === "content" && isFormLabel ? (_jsx(View, { style: styles.topActions, children: actions })) : (actions)] }));
|
|
47
|
-
return (_jsxs(Box, { style: [styles.container, style], direction: "row", alignItems: "center", children: [_jsxs(View, { style: commonStyles.fill, children: [_jsxs(Box, { direction: "row", justifyContent: "spaceBetween", alignItems: "center", children: [_jsxs(Box, { direction: "row", alignItems: "center", shrink: 1, children: [isFormLabel ? (_jsxs(Box, { shrink: 1, children: [_jsxs(Label, { onClick: onClick, htmlFor: id, style: [styles.label, readOnly && { color: readOnlyColor }], children: [label, optionalLabel != null && (_jsx(LakeText, { color: colors.gray[400], style: styles.optionalLabel, children: ` - ${optionalLabel}` }))] }), description != null && (_jsxs(_Fragment, { children: [_jsx(LakeText, { variant: "smallRegular", children: description }), _jsx(Space, { height: 8 })] }))] })) : (_jsxs(LakeText, { variant: "medium", color: readOnlyColor, children: [label, optionalLabel != null && (_jsx(LakeText, { color: colors.gray[400], style: styles.optionalLabel, children: ` - ${optionalLabel}` }))] })), isNotNullish(extra) && extra()] }), isNotNullish(help) && (_jsxs(_Fragment, { children: [_jsx(Space, { width: 16 }), help] }))] }), _jsx(Space, { height: match(type)
|
|
48
|
+
return (_jsxs(Box, { style: [styles.container, style], direction: "row", alignItems: "center", children: [_jsxs(View, { style: commonStyles.fill, children: [_jsxs(Box, { direction: "row", justifyContent: "spaceBetween", alignItems: "center", children: [_jsxs(Box, { direction: "row", alignItems: "center", shrink: 1, children: [isFormLabel ? (_jsxs(Box, { shrink: 1, children: [_jsxs(Label, { id: getLabelId(id), onClick: onClick, htmlFor: id, style: [styles.label, readOnly && { color: readOnlyColor }], children: [label, optionalLabel != null && (_jsx(LakeText, { color: colors.gray[400], style: styles.optionalLabel, children: ` - ${optionalLabel}` }))] }), description != null && (_jsxs(_Fragment, { children: [_jsx(LakeText, { variant: "smallRegular", children: description }), _jsx(Space, { height: 8 })] }))] })) : (_jsxs(LakeText, { variant: "medium", color: readOnlyColor, children: [label, optionalLabel != null && (_jsx(LakeText, { color: colors.gray[400], style: styles.optionalLabel, children: ` - ${optionalLabel}` }))] })), isNotNullish(extra) && extra()] }), isNotNullish(help) && (_jsxs(_Fragment, { children: [_jsx(Space, { width: 16 }), help] }))] }), _jsx(Space, { height: match(type)
|
|
48
49
|
.returnType()
|
|
49
50
|
.with("formSmall", "viewSmall", () => 4)
|
|
50
51
|
.with("form", "view", () => 8)
|
|
@@ -6,7 +6,7 @@ import { commonStyles } from "../constants/commonStyles";
|
|
|
6
6
|
import { backgroundColor, colors, invariantColors, radii, shadows, spacings, texts, } from "../constants/design";
|
|
7
7
|
import { useBoolean } from "../hooks/useBoolean";
|
|
8
8
|
import { useMergeRefs } from "../hooks/useMergeRefs";
|
|
9
|
-
import { getFocusableElements } from "../utils/a11y";
|
|
9
|
+
import { getFocusableElements, getLabelId } from "../utils/a11y";
|
|
10
10
|
import { setHighlightApi } from "../utils/highlights";
|
|
11
11
|
import { isNotNullish, isNullishOrEmpty } from "../utils/nullish";
|
|
12
12
|
import { Box } from "./Box";
|
|
@@ -217,7 +217,7 @@ export const LakeSelect = ({ ref, title, items, valueStyle, size, color = "curre
|
|
|
217
217
|
}, [filter, hasSearch]);
|
|
218
218
|
const ListHeaderComponent = useMemo(() => (_jsxs(Box, { direction: "row", alignItems: "center", style: styles.filterContainer, children: [_jsx(TextInput, { autoComplete: "off", inputMode: "search", multiline: false, rows: 1, onChangeText: filterValue => setFilter(filterValue), placeholder: searchPlaceholder, value: filter, onFocus: setFilterFocused.on, onBlur: setFilterFocused.off, style: [styles.filterInput, filterFocused && styles.filterFocused] }), _jsx(Icon, { name: "search-filled", color: colors[color].primary, size: 20, style: styles.searchIcon })] })), [filter, filterFocused, setFilterFocused, searchPlaceholder, color]);
|
|
219
219
|
const ListEmptyComponent = (_jsxs(Box, { justifyContent: "center", alignItems: "center", style: styles.emptyList, children: [_jsx(Icon, { name: "clipboard-search-regular", size: 24, color: colors.gray.primary }), _jsx(Space, { height: 8 }), _jsx(Text, { style: styles.emptyListText, children: t("common.noResult") })] }));
|
|
220
|
-
return (_jsxs(View, { style: commonStyles.fill, children: [_jsx(Pressable, { id: id, ref: mergedRef, "aria-haspopup": "listbox", role: "button", "aria-expanded": visible, disabled: readOnly || disabled, style: ({ focused, hovered, pressed }) => [
|
|
220
|
+
return (_jsxs(View, { style: commonStyles.fill, children: [_jsx(Pressable, { id: id, ref: mergedRef, "aria-labelledby": id != null ? `${getLabelId(id)} ${id}` : undefined, "aria-haspopup": "listbox", role: "button", "aria-expanded": visible, disabled: readOnly || disabled, style: ({ focused, hovered, pressed }) => [
|
|
221
221
|
mode === "normal" ? styles.normal : styles.borderless,
|
|
222
222
|
size === "small" && styles.small,
|
|
223
223
|
(disabled || readOnly) && styles.inputDisabled,
|
|
@@ -4,6 +4,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
4
4
|
import { Pressable, StyleSheet, Text, TextInput, View, } from "react-native";
|
|
5
5
|
import { backgroundColor, colors, radii, shadows, texts } from "../constants/design";
|
|
6
6
|
import { useBoolean } from "../hooks/useBoolean";
|
|
7
|
+
import { getLabelId } from "../utils/a11y";
|
|
7
8
|
import { groupBy } from "../utils/array";
|
|
8
9
|
import { isNotNullish, isNotNullishOrEmpty } from "../utils/nullish";
|
|
9
10
|
import { safeSplitAround } from "../utils/string";
|
|
@@ -196,7 +197,7 @@ export const MultiSelect = ({ color = "gray", disabled = false, emptyResultText,
|
|
|
196
197
|
}, [filteredItems]);
|
|
197
198
|
const ListHeaderComponent = useMemo(() => (_jsxs(Box, { direction: "row", alignItems: "center", style: styles.filterContainer, children: [_jsx(TextInput, { autoComplete: "off", inputMode: "search", multiline: false, rows: 1, onChangeText: filterValue => setFilter(filterValue), placeholder: filterPlaceholder, value: filter, onFocus: setFilterFocused.on, onBlur: setFilterFocused.off, style: [styles.filterInput, filterFocused && styles.filterFocused] }), _jsx(Icon, { name: "search-filled", color: colors[color].primary, size: 20, style: styles.searchIcon })] })), [filter, filterFocused, setFilterFocused, filterPlaceholder, color]);
|
|
198
199
|
const ListEmptyComponent = useMemo(() => (_jsxs(Box, { justifyContent: "center", alignItems: "center", style: styles.emptyList, children: [_jsx(Icon, { name: "clipboard-search-regular", size: 24, color: colors.gray.primary }), isNotNullishOrEmpty(emptyResultText) && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 8 }), _jsx(Text, { style: styles.emptyListText, children: emptyResultText })] }))] })), [emptyResultText]);
|
|
199
|
-
return (_jsxs(View, { style: style, children: [_jsxs(Pressable, { id: id, ref: inputRef, role: "button", "aria-haspopup": "listbox", "aria-expanded": visible, disabled: disabled, onPress: open, style: ({ hovered, focused }) => [
|
|
200
|
+
return (_jsxs(View, { style: style, children: [_jsxs(Pressable, { id: id, ref: inputRef, "aria-labelledby": id != null ? `${getLabelId(id)} ${id}` : undefined, role: "button", "aria-haspopup": "listbox", "aria-expanded": visible, disabled: disabled, onPress: open, style: ({ hovered, focused }) => [
|
|
200
201
|
styles.base,
|
|
201
202
|
hovered && styles.hovered,
|
|
202
203
|
(focused || visible) && styles.focused,
|
package/src/utils/a11y.d.ts
CHANGED
package/src/utils/a11y.js
CHANGED
|
@@ -16,3 +16,6 @@ const FOCUSABLE_SELECTOR = [
|
|
|
16
16
|
export const getFocusableElements = (rootNode = document.documentElement, includeRoot = true) => {
|
|
17
17
|
return (includeRoot && rootNode.matches(FOCUSABLE_SELECTOR) ? [rootNode] : []).concat(Array.from(rootNode.querySelectorAll(FOCUSABLE_SELECTOR)));
|
|
18
18
|
};
|
|
19
|
+
// `<label for>` doesn't apply to elements that aren't labelable (like a `div` with `role="button"`),
|
|
20
|
+
// so LakeLabel also gives its label this id for fields to reference with `aria-labelledby`
|
|
21
|
+
export const getLabelId = (fieldId) => `${fieldId}-label`;
|