@dloizides/ui-forms 1.20.0 → 1.22.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.0
4
+
5
+ KEFI-EDITOR-UI-2 "Extract stepper and upload-with-link field to @dloizides/ui-forms", extracted from kefi-web.
6
+
7
+ - New `StepperTabs`, `StepperNav` and pure `resolveStepNeighbours`: step tabs sharing the row width, back and next under the step. Generic over the step key; every string is a prop.
8
+ - New `UploadWithLinkField`: preview, an upload control slot and a link input revealed on request. The slot keeps ui-forms free of ui-media; pass `ImagePickerButton`.
9
+
3
10
  ## 1.20.0
4
11
 
5
12
  **Horizontal form fields line up (D-ALL-22 "Horizontal form fields are aligned").**
package/dist/index.d.mts CHANGED
@@ -1062,4 +1062,70 @@ declare function groupAriaChecked(state: GroupSelection): boolean | 'mixed';
1062
1062
  */
1063
1063
  declare function toggleGroupSelection<T>(selected: readonly T[], groupKeys: readonly T[], order?: readonly T[]): T[];
1064
1064
 
1065
- export { AlignToControl, type AlignToControlProps, AnchoredMenu, type AnchoredMenuColors, type AnchoredMenuProps, type ChipOption, ChipSelector, type ChipSelectorProps, type ChipSingleSelectRole, type ChipVariant, type ControlOption, DEFAULT_MAX_RECENTS, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, type DateRangeControlProps, type DateRangeValue, Field, type FieldChildContext, type FieldChildren, type FieldLabelVariant, type FieldMarker, type FieldProps, type FieldSpacing, FormActions, type FormActionsProps, FormCell, type FormCellProps, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormGrid, type FormGridProps, FormSection, type FormSectionProps, FormSwitch, type FormSwitchProps, type GroupSelection, INLINE_FIELD_ROW_MIN_WIDTH, InlineFieldRow, type InlineFieldRowLayout, type InlineFieldRowProps, type RecentValues, SelectControl, type SelectControlProps, type ThemedInput, ThemedTextInput, type ThemedTextInputProps, TypeaheadControl, type TypeaheadControlProps, type UseThemedInputOptions, controlStyles, groupAriaChecked, inlineFieldRowLayout, isUnmatched, resolveGroupSelection, suggestOptions, toggleGroupSelection, useRecentValues, useThemedInput };
1065
+ /** One step; `label` is pre-localized and shown on its tab. */
1066
+ interface StepperStep<TKey extends string | number = string> {
1067
+ key: TKey;
1068
+ label: string;
1069
+ }
1070
+ interface StepNeighbours<TKey extends string | number = string> {
1071
+ previous: StepperStep<TKey> | null;
1072
+ next: StepperStep<TKey> | null;
1073
+ }
1074
+ /** The steps either side of `current`; both null when `current` is not a step. */
1075
+ declare function resolveStepNeighbours<TKey extends string | number>(steps: readonly StepperStep<TKey>[], current: TKey): StepNeighbours<TKey>;
1076
+
1077
+ interface StepperTabsProps<TKey extends string | number = string> {
1078
+ steps: readonly StepperStep<TKey>[];
1079
+ current: TKey;
1080
+ onStep: (key: TKey) => void;
1081
+ /** Pre-localized accessibility hint for every tab. */
1082
+ stepHint: string;
1083
+ /** Row testID; each tab is `${testID}-${step.key}`. */
1084
+ testID: string;
1085
+ }
1086
+ /** A row of step tabs sharing the width; each label wraps inside its own tab. */
1087
+ declare const StepperTabs: <TKey extends string | number = string>({ steps, current, onStep, stepHint, testID, }: StepperTabsProps<TKey>) => React.ReactElement;
1088
+
1089
+ interface StepperNavLabels<TKey extends string | number = string> {
1090
+ back: string;
1091
+ backHint: string;
1092
+ /** The next button's label, built from the step it leads to. */
1093
+ next: (step: StepperStep<TKey>) => string;
1094
+ nextHint: string;
1095
+ }
1096
+ interface StepperNavProps<TKey extends string | number = string> {
1097
+ steps: readonly StepperStep<TKey>[];
1098
+ current: TKey;
1099
+ onStep: (key: TKey) => void;
1100
+ labels: StepperNavLabels<TKey>;
1101
+ /** Row testID; the buttons are `${testID}-back` and `${testID}-next`. */
1102
+ testID: string;
1103
+ }
1104
+ /** Back and next under a step: back is disabled on the first step, next is absent on the last. */
1105
+ declare const StepperNav: <TKey extends string | number = string>({ steps, current, onStep, labels, testID, }: StepperNavProps<TKey>) => React.ReactElement;
1106
+
1107
+ /** Every string the field shows; all pre-localized. */
1108
+ interface UploadWithLinkLabels {
1109
+ title: string;
1110
+ help?: string;
1111
+ pasteToggle: string;
1112
+ pasteToggleHint: string;
1113
+ link: string;
1114
+ linkHint: string;
1115
+ linkPlaceholder: string;
1116
+ }
1117
+ interface UploadWithLinkFieldProps {
1118
+ value: string | null | undefined;
1119
+ onChange: (url: string) => void;
1120
+ labels: UploadWithLinkLabels;
1121
+ /** The upload control, e.g. ui-media `ImagePickerButton`; its transport stays with the app. */
1122
+ uploadControl: React.ReactNode;
1123
+ /** The link input; the others are `${testID}-preview` and `${testID}-paste`. */
1124
+ testID: string;
1125
+ disabled?: boolean;
1126
+ previewSize?: number;
1127
+ }
1128
+ /** An image field: preview, an upload control, and a link input revealed on request. */
1129
+ declare const UploadWithLinkField: ({ value, onChange, labels, uploadControl, testID, disabled, previewSize, }: UploadWithLinkFieldProps) => React.ReactElement;
1130
+
1131
+ export { AlignToControl, type AlignToControlProps, AnchoredMenu, type AnchoredMenuColors, type AnchoredMenuProps, type ChipOption, ChipSelector, type ChipSelectorProps, type ChipSingleSelectRole, type ChipVariant, type ControlOption, DEFAULT_MAX_RECENTS, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, type DateRangeControlProps, type DateRangeValue, Field, type FieldChildContext, type FieldChildren, type FieldLabelVariant, type FieldMarker, type FieldProps, type FieldSpacing, FormActions, type FormActionsProps, FormCell, type FormCellProps, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormGrid, type FormGridProps, FormSection, type FormSectionProps, FormSwitch, type FormSwitchProps, type GroupSelection, INLINE_FIELD_ROW_MIN_WIDTH, InlineFieldRow, type InlineFieldRowLayout, type InlineFieldRowProps, type RecentValues, SelectControl, type SelectControlProps, type StepNeighbours, StepperNav, type StepperNavLabels, type StepperNavProps, type StepperStep, StepperTabs, type StepperTabsProps, type ThemedInput, ThemedTextInput, type ThemedTextInputProps, TypeaheadControl, type TypeaheadControlProps, UploadWithLinkField, type UploadWithLinkFieldProps, type UploadWithLinkLabels, type UseThemedInputOptions, controlStyles, groupAriaChecked, inlineFieldRowLayout, isUnmatched, resolveGroupSelection, resolveStepNeighbours, suggestOptions, toggleGroupSelection, useRecentValues, useThemedInput };
package/dist/index.d.ts CHANGED
@@ -1062,4 +1062,70 @@ declare function groupAriaChecked(state: GroupSelection): boolean | 'mixed';
1062
1062
  */
