@solostylist/ui-kit-native 1.0.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.
Files changed (43) hide show
  1. package/dist/index.d.ts +14 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +16 -0
  4. package/dist/s-form/index.d.ts +2 -0
  5. package/dist/s-form/index.d.ts.map +1 -0
  6. package/dist/s-form/index.js +1 -0
  7. package/dist/s-form/s-form.d.ts +26 -0
  8. package/dist/s-form/s-form.d.ts.map +1 -0
  9. package/dist/s-form/s-form.js +25 -0
  10. package/dist/s-icon-button/index.d.ts +3 -0
  11. package/dist/s-icon-button/index.d.ts.map +1 -0
  12. package/dist/s-icon-button/index.js +1 -0
  13. package/dist/s-icon-button/s-icon-button.d.ts +46 -0
  14. package/dist/s-icon-button/s-icon-button.d.ts.map +1 -0
  15. package/dist/s-icon-button/s-icon-button.js +57 -0
  16. package/dist/s-select/index.d.ts +2 -0
  17. package/dist/s-select/index.d.ts.map +1 -0
  18. package/dist/s-select/index.js +1 -0
  19. package/dist/s-select/s-select.d.ts +48 -0
  20. package/dist/s-select/s-select.d.ts.map +1 -0
  21. package/dist/s-select/s-select.js +237 -0
  22. package/dist/s-text/index.d.ts +2 -0
  23. package/dist/s-text/index.d.ts.map +1 -0
  24. package/dist/s-text/index.js +1 -0
  25. package/dist/s-text/s-text.d.ts +30 -0
  26. package/dist/s-text/s-text.d.ts.map +1 -0
  27. package/dist/s-text/s-text.js +59 -0
  28. package/dist/s-text-field/index.d.ts +2 -0
  29. package/dist/s-text-field/index.d.ts.map +1 -0
  30. package/dist/s-text-field/index.js +1 -0
  31. package/dist/s-text-field/s-text-field.d.ts +53 -0
  32. package/dist/s-text-field/s-text-field.d.ts.map +1 -0
  33. package/dist/s-text-field/s-text-field.js +69 -0
  34. package/dist/theme/index.d.ts +6 -0
  35. package/dist/theme/index.d.ts.map +1 -0
  36. package/dist/theme/index.js +7 -0
  37. package/dist/theme/s-theme-provider.d.ts +28 -0
  38. package/dist/theme/s-theme-provider.d.ts.map +1 -0
  39. package/dist/theme/s-theme-provider.js +110 -0
  40. package/dist/theme/theme-primitives.d.ts +169 -0
  41. package/dist/theme/theme-primitives.d.ts.map +1 -0
  42. package/dist/theme/theme-primitives.js +209 -0
  43. package/package.json +80 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @solostylist/ui-kit-native
