@dloizides/ui-forms 1.1.0 → 1.3.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 +25 -0
- package/dist/index.d.mts +65 -3
- package/dist/index.d.ts +65 -3
- package/dist/index.js +245 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +245 -63
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
- `ChipSelector` gains a `variant` prop (`'solid' | 'outline'`, default `'solid'`). `solid` is the
|
|
6
|
+
original filled-pill look (selected = solid brand fill, white ink) — existing consumers
|
|
7
|
+
(erevna / katalogos / kefi) render **identically**, no change. `outline` is the AML v1 console
|
|
8
|
+
look: an outlined pill on a muted fill where the **selected** state is a subtle tinted-outline
|
|
9
|
+
(brand border + brand ink + a low-alpha brand wash), NOT a solid fill; on web, hover lifts the
|
|
10
|
+
border + ink to the brand colour. Fully theme-driven (brand colour + alpha, border/text from the
|
|
11
|
+
theme), so it re-themes per tenant. Additive + backward-compatible.
|
|
12
|
+
|
|
13
|
+
## 1.2.0
|
|
14
|
+
|
|
15
|
+
- Add `ThemedTextInput` + `useThemedInput` — the shared "readable + lively" text-input treatment,
|
|
16
|
+
driven entirely by the active theme (re-themes per tenant, no hard-coded hex). At rest: dark
|
|
17
|
+
readable text on a subtle off-white surface with a soft border. On hover (web): the border
|
|
18
|
+
strengthens a touch. On focus: the border becomes the brand primary, the background brightens to
|
|
19
|
+
the plain surface (white), and a soft 3px brand-tinted focus ring fades in. The animation is a
|
|
20
|
+
pure GPU-cheap CSS transition on `border-color`/`background-color`/`box-shadow` (the ring is a
|
|
21
|
+
`box-shadow`, so no layout/reflow) — NO `Animated`/`LayoutAnimation`/JS timers. Web-only style
|
|
22
|
+
keys and the mouse-hover handlers are guarded behind `Platform.OS === 'web'`, so it stays
|
|
23
|
+
native-safe (native applies the state-driven colours instantly).
|
|
24
|
+
- `FormField` now renders `ThemedTextInput`, so every field gets the treatment for free; its label
|
|
25
|
+
uses `textSecondary` at 13px/600 for readability. Props/behaviour (label, hint, error, testID,
|
|
26
|
+
a11y, `onFocus`/`onBlur` pass-through) are backward-compatible.
|
|
27
|
+
|
|
3
28
|
## 1.1.0
|
|
4
29
|
|
|
5
30
|
- Add `FormCheckbox` — a native-safe (`Pressable`-based, RN-web + native) themed checkbox: a rounded
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { TextInputProps, ViewStyle } from 'react-native';
|
|
2
|
+
import { TextInputProps, ViewStyle, TextInput, TextStyle } from 'react-native';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Reusable form field — labelled text input with optional required mark + error.
|
|
6
|
+
*
|
|
7
|
+
* The input itself is the shared `ThemedTextInput` primitive, so every FormField gets the
|
|
8
|
+
* readable + lively treatment (dark text, subtle off-white rest background, brand focus
|
|
9
|
+
* animation with a soft ring) for free, driven by the active theme.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
interface FormFieldProps extends Omit<TextInputProps, 'style'> {
|
|
@@ -13,10 +17,66 @@ interface FormFieldProps extends Omit<TextInputProps, 'style'> {
|
|
|
13
17
|
}
|
|
14
18
|
declare const FormField: ({ label, required, error, containerStyle, ...textInputProps }: FormFieldProps) => React.ReactElement;
|
|
15
19
|
|
|
20
|
+
/**
|
|
21
|
+
* `ThemedTextInput` — the shared, readable + lively text-input primitive. A thin `<TextInput>`
|
|
22
|
+
* wrapper that applies the `useThemedInput` treatment (dark readable text, subtle off-white rest
|
|
23
|
+
* background, and the brand focus animation: indigo border + white background + soft 3px ring,
|
|
24
|
+
* eased over .14s via a GPU-cheap CSS transition on web; instant, native-safe off web).
|
|
25
|
+
*
|
|
26
|
+
* It is the ONE source of truth for the input look: `FormField` renders it, and raw filter inputs
|
|
27
|
+
* (search / country / year / date fallbacks) use it directly instead of forking the styles.
|
|
28
|
+
*
|
|
29
|
+
* Every existing `TextInputProps` (value, placeholder, onChangeText, testID, accessibility…) is
|
|
30
|
+
* forwarded untouched; `onFocus`/`onBlur` you pass are composed with the internal focus state.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
interface ThemedTextInputProps extends TextInputProps {
|
|
34
|
+
/** Render with the error border colour (matches `FormField`'s error treatment). */
|
|
35
|
+
hasError?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare const ThemedTextInput: React.ForwardRefExoticComponent<ThemedTextInputProps & React.RefAttributes<TextInput>>;
|
|
38
|
+
|
|
39
|
+
/** The exact focus/blur event types of the installed `react-native` `TextInput` (version-safe). */
|
|
40
|
+
type FocusEvent = Parameters<NonNullable<TextInputProps['onFocus']>>[0];
|
|
41
|
+
type BlurEvent = Parameters<NonNullable<TextInputProps['onBlur']>>[0];
|
|
42
|
+
interface UseThemedInputOptions {
|
|
43
|
+
/** Render with the error border colour (the brand focus border is suppressed while errored). */
|
|
44
|
+
hasError?: boolean;
|
|
45
|
+
/** Consumer focus handler — composed with the internal focus-state toggle. */
|
|
46
|
+
onFocus?: (e: FocusEvent) => void;
|
|
47
|
+
/** Consumer blur handler — composed with the internal focus-state toggle. */
|
|
48
|
+
onBlur?: (e: BlurEvent) => void;
|
|
49
|
+
}
|
|
50
|
+
interface ThemedInput {
|
|
51
|
+
/** The themed input style for the current focus/hover state (memoised). Merge AFTER your box style. */
|
|
52
|
+
style: TextStyle;
|
|
53
|
+
/** A readable mid-grey placeholder colour from the theme. */
|
|
54
|
+
placeholderTextColor: string;
|
|
55
|
+
isFocused: boolean;
|
|
56
|
+
isHovered: boolean;
|
|
57
|
+
/** Spread/attach onto the `<TextInput>` to drive the focus treatment. */
|
|
58
|
+
focusBind: {
|
|
59
|
+
onFocus: (e: FocusEvent) => void;
|
|
60
|
+
onBlur: (e: BlurEvent) => void;
|
|
61
|
+
};
|
|
62
|
+
/** Web-only mouse-hover handlers, pre-typed for `<TextInput>` (an empty object on native). */
|
|
63
|
+
hoverBind: TextInputProps;
|
|
64
|
+
}
|
|
65
|
+
declare function useThemedInput(options?: UseThemedInputOptions): ThemedInput;
|
|
66
|
+
|
|
16
67
|
/**
|
|
17
68
|
* Chip/pill selector for selecting one or multiple options.
|
|
69
|
+
*
|
|
70
|
+
* Two visual variants (both fully theme-driven):
|
|
71
|
+
* - `solid` (default) — the original filled-pill look: selected = solid primary fill with
|
|
72
|
+
* white ink. Unchanged, so existing consumers (erevna / katalogos / kefi) render identically.
|
|
73
|
+
* - `outline` — the AML v1 console look: an outlined pill on a muted fill; selected = a subtle
|
|
74
|
+
* tinted-outline (primary border + primary ink + a low-alpha primary wash), NOT a solid fill.
|
|
75
|
+
* Hover (web) lifts the border/ink to the primary colour.
|
|
18
76
|
*/
|
|
19
77
|
|
|
78
|
+
/** Visual variant. `solid` = filled selected pill (default); `outline` = v1 tinted-outline. */
|
|
79
|
+
type ChipVariant = 'solid' | 'outline';
|
|
20
80
|
interface ChipOption<T> {
|
|
21
81
|
value: T;
|
|
22
82
|
label: string;
|
|
@@ -28,9 +88,11 @@ interface ChipSelectorProps<T> {
|
|
|
28
88
|
onChange: (value: T) => void;
|
|
29
89
|
multiple?: boolean;
|
|
30
90
|
disabled?: boolean;
|
|
91
|
+
/** Visual variant. `solid` (default) = filled selected pill; `outline` = v1 tinted-outline. */
|
|
92
|
+
variant?: ChipVariant;
|
|
31
93
|
containerStyle?: ViewStyle;
|
|
32
94
|
}
|
|
33
|
-
declare const ChipSelector: <T extends string | number>({ label, options, value, onChange, multiple, disabled, containerStyle, }: ChipSelectorProps<T>) => React.ReactElement;
|
|
95
|
+
declare const ChipSelector: <T extends string | number>({ label, options, value, onChange, multiple, disabled, variant, containerStyle, }: ChipSelectorProps<T>) => React.ReactElement;
|
|
34
96
|
|
|
35
97
|
/**
|
|
36
98
|
* Reusable form switch/toggle with an optional description.
|
|
@@ -73,4 +135,4 @@ interface FormCheckboxProps {
|
|
|
73
135
|
}
|
|
74
136
|
declare const FormCheckbox: ({ label, value, onValueChange, onChange, hint, description, disabled, containerStyle, testID, accessibilityLabel, accessibilityHint, }: FormCheckboxProps) => React.ReactElement;
|
|
75
137
|
|
|
76
|
-
export { type ChipOption, ChipSelector, type ChipSelectorProps, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormSwitch, type FormSwitchProps };
|
|
138
|
+
export { type ChipOption, ChipSelector, type ChipSelectorProps, type ChipVariant, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormSwitch, type FormSwitchProps, type ThemedInput, ThemedTextInput, type ThemedTextInputProps, type UseThemedInputOptions, useThemedInput };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { TextInputProps, ViewStyle } from 'react-native';
|
|
2
|
+
import { TextInputProps, ViewStyle, TextInput, TextStyle } from 'react-native';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Reusable form field — labelled text input with optional required mark + error.
|
|
6
|
+
*
|
|
7
|
+
* The input itself is the shared `ThemedTextInput` primitive, so every FormField gets the
|
|
8
|
+
* readable + lively treatment (dark text, subtle off-white rest background, brand focus
|
|
9
|
+
* animation with a soft ring) for free, driven by the active theme.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
interface FormFieldProps extends Omit<TextInputProps, 'style'> {
|
|
@@ -13,10 +17,66 @@ interface FormFieldProps extends Omit<TextInputProps, 'style'> {
|
|
|
13
17
|
}
|
|
14
18
|
declare const FormField: ({ label, required, error, containerStyle, ...textInputProps }: FormFieldProps) => React.ReactElement;
|
|
15
19
|
|
|
20
|
+
/**
|
|
21
|
+
* `ThemedTextInput` — the shared, readable + lively text-input primitive. A thin `<TextInput>`
|
|
22
|
+
* wrapper that applies the `useThemedInput` treatment (dark readable text, subtle off-white rest
|
|
23
|
+
* background, and the brand focus animation: indigo border + white background + soft 3px ring,
|
|
24
|
+
* eased over .14s via a GPU-cheap CSS transition on web; instant, native-safe off web).
|
|
25
|
+
*
|
|
26
|
+
* It is the ONE source of truth for the input look: `FormField` renders it, and raw filter inputs
|
|
27
|
+
* (search / country / year / date fallbacks) use it directly instead of forking the styles.
|
|
28
|
+
*
|
|
29
|
+
* Every existing `TextInputProps` (value, placeholder, onChangeText, testID, accessibility…) is
|
|
30
|
+
* forwarded untouched; `onFocus`/`onBlur` you pass are composed with the internal focus state.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
interface ThemedTextInputProps extends TextInputProps {
|
|
34
|
+
/** Render with the error border colour (matches `FormField`'s error treatment). */
|
|
35
|
+
hasError?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare const ThemedTextInput: React.ForwardRefExoticComponent<ThemedTextInputProps & React.RefAttributes<TextInput>>;
|
|
38
|
+
|
|
39
|
+
/** The exact focus/blur event types of the installed `react-native` `TextInput` (version-safe). */
|
|
40
|
+
type FocusEvent = Parameters<NonNullable<TextInputProps['onFocus']>>[0];
|
|
41
|
+
type BlurEvent = Parameters<NonNullable<TextInputProps['onBlur']>>[0];
|
|
42
|
+
interface UseThemedInputOptions {
|
|
43
|
+
/** Render with the error border colour (the brand focus border is suppressed while errored). */
|
|
44
|
+
hasError?: boolean;
|
|
45
|
+
/** Consumer focus handler — composed with the internal focus-state toggle. */
|
|
46
|
+
onFocus?: (e: FocusEvent) => void;
|
|
47
|
+
/** Consumer blur handler — composed with the internal focus-state toggle. */
|
|
48
|
+
onBlur?: (e: BlurEvent) => void;
|
|
49
|
+
}
|
|
50
|
+
interface ThemedInput {
|
|
51
|
+
/** The themed input style for the current focus/hover state (memoised). Merge AFTER your box style. */
|
|
52
|
+
style: TextStyle;
|
|
53
|
+
/** A readable mid-grey placeholder colour from the theme. */
|
|
54
|
+
placeholderTextColor: string;
|
|
55
|
+
isFocused: boolean;
|
|
56
|
+
isHovered: boolean;
|
|
57
|
+
/** Spread/attach onto the `<TextInput>` to drive the focus treatment. */
|
|
58
|
+
focusBind: {
|
|
59
|
+
onFocus: (e: FocusEvent) => void;
|
|
60
|
+
onBlur: (e: BlurEvent) => void;
|
|
61
|
+
};
|
|
62
|
+
/** Web-only mouse-hover handlers, pre-typed for `<TextInput>` (an empty object on native). */
|
|
63
|
+
hoverBind: TextInputProps;
|
|
64
|
+
}
|
|
65
|
+
declare function useThemedInput(options?: UseThemedInputOptions): ThemedInput;
|
|
66
|
+
|
|
16
67
|
/**
|
|
17
68
|
* Chip/pill selector for selecting one or multiple options.
|
|
69
|
+
*
|
|
70
|
+
* Two visual variants (both fully theme-driven):
|
|
71
|
+
* - `solid` (default) — the original filled-pill look: selected = solid primary fill with
|
|
72
|
+
* white ink. Unchanged, so existing consumers (erevna / katalogos / kefi) render identically.
|
|
73
|
+
* - `outline` — the AML v1 console look: an outlined pill on a muted fill; selected = a subtle
|
|
74
|
+
* tinted-outline (primary border + primary ink + a low-alpha primary wash), NOT a solid fill.
|
|
75
|
+
* Hover (web) lifts the border/ink to the primary colour.
|
|
18
76
|
*/
|
|
19
77
|
|
|
78
|
+
/** Visual variant. `solid` = filled selected pill (default); `outline` = v1 tinted-outline. */
|
|
79
|
+
type ChipVariant = 'solid' | 'outline';
|
|
20
80
|
interface ChipOption<T> {
|
|
21
81
|
value: T;
|
|
22
82
|
label: string;
|
|
@@ -28,9 +88,11 @@ interface ChipSelectorProps<T> {
|
|
|
28
88
|
onChange: (value: T) => void;
|
|
29
89
|
multiple?: boolean;
|
|
30
90
|
disabled?: boolean;
|
|
91
|
+
/** Visual variant. `solid` (default) = filled selected pill; `outline` = v1 tinted-outline. */
|
|
92
|
+
variant?: ChipVariant;
|
|
31
93
|
containerStyle?: ViewStyle;
|
|
32
94
|
}
|
|
33
|
-
declare const ChipSelector: <T extends string | number>({ label, options, value, onChange, multiple, disabled, containerStyle, }: ChipSelectorProps<T>) => React.ReactElement;
|
|
95
|
+
declare const ChipSelector: <T extends string | number>({ label, options, value, onChange, multiple, disabled, variant, containerStyle, }: ChipSelectorProps<T>) => React.ReactElement;
|
|
34
96
|
|
|
35
97
|
/**
|
|
36
98
|
* Reusable form switch/toggle with an optional description.
|
|
@@ -73,4 +135,4 @@ interface FormCheckboxProps {
|
|
|
73
135
|
}
|
|
74
136
|
declare const FormCheckbox: ({ label, value, onValueChange, onChange, hint, description, disabled, containerStyle, testID, accessibilityLabel, accessibilityHint, }: FormCheckboxProps) => React.ReactElement;
|
|
75
137
|
|
|
76
|
-
export { type ChipOption, ChipSelector, type ChipSelectorProps, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormSwitch, type FormSwitchProps };
|
|
138
|
+
export { type ChipOption, ChipSelector, type ChipSelectorProps, type ChipVariant, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormSwitch, type FormSwitchProps, type ThemedInput, ThemedTextInput, type ThemedTextInputProps, type UseThemedInputOptions, useThemedInput };
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,142 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React3 = require('react');
|
|
4
4
|
var reactNative = require('react-native');
|
|
5
5
|
var uiFeedback = require('@dloizides/ui-feedback');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
|
|
8
8
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var React3__default = /*#__PURE__*/_interopDefault(React3);
|
|
11
11
|
|
|
12
12
|
// src/FormField/FormField.tsx
|
|
13
|
+
var IS_WEB = reactNative.Platform.OS === "web";
|
|
14
|
+
var TRANSITION_PROPERTY = "border-color, background-color, box-shadow";
|
|
15
|
+
var TRANSITION_DURATION = "0.14s";
|
|
16
|
+
var TRANSITION_TIMING = "ease";
|
|
17
|
+
var FOCUS_RING_WIDTH = 3;
|
|
18
|
+
var FOCUS_RING_ALPHA = 0.16;
|
|
19
|
+
var HOVER_BORDER_ALPHA = 0.22;
|
|
20
|
+
var HEX_RADIX = 16;
|
|
21
|
+
var HEX_SHORT_LENGTH = 4;
|
|
22
|
+
var HEX_FULL_LENGTH = 7;
|
|
23
|
+
var RED_START = 1;
|
|
24
|
+
var GREEN_START = 3;
|
|
25
|
+
var BLUE_START = 5;
|
|
26
|
+
var CHANNEL_END_OFFSET = 2;
|
|
27
|
+
function withAlpha(color, alpha) {
|
|
28
|
+
const hex = color.trim();
|
|
29
|
+
const isHex = hex.charAt(0) === "#" && (hex.length === HEX_FULL_LENGTH || hex.length === HEX_SHORT_LENGTH);
|
|
30
|
+
if (!isHex) return color;
|
|
31
|
+
const full = hex.length === HEX_SHORT_LENGTH ? `#${hex.charAt(1)}${hex.charAt(1)}${hex.charAt(2)}${hex.charAt(2)}${hex.charAt(3)}${hex.charAt(3)}` : hex;
|
|
32
|
+
const r = parseInt(full.slice(RED_START, RED_START + CHANNEL_END_OFFSET), HEX_RADIX);
|
|
33
|
+
const g = parseInt(full.slice(GREEN_START, GREEN_START + CHANNEL_END_OFFSET), HEX_RADIX);
|
|
34
|
+
const b = parseInt(full.slice(BLUE_START, BLUE_START + CHANNEL_END_OFFSET), HEX_RADIX);
|
|
35
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
36
|
+
}
|
|
37
|
+
function useThemedInput(options = {}) {
|
|
38
|
+
const { hasError = false, onFocus, onBlur } = options;
|
|
39
|
+
const { theme } = uiFeedback.useUi();
|
|
40
|
+
const { colors, palette, semantic } = theme;
|
|
41
|
+
const [isFocused, setIsFocused] = React3.useState(false);
|
|
42
|
+
const [isHovered, setIsHovered] = React3.useState(false);
|
|
43
|
+
const handleFocus = React3.useCallback(
|
|
44
|
+
(e) => {
|
|
45
|
+
setIsFocused(true);
|
|
46
|
+
onFocus?.(e);
|
|
47
|
+
},
|
|
48
|
+
[onFocus]
|
|
49
|
+
);
|
|
50
|
+
const handleBlur = React3.useCallback(
|
|
51
|
+
(e) => {
|
|
52
|
+
setIsFocused(false);
|
|
53
|
+
onBlur?.(e);
|
|
54
|
+
},
|
|
55
|
+
[onBlur]
|
|
56
|
+
);
|
|
57
|
+
const handleHoverIn = React3.useCallback(() => setIsHovered(true), []);
|
|
58
|
+
const handleHoverOut = React3.useCallback(() => setIsHovered(false), []);
|
|
59
|
+
const primary = palette.primary["500"];
|
|
60
|
+
const errorColor = semantic.error["500"];
|
|
61
|
+
const style = React3.useMemo(() => {
|
|
62
|
+
const restBorder = hasError ? errorColor : colors.border;
|
|
63
|
+
const focusBorder = hasError ? errorColor : primary;
|
|
64
|
+
const borderColor = isFocused ? focusBorder : isHovered ? withAlpha(colors.text, HOVER_BORDER_ALPHA) : restBorder;
|
|
65
|
+
const backgroundColor = isFocused ? colors.surface : colors.surfaceElevated;
|
|
66
|
+
const base = { color: colors.text, borderColor, backgroundColor };
|
|
67
|
+
if (!IS_WEB) return base;
|
|
68
|
+
const ring = isFocused && !hasError ? `0 0 0 ${FOCUS_RING_WIDTH}px ${withAlpha(primary, FOCUS_RING_ALPHA)}` : "none";
|
|
69
|
+
const webStyle = {
|
|
70
|
+
transitionProperty: TRANSITION_PROPERTY,
|
|
71
|
+
transitionDuration: TRANSITION_DURATION,
|
|
72
|
+
transitionTimingFunction: TRANSITION_TIMING,
|
|
73
|
+
boxShadow: ring,
|
|
74
|
+
outlineStyle: "none"
|
|
75
|
+
};
|
|
76
|
+
return { ...base, ...webStyle };
|
|
77
|
+
}, [
|
|
78
|
+
isFocused,
|
|
79
|
+
isHovered,
|
|
80
|
+
hasError,
|
|
81
|
+
colors.text,
|
|
82
|
+
colors.border,
|
|
83
|
+
colors.surface,
|
|
84
|
+
colors.surfaceElevated,
|
|
85
|
+
primary,
|
|
86
|
+
errorColor
|
|
87
|
+
]);
|
|
88
|
+
const focusBind = React3.useMemo(() => ({ onFocus: handleFocus, onBlur: handleBlur }), [handleFocus, handleBlur]);
|
|
89
|
+
const hoverBind = React3.useMemo(
|
|
90
|
+
() => IS_WEB ? { onMouseEnter: handleHoverIn, onMouseLeave: handleHoverOut } : {},
|
|
91
|
+
[handleHoverIn, handleHoverOut]
|
|
92
|
+
);
|
|
93
|
+
return {
|
|
94
|
+
style,
|
|
95
|
+
placeholderTextColor: colors.textSecondary,
|
|
96
|
+
isFocused,
|
|
97
|
+
isHovered,
|
|
98
|
+
focusBind,
|
|
99
|
+
hoverBind
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
var ThemedTextInput = React3__default.default.forwardRef(function ThemedTextInput2({ hasError = false, onFocus, onBlur, style, placeholderTextColor, ...rest }, ref) {
|
|
103
|
+
const {
|
|
104
|
+
style: themedStyle,
|
|
105
|
+
placeholderTextColor: themedPlaceholder,
|
|
106
|
+
focusBind,
|
|
107
|
+
hoverBind
|
|
108
|
+
} = useThemedInput({ hasError, onFocus, onBlur });
|
|
109
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
110
|
+
reactNative.TextInput,
|
|
111
|
+
{
|
|
112
|
+
ref,
|
|
113
|
+
placeholderTextColor: placeholderTextColor ?? themedPlaceholder,
|
|
114
|
+
...rest,
|
|
115
|
+
...hoverBind,
|
|
116
|
+
onFocus: focusBind.onFocus,
|
|
117
|
+
onBlur: focusBind.onBlur,
|
|
118
|
+
style: [style, themedStyle]
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
});
|
|
13
122
|
var CONTAINER_MARGIN_BOTTOM = 16;
|
|
14
|
-
var
|
|
123
|
+
var INPUT_PADDING_V = 10;
|
|
124
|
+
var INPUT_PADDING_H = 12;
|
|
15
125
|
var INPUT_BORDER_RADIUS = 8;
|
|
16
126
|
var ERROR_FONT_SIZE = 12;
|
|
127
|
+
var LABEL_FONT_SIZE = 13;
|
|
17
128
|
var styles = reactNative.StyleSheet.create({
|
|
18
129
|
container: {
|
|
19
130
|
marginBottom: CONTAINER_MARGIN_BOTTOM
|
|
20
131
|
},
|
|
21
132
|
label: {
|
|
22
133
|
marginBottom: 4,
|
|
134
|
+
fontSize: LABEL_FONT_SIZE,
|
|
23
135
|
fontWeight: "600"
|
|
24
136
|
},
|
|
25
137
|
input: {
|
|
26
|
-
|
|
138
|
+
paddingVertical: INPUT_PADDING_V,
|
|
139
|
+
paddingHorizontal: INPUT_PADDING_H,
|
|
27
140
|
borderRadius: INPUT_BORDER_RADIUS,
|
|
28
141
|
borderWidth: 1
|
|
29
142
|
},
|
|
@@ -43,22 +156,14 @@ var FormField = ({
|
|
|
43
156
|
const { colors, semantic } = theme;
|
|
44
157
|
const errorColor = semantic.error["500"];
|
|
45
158
|
const hasError = typeof error === "string" && error !== "";
|
|
46
|
-
const themeStyles =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const errorTextStyle = { color: errorColor };
|
|
55
|
-
return {
|
|
56
|
-
label: labelStyle,
|
|
57
|
-
requiredMark: requiredMarkStyle,
|
|
58
|
-
input: inputStyle,
|
|
59
|
-
errorText: errorTextStyle
|
|
60
|
-
};
|
|
61
|
-
}, [colors.border, colors.surface, colors.text, errorColor, hasError]);
|
|
159
|
+
const themeStyles = React3__default.default.useMemo(
|
|
160
|
+
() => ({
|
|
161
|
+
label: { color: colors.textSecondary },
|
|
162
|
+
requiredMark: { color: errorColor },
|
|
163
|
+
errorText: { color: errorColor }
|
|
164
|
+
}),
|
|
165
|
+
[colors.textSecondary, errorColor]
|
|
166
|
+
);
|
|
62
167
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles.container, containerStyle], children: [
|
|
63
168
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles.label, themeStyles.label], children: [
|
|
64
169
|
label,
|
|
@@ -66,12 +171,12 @@ var FormField = ({
|
|
|
66
171
|
required ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: themeStyles.requiredMark, children: "*" }) : null
|
|
67
172
|
] }),
|
|
68
173
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
69
|
-
|
|
174
|
+
ThemedTextInput,
|
|
70
175
|
{
|
|
71
176
|
accessibilityHint: `Enter ${label}`,
|
|
72
177
|
accessibilityLabel: label,
|
|
73
|
-
|
|
74
|
-
style:
|
|
178
|
+
hasError,
|
|
179
|
+
style: styles.input,
|
|
75
180
|
testID: "form-field-input",
|
|
76
181
|
...textInputProps
|
|
77
182
|
}
|
|
@@ -81,6 +186,28 @@ var FormField = ({
|
|
|
81
186
|
};
|
|
82
187
|
var TRANSPARENT_COLOR = "transparent";
|
|
83
188
|
var WHITE_COLOR = "#fff";
|
|
189
|
+
var SOLID_RADIUS = 20;
|
|
190
|
+
var OUTLINE_RADIUS = 999;
|
|
191
|
+
var OUTLINE_PAD_H = 12;
|
|
192
|
+
var OUTLINE_PAD_V = 5;
|
|
193
|
+
var OUTLINE_FONT_SIZE = 13;
|
|
194
|
+
var OUTLINE_TINT_ALPHA = 0.1;
|
|
195
|
+
var HEX_SHORT_LENGTH2 = 3;
|
|
196
|
+
var HEX_RADIX2 = 16;
|
|
197
|
+
var RGB_R_START = 0;
|
|
198
|
+
var RGB_R_END = 2;
|
|
199
|
+
var RGB_G_END = 4;
|
|
200
|
+
var RGB_B_END = 6;
|
|
201
|
+
function withAlpha2(hex, alpha) {
|
|
202
|
+
const normalized = hex.replace("#", "");
|
|
203
|
+
const isShort = normalized.length === HEX_SHORT_LENGTH2;
|
|
204
|
+
const full = isShort ? normalized.split("").map((c) => c + c).join("") : normalized;
|
|
205
|
+
const r = parseInt(full.slice(RGB_R_START, RGB_R_END), HEX_RADIX2);
|
|
206
|
+
const g = parseInt(full.slice(RGB_R_END, RGB_G_END), HEX_RADIX2);
|
|
207
|
+
const b = parseInt(full.slice(RGB_G_END, RGB_B_END), HEX_RADIX2);
|
|
208
|
+
const isValid = !Number.isNaN(r) && !Number.isNaN(g) && !Number.isNaN(b);
|
|
209
|
+
return isValid ? `rgba(${r}, ${g}, ${b}, ${alpha})` : hex;
|
|
210
|
+
}
|
|
84
211
|
var styles2 = reactNative.StyleSheet.create({
|
|
85
212
|
label: {
|
|
86
213
|
marginBottom: 8,
|
|
@@ -97,12 +224,72 @@ var styles2 = reactNative.StyleSheet.create({
|
|
|
97
224
|
chip: {
|
|
98
225
|
paddingHorizontal: 12,
|
|
99
226
|
paddingVertical: 6,
|
|
100
|
-
borderRadius:
|
|
227
|
+
borderRadius: SOLID_RADIUS,
|
|
101
228
|
backgroundColor: TRANSPARENT_COLOR,
|
|
102
229
|
borderWidth: 1
|
|
103
230
|
},
|
|
104
|
-
|
|
231
|
+
chipOutline: {
|
|
232
|
+
paddingHorizontal: OUTLINE_PAD_H,
|
|
233
|
+
paddingVertical: OUTLINE_PAD_V,
|
|
234
|
+
borderRadius: OUTLINE_RADIUS,
|
|
235
|
+
borderWidth: 1
|
|
236
|
+
},
|
|
237
|
+
chipText: {},
|
|
238
|
+
chipTextOutline: { fontSize: OUTLINE_FONT_SIZE }
|
|
105
239
|
});
|
|
240
|
+
function OutlineChip({
|
|
241
|
+
option,
|
|
242
|
+
selected,
|
|
243
|
+
disabled,
|
|
244
|
+
colors,
|
|
245
|
+
onPress
|
|
246
|
+
}) {
|
|
247
|
+
const [hovered, setHovered] = React3__default.default.useState(false);
|
|
248
|
+
const active = selected || hovered;
|
|
249
|
+
const borderColor = active ? colors.primary : colors.border;
|
|
250
|
+
const backgroundColor = selected ? colors.tint : colors.muted;
|
|
251
|
+
const textColor = active ? colors.primary : colors.textMuted;
|
|
252
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
253
|
+
reactNative.Pressable,
|
|
254
|
+
{
|
|
255
|
+
accessibilityHint: `Selects ${option.label}`,
|
|
256
|
+
accessibilityLabel: option.label,
|
|
257
|
+
accessibilityRole: "button",
|
|
258
|
+
accessibilityState: { selected },
|
|
259
|
+
disabled,
|
|
260
|
+
style: styles2.chipWrapper,
|
|
261
|
+
testID: `chip-selector-chip-${String(option.value)}`,
|
|
262
|
+
onHoverIn: () => setHovered(true),
|
|
263
|
+
onHoverOut: () => setHovered(false),
|
|
264
|
+
onPress,
|
|
265
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles2.chipOutline, { borderColor, backgroundColor }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.chipText, styles2.chipTextOutline, { color: textColor }], children: option.label }) })
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
function SolidChip({
|
|
270
|
+
option,
|
|
271
|
+
selected,
|
|
272
|
+
disabled,
|
|
273
|
+
colors,
|
|
274
|
+
onPress
|
|
275
|
+
}) {
|
|
276
|
+
const backgroundColor = selected ? colors.primary : TRANSPARENT_COLOR;
|
|
277
|
+
const textColor = selected ? WHITE_COLOR : colors.textStrong;
|
|
278
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
279
|
+
reactNative.TouchableOpacity,
|
|
280
|
+
{
|
|
281
|
+
accessibilityHint: `Selects ${option.label}`,
|
|
282
|
+
accessibilityLabel: option.label,
|
|
283
|
+
accessibilityRole: "button",
|
|
284
|
+
accessibilityState: { selected },
|
|
285
|
+
disabled,
|
|
286
|
+
style: styles2.chipWrapper,
|
|
287
|
+
testID: `chip-selector-chip-${String(option.value)}`,
|
|
288
|
+
onPress,
|
|
289
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles2.chip, { borderColor: colors.border, backgroundColor }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.chipText, { color: textColor }], children: option.label }) })
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
}
|
|
106
293
|
var ChipSelector = ({
|
|
107
294
|
label,
|
|
108
295
|
options,
|
|
@@ -110,57 +297,52 @@ var ChipSelector = ({
|
|
|
110
297
|
onChange,
|
|
111
298
|
multiple = false,
|
|
112
299
|
disabled = false,
|
|
300
|
+
variant = "solid",
|
|
113
301
|
containerStyle
|
|
114
302
|
}) => {
|
|
115
303
|
const { theme } = uiFeedback.useUi();
|
|
116
304
|
const { colors, palette } = theme;
|
|
117
|
-
const primaryColor = palette.primary["500"];
|
|
118
305
|
const hasLabel = typeof label === "string" && label !== "";
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const chipStyle = { borderColor: colors.border };
|
|
122
|
-
const chipSelectedStyle = { backgroundColor: primaryColor };
|
|
123
|
-
const chipTextStyle = { color: colors.text };
|
|
124
|
-
const chipTextSelectedStyle = { color: WHITE_COLOR };
|
|
306
|
+
const chipColors = React3__default.default.useMemo(() => {
|
|
307
|
+
const primary = palette.primary["500"];
|
|
125
308
|
return {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
309
|
+
border: colors.border,
|
|
310
|
+
textStrong: colors.text,
|
|
311
|
+
textMuted: colors.textSecondary,
|
|
312
|
+
primary,
|
|
313
|
+
muted: colors.background,
|
|
314
|
+
tint: withAlpha2(primary, OUTLINE_TINT_ALPHA)
|
|
131
315
|
};
|
|
132
|
-
}, [colors.border, colors.text,
|
|
316
|
+
}, [colors.border, colors.text, colors.textSecondary, colors.background, palette.primary]);
|
|
317
|
+
const labelStyle = React3__default.default.useMemo(() => ({ color: colors.text }), [colors.text]);
|
|
133
318
|
function isSelected(optionValue) {
|
|
134
319
|
if (multiple && Array.isArray(value))
|
|
135
320
|
return value.includes(optionValue);
|
|
136
321
|
return value === optionValue;
|
|
137
322
|
}
|
|
138
323
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: containerStyle, children: [
|
|
139
|
-
hasLabel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.label,
|
|
324
|
+
hasLabel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.label, labelStyle], children: label }) : null,
|
|
140
325
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.chipContainer, children: options.map((option) => {
|
|
141
326
|
const selected = isSelected(option.value);
|
|
142
|
-
|
|
143
|
-
|
|
327
|
+
const onPress = () => onChange(option.value);
|
|
328
|
+
return variant === "outline" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
329
|
+
OutlineChip,
|
|
330
|
+
{
|
|
331
|
+
colors: chipColors,
|
|
332
|
+
disabled,
|
|
333
|
+
option,
|
|
334
|
+
selected,
|
|
335
|
+
onPress
|
|
336
|
+
},
|
|
337
|
+
String(option.value)
|
|
338
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
339
|
+
SolidChip,
|
|
144
340
|
{
|
|
145
|
-
|
|
146
|
-
accessibilityLabel: option.label,
|
|
147
|
-
accessibilityRole: "button",
|
|
148
|
-
accessibilityState: { selected },
|
|
341
|
+
colors: chipColors,
|
|
149
342
|
disabled,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
onPress
|
|
153
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
154
|
-
reactNative.View,
|
|
155
|
-
{
|
|
156
|
-
style: [
|
|
157
|
-
styles2.chip,
|
|
158
|
-
themeStyles.chip,
|
|
159
|
-
selected ? themeStyles.chipSelected : null
|
|
160
|
-
],
|
|
161
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.chipText, themeStyles.chipText, selected ? themeStyles.chipTextSelected : null], children: option.label })
|
|
162
|
-
}
|
|
163
|
-
)
|
|
343
|
+
option,
|
|
344
|
+
selected,
|
|
345
|
+
onPress
|
|
164
346
|
},
|
|
165
347
|
String(option.value)
|
|
166
348
|
);
|
|
@@ -199,7 +381,7 @@ var FormSwitch = ({
|
|
|
199
381
|
const { theme } = uiFeedback.useUi();
|
|
200
382
|
const { colors } = theme;
|
|
201
383
|
const hasDescription = typeof description === "string" && description !== "";
|
|
202
|
-
const themeStyles =
|
|
384
|
+
const themeStyles = React3__default.default.useMemo(() => {
|
|
203
385
|
const labelStyle = { color: colors.text };
|
|
204
386
|
const descriptionStyle = { color: colors.textSecondary };
|
|
205
387
|
return { label: labelStyle, description: descriptionStyle };
|
|
@@ -289,7 +471,7 @@ var FormCheckbox = ({
|
|
|
289
471
|
const brand = palette.primary["500"];
|
|
290
472
|
const hintText = typeof hint === "string" && hint !== "" ? hint : description;
|
|
291
473
|
const hasHint = typeof hintText === "string" && hintText !== "";
|
|
292
|
-
const themeStyles =
|
|
474
|
+
const themeStyles = React3__default.default.useMemo(() => {
|
|
293
475
|
const boxOn = { backgroundColor: brand, borderColor: brand };
|
|
294
476
|
const boxOff = { backgroundColor: "transparent", borderColor: colors.border };
|
|
295
477
|
const labelStyle = { color: colors.text };
|
|
@@ -329,5 +511,7 @@ exports.ChipSelector = ChipSelector;
|
|
|
329
511
|
exports.FormCheckbox = FormCheckbox;
|
|
330
512
|
exports.FormField = FormField;
|
|
331
513
|
exports.FormSwitch = FormSwitch;
|
|
514
|
+
exports.ThemedTextInput = ThemedTextInput;
|
|
515
|
+
exports.useThemedInput = useThemedInput;
|
|
332
516
|
//# sourceMappingURL=index.js.map
|
|
333
517
|
//# sourceMappingURL=index.js.map
|