1063
1063
  declare function toggleGroupSelection<T>(selected: readonly T[], groupKeys: readonly T[], order?: readonly T[]): T[];
1064
1064
 
1065
- export { AlignToControl, type AlignToControlProps, AnchoredMenu, type AnchoredMenuColors, type AnchoredMenuProps, type ChipOption, ChipSelector, type ChipSelectorProps, type ChipSingleSelectRole, type ChipVariant, type ControlOption, DEFAULT_MAX_RECENTS, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, type DateRangeControlProps, type DateRangeValue, Field, type FieldChildContext, type FieldChildren, type FieldLabelVariant, type FieldMarker, type FieldProps, type FieldSpacing, FormActions, type FormActionsProps, FormCell, type FormCellProps, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormGrid, type FormGridProps, FormSection, type FormSectionProps, FormSwitch, type FormSwitchProps, type GroupSelection, INLINE_FIELD_ROW_MIN_WIDTH, InlineFieldRow, type InlineFieldRowLayout, type InlineFieldRowProps, type RecentValues, SelectControl, type SelectControlProps, type ThemedInput, ThemedTextInput, type ThemedTextInputProps, TypeaheadControl, type TypeaheadControlProps, type UseThemedInputOptions, controlStyles, groupAriaChecked, inlineFieldRowLayout, isUnmatched, resolveGroupSelection, suggestOptions, toggleGroupSelection, useRecentValues, useThemedInput };
1065
+ /** One step; `label` is pre-localized and shown on its tab. */
1066
+ interface StepperStep<TKey extends string | number = string> {
1067
+ key: TKey;
1068
+ label: string;
1069
+ }
1070
+ interface StepNeighbours<TKey extends string | number = string> {
1071
+ previous: StepperStep<TKey> | null;
1072
+ next: StepperStep<TKey> | null;
1073
+ }
1074
+ /** The steps either side of `current`; both null when `current` is not a step. */
1075
+ declare function resolveStepNeighbours<TKey extends string | number>(steps: readonly StepperStep<TKey>[], current: TKey): StepNeighbours<TKey>;
1076
+
1077
+ interface StepperTabsProps<TKey extends string | number = string> {
1078
+ steps: readonly StepperStep<TKey>[];
1079
+ current: TKey;
1080
+ onStep: (key: TKey) => void;
1081
+ /** Pre-localized accessibility hint for every tab. */
1082
+ stepHint: string;
1083
+ /** Row testID; each tab is `${testID}-${step.key}`. */
1084
+ testID: string;
1085
+ }
1086
+ /** A row of step tabs sharing the width; each label wraps inside its own tab. */
1087
+ declare const StepperTabs: <TKey extends string | number = string>({ steps, current, onStep, stepHint, testID, }: StepperTabsProps<TKey>) => React.ReactElement;
1088
+
1089
+ interface StepperNavLabels<TKey extends string | number = string> {
1090
+ back: string;
1091
+ backHint: string;
1092
+ /** The next button's label, built from the step it leads to. */
1093
+ next: (step: StepperStep<TKey>) => string;
1094
+ nextHint: string;
1095
+ }
1096
+ interface StepperNavProps<TKey extends string | number = string> {
1097
+ steps: readonly StepperStep<TKey>[];
1098
+ current: TKey;
1099
+ onStep: (key: TKey) => void;
1100
+ labels: StepperNavLabels<TKey>;
1101
+ /** Row testID; the buttons are `${testID}-back` and `${testID}-next`. */
1102
+ testID: string;
1103
+ }
1104
+ /** Back and next under a step: back is disabled on the first step, next is absent on the last. */
1105
+ declare const StepperNav: <TKey extends string | number = string>({ steps, current, onStep, labels, testID, }: StepperNavProps<TKey>) => React.ReactElement;
1106
+
1107
+ /** Every string the field shows; all pre-localized. */
1108
+ interface UploadWithLinkLabels {
1109
+ title: string;
1110
+ help?: string;
1111
+ pasteToggle: string;
1112
+ pasteToggleHint: string;
1113
+ link: string;
1114
+ linkHint: string;
1115
+ linkPlaceholder: string;
1116
+ }
1117
+ interface UploadWithLinkFieldProps {
1118
+ value: string | null | undefined;
1119
+ onChange: (url: string) => void;
1120
+ labels: UploadWithLinkLabels;
1121
+ /** The upload control, e.g. ui-media `ImagePickerButton`; its transport stays with the app. */
1122
+ uploadControl: React.ReactNode;
1123
+ /** The link input; the others are `${testID}-preview` and `${testID}-paste`. */
1124
+ testID: string;
1125
+ disabled?: boolean;
1126
+ previewSize?: number;
1127
+ }
1128
+ /** An image field: preview, an upload control, and a link input revealed on request. */
1129
+ declare const UploadWithLinkField: ({ value, onChange, labels, uploadControl, testID, disabled, previewSize, }: UploadWithLinkFieldProps) => React.ReactElement;
1130
+
1131
+ export { AlignToControl, type AlignToControlProps, AnchoredMenu, type AnchoredMenuColors, type AnchoredMenuProps, type ChipOption, ChipSelector, type ChipSelectorProps, type ChipSingleSelectRole, type ChipVariant, type ControlOption, DEFAULT_MAX_RECENTS, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, type DateRangeControlProps, type DateRangeValue, Field, type FieldChildContext, type FieldChildren, type FieldLabelVariant, type FieldMarker, type FieldProps, type FieldSpacing, FormActions, type FormActionsProps, FormCell, type FormCellProps, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormGrid, type FormGridProps, FormSection, type FormSectionProps, FormSwitch, type FormSwitchProps, type GroupSelection, INLINE_FIELD_ROW_MIN_WIDTH, InlineFieldRow, type InlineFieldRowLayout, type InlineFieldRowProps, type RecentValues, SelectControl, type SelectControlProps, type StepNeighbours, StepperNav, type StepperNavLabels, type StepperNavProps, type StepperStep, StepperTabs, type StepperTabsProps, type ThemedInput, ThemedTextInput, type ThemedTextInputProps, TypeaheadControl, type TypeaheadControlProps, UploadWithLinkField, type UploadWithLinkFieldProps, type UploadWithLinkLabels, type UseThemedInputOptions, controlStyles, groupAriaChecked, inlineFieldRowLayout, isUnmatched, resolveGroupSelection, resolveStepNeighbours, suggestOptions, toggleGroupSelection, useRecentValues, useThemedInput };
package/dist/index.js CHANGED
@@ -1437,12 +1437,18 @@ var FormSwitch = ({
1437
1437
  )
1438
1438
  );