3
+ *
4
+ * React Native UI components for SoloStylist
5
+ * Synced with web ui-kit and uses @solostylist/core for theming
6
+ */
7
+ export { SThemeProvider, useSTheme, darkTheme, lightTheme, type SThemeProviderProps, type ThemeMode, type NativeTheme, type NativeThemeColors, } from './theme';
8
+ export { SForm, type SFormProps } from './s-form';
9
+ export { SIconButton, type SIconButtonProps } from './s-icon-button';
10
+ export { SSelect, type SSelectProps } from './s-select';
11
+ export { SText, type STextProps } from './s-text';
12
+ export { STextField, type STextFieldProps } from './s-text-field';
13
+ export { brand, lightBrand, gray, lightGray, green, lightGreen, orange, lightOrange, blue, lightBlue, red, lightRed, purple, lightPurple, blackAlpha, whiteAlpha, darkGradients, lightGradients, darkPulse, lightPulse, } from './theme';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,cAAc,EACd,SAAS,EACT,SAAS,EACT,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,GACvB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGlE,OAAO,EACL,KAAK,EACL,UAAU,EACV,IAAI,EACJ,SAAS,EACT,KAAK,EACL,UAAU,EACV,MAAM,EACN,WAAW,EACX,IAAI,EACJ,SAAS,EACT,GAAG,EACH,QAAQ,EACR,MAAM,EACN,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,SAAS,EACT,UAAU,GACX,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @solostylist/ui-kit-native
3
+ *
4
+ * React Native UI components for SoloStylist
5
+ * Synced with web ui-kit and uses @solostylist/core for theming
6
+ */
7
+ // Theme
8
+ export { SThemeProvider, useSTheme, darkTheme, lightTheme, } from './theme';
9
+ // Components
10
+ export { SForm } from './s-form';
11
+ export { SIconButton } from './s-icon-button';
12
+ export { SSelect } from './s-select';
13
+ export { SText } from './s-text';
14
+ export { STextField } from './s-text-field';
15
+ // Re-export core colors for convenience
16
+ export { brand, lightBrand, gray, lightGray, green, lightGreen, orange, lightOrange, blue, lightBlue, red, lightRed, purple, lightPurple, blackAlpha, whiteAlpha, darkGradients, lightGradients, darkPulse, lightPulse, } from './theme';
@@ -0,0 +1,2 @@
1
+ export { SForm, type SFormProps, default } from './s-form';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/s-form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1 @@
1
+ export { SForm, default } from './s-form';
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ /**
3
+ * Props interface for the SForm component
4
+ */
5
+ export interface SFormProps {
6
+ /** The form field label - can be text or a React element */
7
+ label?: string | React.ReactNode;
8
+ /** Optional hint text displayed below the input */
9
+ hint?: string;
10
+ /** Error message to display below the form field */
11
+ error?: string;
12
+ /** Whether the form field is required (adds asterisk to label) */
13
+ required?: boolean;
14
+ /** Child form elements (input, select, etc.) to wrap */
15
+ children: React.ReactNode;
16
+ }
17
+ /**
18
+ * A form wrapper component that provides consistent labeling, error handling, and help text.
19
+ * Synced with web SForm component from @solostylist/ui-kit.
20
+ */
21
+ export declare const SForm: {
22
+ ({ label, hint, error, required, children }: SFormProps): import("react/jsx-runtime").JSX.Element;
23
+ displayName: string;
24
+ };
25
+ export default SForm;
26
+ //# sourceMappingURL=s-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s-form.d.ts","sourceRoot":"","sources":["../../src/s-form/s-form.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;IACjC,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK;iDAAwD,UAAU;;CAkDnF,CAAC;AAIF,eAAe,KAAK,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet } from 'react-native';
3
+ import { HelperText, Surface } from 'react-native-paper';
4
+ import { SText } from '../s-text';
5
+ import { useSTheme } from '../theme';
6
+ /**
7
+ * A form wrapper component that provides consistent labeling, error handling, and help text.
8
+ * Synced with web SForm component from @solostylist/ui-kit.
9
+ */
10
+ export const SForm = ({ label, hint, error, required = false, children }) => {
11
+ const { theme } = useSTheme();
12
+ const styles = StyleSheet.create({
13
+ container: {
14
+ width: '100%',
15
+ },
16
+ labelContainer: {
17
+ flexDirection: 'row',
18
+ alignItems: 'center',
19
+ marginBottom: theme.spacing.xs,
20
+ },
21
+ });
22
+ return (_jsxs(Surface, { style: styles.container, elevation: 0, children: [label ? (_jsx(Surface, { style: styles.labelContainer, elevation: 0, children: typeof label === 'string' ? (_jsxs(_Fragment, { children: [_jsx(SText, { variant: "body2", fontWeight: "400", color: error ? 'error' : 'text.secondary', children: label }), required && (_jsx(SText, { variant: "body2", color: "error", style: { marginLeft: 2 }, children: "*" }))] })) : (label) })) : null, children, hint && !error ? (_jsx(HelperText, { type: "info", visible: true, children: hint })) : null, error ? (_jsx(HelperText, { type: "error", visible: true, children: error })) : null] }));
23
+ };
24
+ SForm.displayName = 'SForm';
25
+ export default SForm;
@@ -0,0 +1,3 @@
1
+ export { SIconButton, default } from './s-icon-button';
2
+ export type { SIconButtonProps, SIconButtonTooltipOptions } from './s-icon-button';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/s-icon-button/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ export { SIconButton, default } from './s-icon-button';
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { type IconButtonProps } from 'react-native-paper';
3
+ /**
4
+ * Tooltip options for SIconButton
5
+ * Similar to web version but adapted for React Native
6
+ */
7
+ export interface SIconButtonTooltipOptions {
8
+ /** Tooltip text content */
9
+ title: string;
10
+ /** Whether to show enter delay (in ms) */
11
+ enterDelay?: number;
12
+ /** Whether to show leave delay (in ms) */
13
+ leaveDelay?: number;
14
+ }
15
+ /**
16
+ * Props interface for SIconButton component
17
+ * Synced with web SIconButton from @solostylist/ui-kit
18
+ */
19
+ export interface SIconButtonProps extends Omit<IconButtonProps, 'theme' | 'size'> {
20
+ /** Icon name from Material Community Icons */
21
+ icon: string;
22
+ /** Size of the icon button - matches web MUI sizes */
23
+ size?: 'small' | 'medium' | 'large';
24
+ /** Icon color */
25
+ iconColor?: string;
26
+ /** Container color (background) */
27
+ containerColor?: string;
28
+ /** Whether the button is disabled */
29
+ disabled?: boolean;
30
+ /** Accessibility label */
31
+ accessibilityLabel?: string;
32
+ /** Callback fired when button is pressed */
33
+ onPress?: () => void;
34
+ /** Optional tooltip configuration (excludes 'children' which is handled internally) */
35
+ tooltipOptions?: SIconButtonTooltipOptions;
36
+ }
37
+ /**
38
+ * An enhanced icon button component with optional tooltip integration.
39
+ * Synced with web SIconButton from @solostylist/ui-kit.
40
+ */
41
+ export declare const SIconButton: {
42
+ ({ icon, size, iconColor, containerColor, disabled, accessibilityLabel, onPress, style, tooltipOptions, ...props }: SIconButtonProps): React.JSX.Element;
43
+ displayName: string;
44
+ };
45
+ export default SIconButton;
46
+ //# sourceMappingURL=s-icon-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s-icon-button.d.ts","sourceRoot":"","sources":["../../src/s-icon-button/s-icon-button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG/E;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/E,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0BAA0B;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uFAAuF;IACvF,cAAc,CAAC,EAAE,yBAAyB,CAAC;CAC5C;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW;wHAWrB,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO;;CAuEtC,CAAC;AAIF,eAAe,WAAW,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { StyleSheet } from 'react-native';
3
+ import { IconButton, Tooltip } from 'react-native-paper';
4
+ import { useSTheme } from '../theme';
5
+ /**
6
+ * An enhanced icon button component with optional tooltip integration.
7
+ * Synced with web SIconButton from @solostylist/ui-kit.
8
+ */
9
+ export const SIconButton = ({ icon, size = 'medium', iconColor, containerColor, disabled = false, accessibilityLabel, onPress, style, tooltipOptions, ...props }) => {
10
+ const { theme } = useSTheme();
11
+ // Map size to icon size - matches web MUI sizes
12
+ // small: 12px, medium: 16px, large: 20px
13
+ const getIconSize = () => {
14
+ switch (size) {
15
+ case 'small':
16
+ return 12;
17
+ case 'medium':
18
+ return 16;
19
+ case 'large':
20
+ return 20;
21
+ default:
22
+ return 16;
23
+ }
24
+ };
25
+ const iconSize = getIconSize();
26
+ // Default icon color based on theme
27
+ const defaultIconColor = iconColor || theme.colors.text.primary;
28
+ // Get container color
29
+ const getContainerColor = () => {
30
+ if (containerColor)
31
+ return containerColor;
32
+ if (disabled)
33
+ return 'transparent';
34
+ return 'transparent';
35
+ };
36
+ // Get icon color
37
+ const getIconColor = () => {
38
+ if (iconColor)
39
+ return iconColor;
40
+ if (disabled)
41
+ return theme.colors.text.disabled;
42
+ return defaultIconColor;
43
+ };
44
+ const styles = StyleSheet.create({
45
+ button: {
46
+ borderRadius: theme.borderRadius.full,
47
+ },
48
+ });
49
+ const iconButton = (_jsx(IconButton, { icon: icon, size: iconSize, iconColor: getIconColor(), containerColor: getContainerColor(), disabled: disabled, onPress: onPress, accessibilityLabel: accessibilityLabel, style: [styles.button, style], ...props }));
50
+ // Wrap with tooltip if tooltipOptions is provided
51
+ if (tooltipOptions) {
52
+ return (_jsx(Tooltip, { title: tooltipOptions.title, enterTouchDelay: tooltipOptions.enterDelay ?? 0, leaveTouchDelay: tooltipOptions.leaveDelay ?? 0, children: iconButton }));
53
+ }
54
+ return iconButton;
55
+ };
56
+ SIconButton.displayName = 'SIconButton';
57
+ export default SIconButton;
@@ -0,0 +1,2 @@
1
+ export { SSelect, type SSelectProps } from './s-select';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/s-select/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1 @@
1
+ export { SSelect } from './s-select';
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ /** Base type constraint for select options - can be primitives or objects */
3
+ type BaseOption = string | number | Record<string, any>;
4
+ /**
5
+ * Props interface for SSelect component
6
+ * Synced with web SSelect from @solostylist/ui-kit
7
+ */
8
+ export interface SSelectProps<T extends BaseOption = string> {
9
+ /** Array of options to display in the select dropdown */
10
+ options?: T[];
11
+ /** Key to use for option display text (required for object options) */
12
+ optionLabel?: T extends object ? keyof T : never;
13
+ /** Key to use for option values (required for object options) */
14
+ optionValue?: T extends object ? keyof T : never;
15
+ /** Placeholder text shown when no option is selected */
16
+ placeholder?: string;
17
+ /** Field label displayed above the select */
18
+ label?: string | React.ReactNode;
19
+ /** Error message to display below the field */
20
+ error?: string;
21
+ /** Whether the field is required (shows asterisk in label) */
22
+ required?: boolean;
23
+ /** Current value (controlled component) */
24
+ value?: T | T[] | null;
25
+ /** Callback when value changes */
26
+ onChange?: (value: T | T[] | null) => void;
27
+ /** Visual style variant */
28
+ variant?: 'outlined' | 'flat';
29
+ /** Enable search/filter functionality in the dropdown */
30
+ searchable?: boolean;
31
+ /** Placeholder text for the search input */
32
+ searchPlaceholder?: string;
33
+ /** Enable multiple selection */
34
+ multiple?: boolean;
35
+ /** Whether the field is disabled */
36
+ disabled?: boolean;
37
+ /** Input size variant */
38
+ size?: 'small' | 'medium';
39
+ /** Help text shown below the input */
40
+ hint?: string;
41
+ }
42
+ /**
43
+ * A flexible select component that handles both primitive and object options with form integration.
44
+ * Synced with web SSelect from @solostylist/ui-kit.
45
+ */
46
+ export declare function SSelect<T extends BaseOption = string>({ options, optionLabel, optionValue, placeholder, label, error, required, value: controlledValue, onChange, variant, searchable, searchPlaceholder, multiple, disabled, size, hint, }: SSelectProps<T>): React.JSX.Element;
47
+ export {};
48
+ //# sourceMappingURL=s-select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s-select.d.ts","sourceRoot":"","sources":["../../src/s-select/s-select.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAOzD,6EAA6E;AAC7E,KAAK,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAExD;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,GAAG,MAAM;IACzD,yDAAyD;IACzD,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;IACd,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC;IACjD,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC;IACjD,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;IACjC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC;IACvB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C,2BAA2B;IAC3B,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAC9B,yDAAyD;IACzD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB;IACzB,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,UAAU,GAAG,MAAM,EAAE,EACrD,OAAY,EACZ,WAA0D,EAC1D,WAAwD,EACxD,WAAyB,EACzB,KAAK,EACL,KAAK,EACL,QAAgB,EAChB,KAAK,EAAE,eAAe,EACtB,QAAQ,EACR,OAAoB,EACpB,UAAkB,EAClB,iBAA+B,EAC/B,QAAgB,EAChB,QAAgB,EAChB,IAAe,EACf,IAAI,GACL,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAsUrC"}
@@ -0,0 +1,237 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Color from 'color';
3
+ import { useMemo, useRef, useState } from 'react';
4
+ import { Platform, Pressable, ScrollView, StyleSheet, View } from 'react-native';
5
+ import { Checkbox, Divider, Menu, Surface, TextInput } from 'react-native-paper';
6
+ import { SForm } from '../s-form';
7
+ import { SText } from '../s-text';
8
+ import { useSTheme } from '../theme';
9
+ /**
10
+ * A flexible select component that handles both primitive and object options with form integration.
11
+ * Synced with web SSelect from @solostylist/ui-kit.
12
+ */
13
+ export function SSelect({ options = [], optionLabel = 'name', optionValue = 'id', placeholder = 'Select...', label, error, required = false, value: controlledValue, onChange, variant = 'outlined', searchable = false, searchPlaceholder = 'Search...', multiple = false, disabled = false, size = 'medium', hint, }) {
14
+ const { theme } = useSTheme();
15
+ // State management
16
+ const isControlled = controlledValue !== undefined;
17
+ const [internalValue, setInternalValue] = useState(multiple ? [] : null);
18
+ const value = isControlled ? controlledValue : internalValue;
19
+ const [menuVisible, setMenuVisible] = useState(false);
20
+ const [searchTerm, setSearchTerm] = useState('');
21
+ const [hoveredIndex, setHoveredIndex] = useState(null);
22
+ const [anchorWidth, setAnchorWidth] = useState(undefined);
23
+ const anchorRef = useRef(null);
24
+ // Helper functions for option handling
25
+ const getOptionLabel = (item) => {
26
+ if (typeof item === 'string' || typeof item === 'number') {
27
+ return String(item);
28
+ }
29
+ return String(item[optionLabel]);
30
+ };
31
+ const getOptionValue = (item) => {
32
+ if (typeof item === 'string' || typeof item === 'number') {
33
+ return item;
34
+ }
35
+ return item[optionValue];
36
+ };
37
+ const isOptionDisabled = (item) => {
38
+ if (typeof item === 'object' && item !== null) {
39
+ return item.disabled ?? false;
40
+ }
41
+ return false;
42
+ };
43
+ // Filtered options for search
44
+ const filteredOptions = useMemo(() => {
45
+ if (!searchable || !searchTerm) {
46
+ return options;
47
+ }
48
+ return options.filter((item) => {
49
+ const label = getOptionLabel(item).toLowerCase();
50
+ return label.includes(searchTerm.toLowerCase());
51
+ });
52
+ }, [options, searchTerm, searchable]);
53
+ // Get display value
54
+ const getDisplayValue = () => {
55
+ if (multiple) {
56
+ const values = value || [];
57
+ if (values.length === 0) {
58
+ return '';
59
+ }
60
+ return values
61
+ .map((val) => {
62
+ const option = options.find((item) => getOptionValue(item) === getOptionValue(val));
63
+ return option ? getOptionLabel(option) : '';
64
+ })
65
+ .filter(Boolean)
66
+ .join(', ');
67
+ }
68
+ else {
69
+ const singleValue = value;
70
+ if (singleValue === null || singleValue === undefined) {
71
+ return '';
72
+ }
73
+ const option = options.find((item) => getOptionValue(item) === getOptionValue(singleValue));
74
+ return option ? getOptionLabel(option) : '';
75
+ }
76
+ };
77
+ // Check if option is selected
78
+ const isOptionSelected = (item) => {
79
+ if (multiple) {
80
+ const values = value || [];
81
+ return values.some((val) => getOptionValue(val) === getOptionValue(item));
82
+ }
83
+ else {
84
+ const singleValue = value;
85
+ if (!singleValue)
86
+ return false;
87
+ return getOptionValue(singleValue) === getOptionValue(item);
88
+ }
89
+ };
90
+ // Handle option selection
91
+ const handleOptionPress = (item) => {
92
+ if (isOptionDisabled(item))
93
+ return;
94
+ let newValue;
95
+ if (multiple) {
96
+ const currentValues = value || [];
97
+ const isSelected = isOptionSelected(item);
98
+ if (isSelected) {
99
+ newValue = currentValues.filter((val) => getOptionValue(val) !== getOptionValue(item));
100
+ }
101
+ else {
102
+ newValue = [...currentValues, item];
103
+ }
104
+ }
105
+ else {
106
+ newValue = item;
107
+ setMenuVisible(false);
108
+ setSearchTerm('');
109
+ }
110
+ if (onChange) {
111
+ onChange(newValue);
112
+ }
113
+ else if (!isControlled) {
114
+ setInternalValue(newValue);
115
+ }
116
+ };
117
+ const handleOpenMenu = () => {
118
+ if (!disabled) {
119
+ setMenuVisible(true);
120
+ }
121
+ };
122
+ const handleAnchorLayout = (event) => {
123
+ const { width } = event.nativeEvent.layout;
124
+ setAnchorWidth(width);
125
+ };
126
+ const handleCloseMenu = () => {
127
+ setMenuVisible(false);
128
+ setSearchTerm('');
129
+ setHoveredIndex(null);
130
+ };
131
+ const displayValue = getDisplayValue();
132
+ const styles = StyleSheet.create({
133
+ input: {
134
+ // Match web sizes: small = 36px, medium = 40px
135
+ ...(size === 'small' ? { height: 36 } : { height: 40 }),
136
+ },
137
+ inputContent: {
138
+ // Match STextField styling: fontFamily and fontSize
139
+ fontFamily: theme.typography.fontFamily,
140
+ fontSize: size === 'small' ? theme.typography.fontSize.sm : theme.typography.fontSize.md,
141
+ },
142
+ menuContent: {
143
+ borderRadius: theme.borderRadius.md,
144
+ backgroundColor: theme.colors.background.paper,
145
+ paddingTop: theme.spacing.sm,
146
+ paddingBottom: theme.spacing.sm,
147
+ borderWidth: 1,
148
+ borderColor: theme.colors.divider,
149
+ borderStyle: 'solid',
150
+ boxShadow: 'none',
151
+ },
152
+ menuItem: {
153
+ paddingTop: theme.spacing.sm,
154
+ paddingBottom: theme.spacing.sm,
155
+ paddingHorizontal: theme.spacing.md,
156
+ minHeight: 'auto',
157
+ },
158
+ menuItemSelected: {
159
+ // Match web: solid blue background for selected items (matches MUI's visual appearance)
160
+ // Use primary color with opacity to create a visible blue background
161
+ backgroundColor: theme.dark
162
+ ? Color(theme.colors.primary[500]).alpha(0.3).rgb().string()
163
+ : Color(theme.colors.primary[500]).alpha(0.1).rgb().string(),
164
+ },
165
+ searchContainer: {
166
+ padding: theme.spacing.sm,
167
+ backgroundColor: theme.colors.background.paper,
168
+ borderTopLeftRadius: theme.borderRadius.lg,
169
+ borderTopRightRadius: theme.borderRadius.lg,
170
+ },
171
+ searchInput: {
172
+ backgroundColor: theme.colors.background.default,
173
+ height: 36,
174
+ },
175
+ searchContent: {
176
+ // Match STextField styling: fontFamily and fontSize
177
+ fontFamily: theme.typography.fontFamily,
178
+ fontSize: theme.typography.fontSize.sm,
179
+ },
180
+ searchOutline: {
181
+ borderRadius: theme.borderRadius.md,
182
+ },
183
+ noResults: {
184
+ padding: theme.spacing.lg,
185
+ alignItems: 'center',
186
+ },
187
+ checkbox: {
188
+ marginRight: theme.spacing.sm,
189
+ },
190
+ scrollView: {
191
+ // Ensure ScrollView doesn't add extra padding that would break web alignment
192
+ flexGrow: 0,
193
+ },
194
+ menuItemHover: {
195
+ // Match web hover state: backgroundColor: theme.palette.action.hover
196
+ backgroundColor: theme.dark
197
+ ? Color(theme.colors.secondary[600]).alpha(0.2).rgb().string()
198
+ : Color(theme.colors.secondary[300]).alpha(0.2).rgb().string(),
199
+ },
200
+ menuItemSelectedHover: {
201
+ // Combined style for selected + hovered items
202
+ // Darken the selected background on hover
203
+ backgroundColor: theme.dark
204
+ ? Color(theme.colors.primary[500]).alpha(0.4).rgb().string()
205
+ : Color(theme.colors.primary[500]).alpha(0.15).rgb().string(),
206
+ },
207
+ });
208
+ return (_jsx(SForm, { label: label, hint: hint, error: error, required: required, children: _jsxs(Menu, { visible: menuVisible, onDismiss: handleCloseMenu, anchorPosition: "bottom", contentStyle: [styles.menuContent, anchorWidth !== undefined && { width: anchorWidth }], anchor: _jsx(View, { ref: anchorRef, onLayout: handleAnchorLayout, children: _jsx(Pressable, { onPress: handleOpenMenu, disabled: disabled, children: _jsx(TextInput, { mode: variant, placeholder: placeholder, placeholderTextColor: theme.colors.divider, value: displayValue, error: !!error, disabled: disabled, editable: false, pointerEvents: "none", dense: size === 'small', right: _jsx(TextInput.Icon, { icon: menuVisible ? 'chevron-up' : 'chevron-down' }), style: styles.input, contentStyle: styles.inputContent,
209
+ // Match web focus styling: blue outline when menu is open (focused state)
210
+ // Use outlineColor to show blue border when menu is visible (simulating focus)
211
+ outlineColor: error ? theme.colors.error[500] : menuVisible ? theme.colors.primary[400] : theme.colors.divider, activeOutlineColor: theme.colors.primary[400] }) }) }), children: [searchable && (_jsxs(_Fragment, { children: [_jsx(Surface, { style: styles.searchContainer, elevation: 0, children: _jsx(TextInput, { mode: "outlined", placeholder: searchPlaceholder, placeholderTextColor: theme.colors.divider, value: searchTerm, onChangeText: setSearchTerm, dense: true, left: _jsx(TextInput.Icon, { icon: "magnify" }), style: styles.searchInput, contentStyle: styles.searchContent, outlineStyle: styles.searchOutline, autoFocus: true }) }), _jsx(Divider, {})] })), _jsx(ScrollView, { style: styles.scrollView, contentContainerStyle: { paddingHorizontal: 0 }, children: filteredOptions.length > 0 ? (filteredOptions.map((item, index) => {
212
+ const itemValue = getOptionValue(item);
213
+ const itemLabel = getOptionLabel(item);
214
+ const isSelected = isOptionSelected(item);
215
+ const isDisabled = isOptionDisabled(item);
216
+ const isHovered = hoveredIndex === index;
217
+ return (_jsxs(Pressable, { onPress: () => handleOptionPress(item), onPressIn: () => setHoveredIndex(index), onPressOut: () => setHoveredIndex(null), ...(Platform.OS === 'web' && {
218
+ onMouseEnter: () => setHoveredIndex(index),
219
+ onMouseLeave: () => setHoveredIndex(null),
220
+ }), disabled: isDisabled, style: ({ pressed }) => {
221
+ const isActive = isHovered || pressed;
222
+ return [
223
+ styles.menuItem,
224
+ // Apply selected style only when not active (hovered or pressed)
225
+ isSelected && !isActive && styles.menuItemSelected,
226
+ // Apply hover/press style - selected hover takes precedence
227
+ isActive && isSelected && styles.menuItemSelectedHover,
228
+ isActive && !isSelected && styles.menuItemHover,
229
+ ];
230
+ }, children: [multiple && (_jsx(Checkbox.Android, { status: isSelected ? 'checked' : 'unchecked', disabled: isDisabled, style: styles.checkbox })), _jsx(SText, { variant: "body1", style: [
231
+ isSelected && {
232
+ color: theme.colors.text.primary,
233
+ fontWeight: theme.typography.fontWeight.medium,
234
+ },
235
+ ], children: itemLabel })] }, `${itemValue}-${index}`));
236
+ })) : (_jsx(Surface, { style: styles.noResults, elevation: 0, children: _jsx(SText, { variant: "body1", color: "text.secondary", children: searchTerm ? 'No results found' : 'No options available' }) })) })] }) }));
237
+ }
@@ -0,0 +1,2 @@
1
+ export { SText, default, type STextProps } from './s-text';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/s-text/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1 @@
1
+ export { SText, default } from './s-text';
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import { TextProps as RNTextProps, StyleProp, TextStyle } from 'react-native';
3
+ export interface STextProps extends Omit<RNTextProps, 'style'> {
4
+ /** Typography variant matching MUI's typography scale */
5
+ variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'subtitle1' | 'subtitle2' | 'body1' | 'body2' | 'caption';
6
+ /** Text color - supports theme colors or custom color string */
7
+ color?: 'primary' | 'secondary' | 'text.primary' | 'text.secondary' | 'text.disabled' | 'error' | 'warning' | 'success' | 'info' | string;
8
+ /** Font weight override */
9
+ fontWeight?: '400' | '500' | '600' | '700';
10
+ /** Font size override (in pixels) */
11
+ fontSize?: number;
12
+ /** Text alignment */
13
+ align?: 'left' | 'center' | 'right' | 'justify';
14
+ /** Text transform */
15
+ textTransform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase';
16
+ /** Line height multiplier */
17
+ lineHeight?: number;
18
+ /** Letter spacing */
19
+ letterSpacing?: number;
20
+ /** Additional custom styles */
21
+ style?: StyleProp<TextStyle>;
22
+ /** Child content */
23
+ children?: React.ReactNode;
24
+ }
25
+ export declare const SText: {
26
+ ({ variant, color, fontWeight, fontSize, align, textTransform, lineHeight, letterSpacing, style, children, ...props }: STextProps): React.JSX.Element;
27
+ displayName: string;
28
+ };
29
+ export default SText;
30
+ //# sourceMappingURL=s-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s-text.d.ts","sourceRoot":"","sources":["../../src/s-text/s-text.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,EAAE,SAAS,IAAI,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9E,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC;IAC5D,yDAAyD;IACzD,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAE9G,gEAAgE;IAChE,KAAK,CAAC,EACF,SAAS,GACT,WAAW,GACX,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,OAAO,GACP,SAAS,GACT,SAAS,GACT,MAAM,GACN,MAAM,CAAC;IAEX,2BAA2B;IAC3B,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IAE3C,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAEhD,qBAAqB;IACrB,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,CAAC;IAElE,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,qBAAqB;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAE7B,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AA8BD,eAAO,MAAM,KAAK;2HAYf,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO;;CAoChC,CAAC;AAIF,eAAe,KAAK,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { Text } from 'react-native-paper';
4
+ import { useSTheme } from '../theme';
5
+ const getFontFamily = (weight) => {
6
+ const fontMap = {
7
+ '400': 'Outfit_400Regular',
8
+ '500': 'Outfit_500Medium',
9
+ '600': 'Outfit_600SemiBold',
10
+ '700': 'Outfit_700Bold',
11
+ };
12
+ return fontMap[weight] || 'Outfit_400Regular';
13
+ };
14
+ const resolveColor = (colorProp, theme) => {
15
+ if (!colorProp)
16
+ return theme.colors.text.primary;
17
+ const colorMap = {
18
+ primary: theme.colors.primary[500],
19
+ secondary: theme.colors.secondary[500],
20
+ 'text.primary': theme.colors.text.primary,
21
+ 'text.secondary': theme.colors.text.secondary,
22
+ 'text.disabled': theme.colors.text.disabled,
23
+ error: theme.colors.error[500],
24
+ warning: theme.colors.warning[500],
25
+ success: theme.colors.success[500],
26
+ info: theme.colors.info[500],
27
+ };
28
+ return colorMap[colorProp] || colorProp;
29
+ };
30
+ export const SText = ({ variant = 'body1', color, fontWeight, fontSize, align = 'left', textTransform = 'none', lineHeight, letterSpacing, style, children, ...props }) => {
31
+ const { theme } = useSTheme();
32
+ const computedStyle = useMemo(() => {
33
+ const variantStyle = theme.typography.scale[variant];
34
+ const weight = fontWeight || variantStyle.fontWeight;
35
+ // Apply default color for caption variant (matches MUI theme behavior)
36
+ const resolvedColor = color !== undefined ? color : variant === 'caption' ? 'text.secondary' : undefined;
37
+ const baseStyle = {
38
+ fontSize: fontSize || variantStyle.fontSize,
39
+ fontWeight: weight,
40
+ fontFamily: getFontFamily(weight),
41
+ lineHeight: lineHeight
42
+ ? (fontSize || variantStyle.fontSize) * lineHeight
43
+ : (fontSize || variantStyle.fontSize) * (variantStyle.lineHeight || 1.5),
44
+ color: resolveColor(resolvedColor, theme),
45
+ textAlign: align,
46
+ textTransform,
47
+ };
48
+ if (letterSpacing !== undefined) {
49
+ baseStyle.letterSpacing = letterSpacing;
50
+ }
51
+ else if (variantStyle.letterSpacing !== undefined) {
52
+ baseStyle.letterSpacing = variantStyle.letterSpacing;
53
+ }
54
+ return baseStyle;
55
+ }, [variant, color, fontWeight, fontSize, align, textTransform, lineHeight, letterSpacing, theme]);
56
+ return (_jsx(Text, { style: [computedStyle, style], ...props, children: children }));
57
+ };
58
+ SText.displayName = 'SText';
59
+ export default SText;
@@ -0,0 +1,2 @@
1
+ export { STextField, type STextFieldProps, default } from './s-text-field';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/s-text-field/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC"}