@lotics/ui 1.10.0 → 1.11.1
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 +24 -7
- package/src/alert.tsx +35 -5
- package/src/avatar.tsx +28 -3
- package/src/back_button.tsx +4 -2
- package/src/button.tsx +35 -5
- package/src/calendar/calendar_view.tsx +127 -0
- package/src/calendar/dates.ts +102 -0
- package/src/calendar/index.ts +20 -0
- package/src/calendar/layout.test.ts +103 -0
- package/src/calendar/layout.ts +142 -0
- package/src/calendar/month_view.tsx +159 -0
- package/src/calendar/time_grid_view.tsx +263 -0
- package/src/calendar/types.ts +67 -0
- package/src/checkbox_input.tsx +9 -3
- package/src/command_menu.tsx +50 -4
- package/src/dialog.tsx +1 -1
- package/src/download.ts +14 -2
- package/src/form_field.tsx +77 -25
- package/src/form_switch.tsx +22 -3
- package/src/gantt/gantt_view.tsx +145 -0
- package/src/gantt/index.ts +5 -0
- package/src/gantt/scale.test.ts +47 -0
- package/src/gantt/scale.ts +92 -0
- package/src/gantt/types.ts +51 -0
- package/src/grid/select_header_cell.tsx +1 -0
- package/src/icon.tsx +14 -8
- package/src/icon_button.tsx +10 -4
- package/src/index.css +11 -0
- package/src/kanban/constants.ts +18 -0
- package/src/kanban/default_renderers.tsx +160 -0
- package/src/kanban/drag_preview.tsx +157 -0
- package/src/kanban/index.ts +13 -0
- package/src/kanban/insert_card_zone.tsx +135 -0
- package/src/kanban/kanban_board.tsx +616 -0
- package/src/kanban/kanban_card.tsx +312 -0
- package/src/kanban/kanban_column.tsx +487 -0
- package/src/kanban/placeholders.tsx +54 -0
- package/src/kanban/types.ts +116 -0
- package/src/landmark.tsx +34 -0
- package/src/menu_button.tsx +21 -0
- package/src/menu_list_item.tsx +3 -0
- package/src/number_input.tsx +10 -1
- package/src/pill_button.tsx +1 -0
- package/src/popover.tsx +47 -2
- package/src/popover_header.tsx +4 -2
- package/src/pressable_highlight.tsx +24 -0
- package/src/radio_picker.tsx +63 -5
- package/src/section_heading.tsx +5 -3
- package/src/skip_link.tsx +46 -0
- package/src/switch.tsx +9 -1
- package/src/switch_button.tsx +3 -0
- package/src/tabs.tsx +81 -19
- package/src/text.tsx +33 -0
- package/src/text_input_field.tsx +31 -0
- package/src/tooltip.tsx +43 -6
package/src/text.tsx
CHANGED
|
@@ -22,8 +22,25 @@ export interface TextProps {
|
|
|
22
22
|
decoration?: TextDecorationLine;
|
|
23
23
|
style?: RNTextProps["style"];
|
|
24
24
|
onPress?: () => void;
|
|
25
|
+
/** ID used to target this element from `accessibilityLabelledBy` / `aria-describedby`. */
|
|
26
|
+
nativeID?: string;
|
|
27
|
+
accessibilityRole?: RNTextProps["accessibilityRole"];
|
|
28
|
+
accessibilityLabel?: string;
|
|
29
|
+
accessibilityElementsHidden?: boolean;
|
|
30
|
+
importantForAccessibility?: RNTextProps["importantForAccessibility"];
|
|
31
|
+
/**
|
|
32
|
+
* Renders as a heading of this rank (1–6): exposes `accessibilityRole="header"`
|
|
33
|
+
* and `aria-level`, giving the page a real heading outline. Appearance still
|
|
34
|
+
* comes from `size`/`weight` — `level` is the semantic rank only. Use one
|
|
35
|
+
* level-1 per page.
|
|
36
|
+
*/
|
|
37
|
+
level?: HeadingLevel;
|
|
38
|
+
"aria-live"?: "off" | "polite" | "assertive";
|
|
39
|
+
"aria-hidden"?: boolean;
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
43
|
+
|
|
27
44
|
type TextSize = "xs" | "sm" | "md" | "lg" | "xl" | "xxl";
|
|
28
45
|
type TextAlign = "left" | "right" | "center";
|
|
29
46
|
type TextDecorationLine = "underline" | "lineThrough" | "underline lineThrough";
|
|
@@ -48,6 +65,14 @@ export function Text(props: TextProps) {
|
|
|
48
65
|
transform,
|
|
49
66
|
onPress,
|
|
50
67
|
style,
|
|
68
|
+
nativeID,
|
|
69
|
+
accessibilityRole,
|
|
70
|
+
accessibilityLabel,
|
|
71
|
+
accessibilityElementsHidden,
|
|
72
|
+
importantForAccessibility,
|
|
73
|
+
level,
|
|
74
|
+
"aria-live": ariaLive,
|
|
75
|
+
"aria-hidden": ariaHidden,
|
|
51
76
|
} = props;
|
|
52
77
|
|
|
53
78
|
// On web, responsive font sizes are applied via CSS using data-text-size attribute.
|
|
@@ -57,6 +82,14 @@ export function Text(props: TextProps) {
|
|
|
57
82
|
return (
|
|
58
83
|
<RNText
|
|
59
84
|
testID={testID}
|
|
85
|
+
nativeID={nativeID}
|
|
86
|
+
accessibilityRole={level != null ? "header" : accessibilityRole}
|
|
87
|
+
accessibilityLabel={accessibilityLabel}
|
|
88
|
+
accessibilityElementsHidden={accessibilityElementsHidden}
|
|
89
|
+
importantForAccessibility={importantForAccessibility}
|
|
90
|
+
aria-level={level}
|
|
91
|
+
aria-live={ariaLive}
|
|
92
|
+
aria-hidden={ariaHidden}
|
|
60
93
|
{...webProps}
|
|
61
94
|
style={[
|
|
62
95
|
{ color: getTextColor(color) },
|
package/src/text_input_field.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import { ShortcutBadge } from "./shortcut_badge";
|
|
|
12
12
|
import { fontFamilyRegular, getInputLineHeight, getInputTextStyle } from "./text_utils";
|
|
13
13
|
import { useScreenSize } from "./use_screen_size";
|
|
14
14
|
import { useAutoGrowHeight } from "./use_auto_grow_height";
|
|
15
|
+
import { useFormField } from "./form_field";
|
|
15
16
|
import type { ShortcutDescriptor } from "./keyboard";
|
|
16
17
|
|
|
17
18
|
interface TextInputFieldProps extends RNTextInputProps {
|
|
@@ -25,6 +26,13 @@ interface TextInputFieldProps extends RNTextInputProps {
|
|
|
25
26
|
autoGrow?: boolean;
|
|
26
27
|
/** Keyboard shortcut badge shown in the right slot when the input is empty. */
|
|
27
28
|
shortcut?: string | ShortcutDescriptor;
|
|
29
|
+
/** Accessible name for the clear button. Default: "Clear". Pass a translated string from the consumer. */
|
|
30
|
+
clearLabel?: string;
|
|
31
|
+
// DOM-only ARIA attrs not declared on React Native's TextInputProps. They
|
|
32
|
+
// are forwarded verbatim to the underlying web input.
|
|
33
|
+
"aria-controls"?: string;
|
|
34
|
+
"aria-activedescendant"?: string;
|
|
35
|
+
"aria-autocomplete"?: "none" | "inline" | "list" | "both";
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
export function TextInputField(props: TextInputFieldProps) {
|
|
@@ -40,12 +48,29 @@ export function TextInputField(props: TextInputFieldProps) {
|
|
|
40
48
|
disabled,
|
|
41
49
|
autoGrow,
|
|
42
50
|
shortcut,
|
|
51
|
+
clearLabel = "Clear",
|
|
43
52
|
ref,
|
|
53
|
+
"aria-controls": ariaControls,
|
|
54
|
+
"aria-activedescendant": ariaActivedescendant,
|
|
55
|
+
"aria-autocomplete": ariaAutocomplete,
|
|
44
56
|
...inputProps
|
|
45
57
|
} = props;
|
|
46
58
|
|
|
59
|
+
// Forwarded to the DOM input on RN Web. RN's TextInput type does not declare
|
|
60
|
+
// these attrs, but react-native-web passes unknown props through to the
|
|
61
|
+
// element, which is the correct behavior for combobox patterns.
|
|
62
|
+
const webAriaAttrs: Record<string, unknown> = {};
|
|
63
|
+
if (ariaControls !== undefined) webAriaAttrs["aria-controls"] = ariaControls;
|
|
64
|
+
if (ariaActivedescendant !== undefined) webAriaAttrs["aria-activedescendant"] = ariaActivedescendant;
|
|
65
|
+
if (ariaAutocomplete !== undefined) webAriaAttrs["aria-autocomplete"] = ariaAutocomplete;
|
|
66
|
+
|
|
47
67
|
const { small } = useScreenSize();
|
|
48
68
|
const lineHeight = getInputLineHeight(small);
|
|
69
|
+
const binding = useFormField();
|
|
70
|
+
|
|
71
|
+
// Describedby chains description and error so both are read. We join them
|
|
72
|
+
// explicitly here because React Native Web does not flatten array attrs.
|
|
73
|
+
const describedBy = [binding?.descriptionId, binding?.errorId].filter(Boolean).join(" ") || undefined;
|
|
49
74
|
|
|
50
75
|
const minHeight =
|
|
51
76
|
numberOfLines && numberOfLines > 1
|
|
@@ -90,11 +115,16 @@ export function TextInputField(props: TextInputFieldProps) {
|
|
|
90
115
|
)}
|
|
91
116
|
<RNTextInput
|
|
92
117
|
{...inputProps}
|
|
118
|
+
{...webAriaAttrs}
|
|
93
119
|
ref={mergedRef}
|
|
94
120
|
value={value}
|
|
95
121
|
onChangeText={handleChangeText}
|
|
96
122
|
onFocus={inputProps.onFocus}
|
|
97
123
|
onBlur={inputProps.onBlur}
|
|
124
|
+
nativeID={binding?.inputId ?? inputProps.nativeID}
|
|
125
|
+
accessibilityLabelledBy={binding?.labelId ?? inputProps.accessibilityLabelledBy}
|
|
126
|
+
aria-describedby={describedBy}
|
|
127
|
+
aria-invalid={binding?.invalid || undefined}
|
|
98
128
|
style={[
|
|
99
129
|
styles.input,
|
|
100
130
|
getInputTextStyle(),
|
|
@@ -115,6 +145,7 @@ export function TextInputField(props: TextInputFieldProps) {
|
|
|
115
145
|
{!!clearable && !!value ? (
|
|
116
146
|
<IconButton
|
|
117
147
|
icon="x"
|
|
148
|
+
tooltip={clearLabel}
|
|
118
149
|
onPress={() => {
|
|
119
150
|
onChangeText?.("");
|
|
120
151
|
onClear?.();
|
package/src/tooltip.tsx
CHANGED
|
@@ -190,17 +190,27 @@ export function useTooltip(options?: string | UseTooltipOptions) {
|
|
|
190
190
|
const side = typeof options === "string" ? "top" : (options?.side ?? "top");
|
|
191
191
|
const offset = typeof options === "string" ? undefined : options?.offset;
|
|
192
192
|
|
|
193
|
-
const
|
|
194
|
-
(
|
|
193
|
+
const showFor = useCallback(
|
|
194
|
+
(target: unknown) => {
|
|
195
195
|
if (!text || !context) return;
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
// Tooltips are a web-only UI. `typeof` guards the native platforms where
|
|
197
|
+
// `HTMLElement` does not exist as a global, then `instanceof` narrows
|
|
198
|
+
// without an unchecked cast.
|
|
199
|
+
if (typeof HTMLElement === "undefined") return;
|
|
200
|
+
if (!(target instanceof HTMLElement)) return;
|
|
198
201
|
const rect = target.getBoundingClientRect();
|
|
199
202
|
context.show(text, rect, side, offset);
|
|
200
203
|
},
|
|
201
204
|
[text, context, side, offset],
|
|
202
205
|
);
|
|
203
206
|
|
|
207
|
+
const onMouseEnter = useCallback(
|
|
208
|
+
(e: { currentTarget: unknown }) => {
|
|
209
|
+
showFor(e.currentTarget);
|
|
210
|
+
},
|
|
211
|
+
[showFor],
|
|
212
|
+
);
|
|
213
|
+
|
|
204
214
|
const onMouseLeave = useCallback(() => {
|
|
205
215
|
if (!context) return;
|
|
206
216
|
context.hide();
|
|
@@ -211,15 +221,42 @@ export function useTooltip(options?: string | UseTooltipOptions) {
|
|
|
211
221
|
context.hide();
|
|
212
222
|
}, [context]);
|
|
213
223
|
|
|
214
|
-
//
|
|
224
|
+
// Keyboard focus must reveal the same label as hover. `:focus-visible` CSS
|
|
225
|
+
// handles the visual ring; we tie tooltip visibility to real focus so
|
|
226
|
+
// screen-reader-off keyboard users still see names.
|
|
227
|
+
//
|
|
228
|
+
// Typed as `unknown` because these handlers spread onto both
|
|
229
|
+
// `react-native-web`'s `Pressable` (expects `NativeSyntheticEvent<TargetedEvent>`)
|
|
230
|
+
// and regular DOM elements (expect `React.FocusEvent`). At runtime on web,
|
|
231
|
+
// both shapes expose `currentTarget` as an `HTMLElement`, which is all we read.
|
|
232
|
+
const onFocus = useCallback(
|
|
233
|
+
(e: { currentTarget: unknown }) => {
|
|
234
|
+
showFor(e.currentTarget);
|
|
235
|
+
},
|
|
236
|
+
[showFor],
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const onBlur = useCallback(() => {
|
|
240
|
+
if (!context) return;
|
|
241
|
+
context.hide();
|
|
242
|
+
}, [context]);
|
|
243
|
+
|
|
215
244
|
if (!text || !context) {
|
|
216
|
-
return {
|
|
245
|
+
return {
|
|
246
|
+
onMouseEnter: undefined,
|
|
247
|
+
onMouseLeave: undefined,
|
|
248
|
+
onMouseDown: undefined,
|
|
249
|
+
onFocus: undefined,
|
|
250
|
+
onBlur: undefined,
|
|
251
|
+
};
|
|
217
252
|
}
|
|
218
253
|
|
|
219
254
|
return {
|
|
220
255
|
onMouseEnter,
|
|
221
256
|
onMouseLeave,
|
|
222
257
|
onMouseDown,
|
|
258
|
+
onFocus,
|
|
259
|
+
onBlur,
|
|
223
260
|
};
|
|
224
261
|
}
|
|
225
262
|
|