1439
1439
  };
1440
+
1441
+ // src/FormCheckbox/checkboxColors.ts
1442
+ var checkboxBoxColors = (colors, brand, checked) => checked ? { backgroundColor: brand, borderColor: brand } : { backgroundColor: "transparent", borderColor: colors.textSecondary };
1440
1443
  var ACTIVATION_KEYS = ["Enter", " ", "Spacebar"];
1441
1444
  var HINT_FONT_SIZE2 = 12;
1442
1445
  var BOX_SIZE = 20;
1443
1446
  var BOX_RADIUS = 5;
1444
1447
  var CHECK_FONT_SIZE = 14;
1445
1448
  var DISABLED_OPACITY = 0.5;
1449
+ var FOCUS_RING_WIDTH = 2;
1450
+ var WEB_NO_DEFAULT_OUTLINE = { outlineStyle: "none" };
1451
+ var FOCUS_RING_INSET = -6;
1446
1452
  var styles9 = reactNative.StyleSheet.create({
1447
1453
  container: {
1448
1454
  flexDirection: "row",
@@ -1467,6 +1473,15 @@ var styles9 = reactNative.StyleSheet.create({
1467
1473
  // Nudge the box to sit on the label's first text line.
1468
1474
  marginTop: 1
1469
1475
  },
1476
+ focusRing: {
1477
+ position: "absolute",
1478
+ top: FOCUS_RING_INSET,
1479
+ left: FOCUS_RING_INSET,
1480
+ right: FOCUS_RING_INSET,
1481
+ bottom: FOCUS_RING_INSET,
1482
+ borderWidth: FOCUS_RING_WIDTH,
1483
+ borderRadius: BOX_RADIUS - FOCUS_RING_INSET
1484
+ },
1470
1485
  check: {
1471
1486
  fontSize: CHECK_FONT_SIZE,
1472
1487
  fontWeight: "700",
@@ -1502,13 +1517,15 @@ var FormCheckbox = ({
1502
1517
  const hintText = typeof hint === "string" && hint !== "" ? hint : description;
1503
1518
  const hasHint = typeof hintText === "string" && hintText !== "";
1504
1519
  const { hintId } = useFieldIds();
1520
+ const [focused, setFocused] = React2__default.default.useState(false);
1505
1521
  const themeStyles = React2__default.default.useMemo(() => {
1506
- const boxOn = { backgroundColor: brand, borderColor: brand };
1507
- const boxOff = { backgroundColor: "transparent", borderColor: colors.border };
1522
+ const boxOn = checkboxBoxColors(colors, brand, true);
1523
+ const boxOff = checkboxBoxColors(colors, brand, false);
1524
+ const ring = { borderColor: brand };
1508
1525
  const labelStyle = { color: colors.text };
1509
1526
  const hintStyle = { color: colors.textSecondary };
1510
- return { boxOn, boxOff, label: labelStyle, hint: hintStyle };
1511
- }, [brand, colors.border, colors.text, colors.textSecondary]);
1527
+ return { boxOn, boxOff, ring, label: labelStyle, hint: hintStyle };
1528
+ }, [brand, colors]);
1512
1529
  const toggle = () => {
1513
1530
  const next = !value;
1514
1531
  if (onValueChange) onValueChange(next);
@@ -1532,18 +1549,24 @@ var FormCheckbox = ({
1532
1549
  "aria-checked": value,
1533
1550
  "aria-describedby": hasHint ? hintId : void 0,
1534
1551
  disabled,
1552
+ onBlur: () => setFocused(false),
1553
+ onFocus: () => setFocused(true),
1535
1554
  onPress: toggle,
1536
1555
  role: "checkbox",
1537
1556
  style: [
1538
1557
  styles9.container,
1539
1558
  hasHint ? null : styles9.centered,
1540
1559
  containerStyle,
1541
- disabled ? { opacity: DISABLED_OPACITY } : null
1560
+ disabled ? { opacity: DISABLED_OPACITY } : null,
1561
+ reactNative.Platform.OS === "web" ? WEB_NO_DEFAULT_OUTLINE : null
1542
1562
  ],
1543
1563
  testID,
1544
1564
  ...keyboardProps,
1545
1565
  children: [
1546
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles9.box, value ? themeStyles.boxOn : themeStyles.boxOff], children: value ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null }),
1566
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles9.box, value ? themeStyles.boxOn : themeStyles.boxOff], testID: `${testID}-box`, children: [
1567
+ focused ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none", style: [styles9.focusRing, themeStyles.ring], testID: `${testID}-focus-ring` }) : null,
1568
+ value ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null
1569
+ ] }),
1547
1570
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles9.textColumn, children: [
1548
1571
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.label, themeStyles.label], children: label }),
1549
1572
  hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { nativeID: hintId, style: [styles9.hint, themeStyles.hint], children: hintText }) : null
@@ -1837,6 +1860,192 @@ function toggleGroupSelection(selected, groupKeys, order) {
1837
1860
  });
1838
1861
  return order === void 0 ? merged : order.filter((key) => merged.includes(key));
1839
1862
  }
1863
+ var ROW_GAP = 4;
1864
+ var TAB_PADDING = 4;
1865
+ var INDICATOR_WIDTH = 2;
1866
+ var TAB_FONT_SIZE = 12;
1867
+ var styles10 = reactNative.StyleSheet.create({
1868
+ row: { flexDirection: "row", flexWrap: "nowrap", gap: ROW_GAP },
1869
+ tab: {
1870
+ borderBottomWidth: INDICATOR_WIDTH,
1871
+ flexBasis: 0,
1872
+ flexGrow: 1,
1873
+ flexShrink: 1,
1874
+ justifyContent: "center",
1875
+ minHeight: a11y.MIN_TOUCH_TARGET_PX,
1876
+ minWidth: 0,
1877
+ paddingHorizontal: TAB_PADDING
1878
+ },
1879
+ text: { fontSize: TAB_FONT_SIZE, textAlign: "center" },
1880
+ textSelected: { fontWeight: "700" }
1881
+ });
1882
+ var StepperTab = ({
1883
+ step,
1884
+ selected,
1885
+ hint,
1886
+ onStep,
1887
+ testID
1888
+ }) => {
1889
+ const { theme } = uiFeedback.useUi();
1890
+ const ring = uiButtons.useFocusRing();
1891
+ const indicator = selected ? theme.palette.primary["500"] : theme.colors.border;
1892
+ const webKeyProps = {
1893
+ onKeyDown: (event) => {
1894
+ if (event.key !== " ") return;
1895
+ event.preventDefault?.();
1896
+ onStep(step.key);
1897
+ }
1898
+ };
1899
+ return /* @__PURE__ */ jsxRuntime.jsx(
1900
+ reactNative.Pressable,
1901
+ {
1902
+ accessibilityHint: hint,
1903
+ accessibilityLabel: step.label,
1904
+ accessibilityRole: "tab",
1905
+ accessibilityState: { selected },
1906
+ "aria-selected": selected,
1907
+ style: [styles10.tab, { borderBottomColor: indicator }, ring.ringStyle],
1908
+ testID,
1909
+ onBlur: ring.onBlur,
1910
+ onFocus: ring.onFocus,
1911
+ onPress: () => onStep(step.key),
1912
+ ...webKeyProps,
1913
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.text, { color: theme.colors.text }, selected ? styles10.textSelected : void 0], children: step.label })
1914
+ }
1915
+ );
1916
+ };
1917
+ var StepperTabs = ({
1918
+ steps,
1919
+ current,
1920
+ onStep,
1921
+ stepHint,
1922
+ testID
1923
+ }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { accessibilityRole: "tablist", style: styles10.row, testID, children: steps.map((step) => /* @__PURE__ */ jsxRuntime.jsx(
1924
+ StepperTab,
1925
+ {
1926
+ hint: stepHint,
1927
+ selected: step.key === current,
1928
+ step,
1929
+ testID: `${testID}-${step.key}`,
1930
+ onStep
1931
+ },
1932
+ step.key
1933
+ )) });
1934
+
1935
+ // src/Stepper/stepperModel.ts
1936
+ function resolveStepNeighbours(steps, current) {
1937
+ const index = steps.findIndex((step) => step.key === current);
1938
+ if (index < 0) return { previous: null, next: null };
1939
+ return { previous: steps[index - 1] ?? null, next: steps[index + 1] ?? null };
1940
+ }
1941
+ var ROW_GAP2 = 8;
1942
+ var styles11 = reactNative.StyleSheet.create({
1943
+ cell: { flex: 1, minWidth: 0 },
1944
+ row: { flexDirection: "row", gap: ROW_GAP2 }
1945
+ });
1946
+ var StepperNav = ({
1947
+ steps,
1948
+ current,
1949
+ onStep,
1950
+ labels,
1951
+ testID
1952
+ }) => {
1953
+ const { previous, next } = resolveStepNeighbours(steps, current);
1954
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles11.row, testID, children: [
1955
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles11.cell, children: /* @__PURE__ */ jsxRuntime.jsx(
1956
+ uiButtons.Button,
1957
+ {
1958
+ fullWidth: true,
1959
+ accessibilityHint: labels.backHint,
1960
+ accessibilityLabel: labels.back,
1961
+ autoLoading: false,
1962
+ disabled: previous === null,
1963
+ label: labels.back,
1964
+ testID: `${testID}-back`,
1965
+ variant: "outline",
1966
+ onPress: () => {
1967
+ if (previous !== null) onStep(previous.key);
1968
+ }
1969
+ }
1970
+ ) }),
1971
+ next === null ? null : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles11.cell, children: /* @__PURE__ */ jsxRuntime.jsx(
1972
+ uiButtons.Button,
1973
+ {
1974
+ fullWidth: true,
1975
+ accessibilityHint: labels.nextHint,
1976
+ accessibilityLabel: labels.next(next),
1977
+ autoLoading: false,
1978
+ label: labels.next(next),
1979
+ testID: `${testID}-next`,
1980
+ onPress: () => onStep(next.key)
1981
+ }
1982
+ ) })
1983
+ ] });
1984
+ };
1985
+ var DEFAULT_PREVIEW_PX = 48;
1986
+ var PREVIEW_RADIUS = 8;
1987
+ var GAP = 8;
1988
+ var TITLE_FONT_SIZE = 14;
1989
+ var HELP_FONT_SIZE = 13;
1990
+ var styles12 = reactNative.StyleSheet.create({
1991
+ help: { flex: 1, fontSize: HELP_FONT_SIZE },
1992
+ root: { gap: GAP },
1993
+ tile: { alignItems: "center", flexDirection: "row", gap: GAP },
1994
+ title: { fontSize: TITLE_FONT_SIZE, fontWeight: "700" },
1995
+ toggle: { alignSelf: "flex-start", justifyContent: "center", minHeight: a11y.MIN_TOUCH_TARGET_PX },
1996
+ toggleText: { fontSize: TITLE_FONT_SIZE, fontWeight: "700", textDecorationLine: "underline" }
1997
+ });
1998
+ var UploadWithLinkField = ({
1999
+ value,
2000
+ onChange,
2001
+ labels,
2002
+ uploadControl,
2003
+ testID,
2004
+ disabled = false,
2005
+ previewSize = DEFAULT_PREVIEW_PX
2006
+ }) => {
2007
+ const { theme } = uiFeedback.useUi();
2008
+ const ring = uiButtons.useFocusRing();
2009
+ const url = value ?? "";
2010
+ const [pasting, setPasting] = React2.useState(url !== "");
2011
+ const previewBox = { borderRadius: PREVIEW_RADIUS, height: previewSize, width: previewSize };
2012
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles12.root, children: [
2013
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles12.title, { color: theme.colors.text }], children: labels.title }),
2014
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles12.tile, children: [
2015
+ url === "" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [previewBox, { backgroundColor: theme.colors.border }] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Image, { accessible: false, source: { uri: url }, style: previewBox, testID: `${testID}-preview` }),
2016
+ labels.help === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles12.help, { color: theme.colors.text }], children: labels.help })
2017
+ ] }),
2018
+ uploadControl,
2019
+ /* @__PURE__ */ jsxRuntime.jsx(
2020
+ reactNative.Pressable,
2021
+ {
2022
+ accessibilityHint: labels.pasteToggleHint,
2023
+ accessibilityLabel: labels.pasteToggle,
2024
+ accessibilityRole: "button",
2025
+ accessibilityState: { expanded: pasting },
2026
+ "aria-expanded": pasting,
2027
+ style: [styles12.toggle, ring.ringStyle],
2028
+ testID: `${testID}-paste`,
2029
+ onBlur: ring.onBlur,
2030
+ onFocus: ring.onFocus,
2031
+ onPress: () => setPasting(!pasting),
2032
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles12.toggleText, { color: theme.colors.text }], children: labels.pasteToggle })
2033
+ }
2034
+ ),
2035
+ pasting ? /* @__PURE__ */ jsxRuntime.jsx(
2036
+ FormField,
2037
+ {
2038
+ accessibilityHint: labels.linkHint,
2039
+ editable: !disabled,
2040
+ label: labels.link,
2041
+ placeholder: labels.linkPlaceholder,
2042
+ testID,
2043
+ value: url,
2044
+ onChangeText: onChange
2045
+ }
2046
+ ) : null
2047
+ ] });
2048
+ };
1840
2049
 
1841
2050
  exports.AlignToControl = AlignToControl;
1842
2051
  exports.AnchoredMenu = AnchoredMenu;
@@ -1857,13 +2066,17 @@ exports.FormSwitch = FormSwitch;
1857
2066
  exports.INLINE_FIELD_ROW_MIN_WIDTH = INLINE_FIELD_ROW_MIN_WIDTH;
1858
2067
  exports.InlineFieldRow = InlineFieldRow;
1859
2068
  exports.SelectControl = SelectControl;
2069
+ exports.StepperNav = StepperNav;
2070
+ exports.StepperTabs = StepperTabs;
1860
2071
  exports.ThemedTextInput = ThemedTextInput;
1861
2072
  exports.TypeaheadControl = TypeaheadControl;
2073
+ exports.UploadWithLinkField = UploadWithLinkField;
1862
2074
  exports.controlStyles = controlStyles;
1863
2075
  exports.groupAriaChecked = groupAriaChecked;
1864
2076
  exports.inlineFieldRowLayout = inlineFieldRowLayout;
1865
2077
  exports.isUnmatched = isUnmatched;
1866
2078
  exports.resolveGroupSelection = resolveGroupSelection;
2079
+ exports.resolveStepNeighbours = resolveStepNeighbours;
1867
2080
  exports.suggestOptions = suggestOptions;
1868
2081
  exports.toggleGroupSelection = toggleGroupSelection;
1869
2082
  exports.useRecentValues = useRecentValues;