@lion-npm/ui 0.0.0-stage → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.oxlintrc.json +8 -0
- package/AGENTS.md +0 -0
- package/Dockerfile +7 -0
- package/LICENSE +21 -0
- package/README.md +31 -2
- package/compose.yml +14 -0
- package/index.html +15 -0
- package/package.json +27 -4
- package/public/favicon.svg +1 -0
- package/public/icons.svg +24 -0
- package/src/App.tsx +84 -0
- package/src/assets/css/buttons/buttons-android.css +251 -0
- package/src/assets/css/buttons/buttons-ios.css +213 -0
- package/src/assets/css/buttons/buttons-web.css +138 -0
- package/src/assets/css/custom.css +8 -0
- package/src/assets/css/index.css +60 -0
- package/src/assets/css/inputs/inputs-android.css +259 -0
- package/src/assets/css/inputs/inputs-bootstrap.css +74 -0
- package/src/assets/css/inputs/inputs-ios.css +268 -0
- package/src/assets/css/loadings/loading-bar.css +86 -0
- package/src/assets/css/modals/modals.css +236 -0
- package/src/components/buttons/AndroidButton.tsx +43 -0
- package/src/components/buttons/BootstrapButton.tsx +50 -0
- package/src/components/buttons/IOSButton.tsx +42 -0
- package/src/components/buttons/LionButton.tsx +73 -0
- package/src/components/inputs/AndroidInput.tsx +158 -0
- package/src/components/inputs/BootstrapInput.tsx +106 -0
- package/src/components/inputs/IOSInput.tsx +163 -0
- package/src/components/inputs/LionInput.tsx +81 -0
- package/src/components/loadings/LoadingBar.tsx +21 -0
- package/src/components/modals/LionModal.tsx +155 -0
- package/src/context/LionUIComponentsContext.tsx +73 -0
- package/src/features/Header.tsx +83 -0
- package/src/features/VariantList.tsx +19 -0
- package/src/features/buttons/ButtonsSection.tsx +7 -0
- package/src/features/buttons/components/ButtonsAndroidSection.tsx +79 -0
- package/src/features/buttons/components/ButtonsBootstrapSection.tsx +77 -0
- package/src/features/buttons/components/ButtonsCustomSection.tsx +234 -0
- package/src/features/buttons/components/ButtonsIOSSection.tsx +78 -0
- package/src/features/inputs/InputsSection.tsx +7 -0
- package/src/features/inputs/components/InputsAndroidSection.tsx +155 -0
- package/src/features/inputs/components/InputsBootstrapSection.tsx +154 -0
- package/src/features/inputs/components/InputsCustomSection.tsx +234 -0
- package/src/features/inputs/components/InputsIOSSection.tsx +156 -0
- package/src/features/loadings/LoadingSection.tsx +5 -0
- package/src/features/loadings/components/LoadingBarSection.tsx +74 -0
- package/src/features/modals/ModalsSection.tsx +5 -0
- package/src/features/modals/components/ModalsAndroidSection.tsx +108 -0
- package/src/features/modals/components/ModalsDefaultForm.tsx +31 -0
- package/src/features/modals/components/ModalsDefaultInput.tsx +22 -0
- package/src/features/modals/components/ModalsIOSSection.tsx +106 -0
- package/src/features/modals/components/ModalsWebSection.tsx +104 -0
- package/src/index.ts +27 -0
- package/src/interfaces/LionUIComponentsProviderProps.tsx +7 -0
- package/src/interfaces/LionUIContextProps.tsx +6 -0
- package/src/interfaces/LionUIState.tsx +13 -0
- package/src/main.tsx +9 -0
- package/src/types/Android/AndroidInputShapeTypes.tsx +7 -0
- package/src/types/Android/AndroidInputStyleTypes.tsx +7 -0
- package/src/types/Android/AndroidShapeTypes.tsx +6 -0
- package/src/types/Android/AndroidStyleTypes.tsx +9 -0
- package/src/types/ButtonTypes.tsx +9 -0
- package/src/types/IOS/IOSInputShapeTypes.tsx +9 -0
- package/src/types/IOS/IOSInputStyleTypes.tsx +11 -0
- package/src/types/IOS/IOSShapeTypes.tsx +7 -0
- package/src/types/IOS/IOSStyleTypes.tsx +9 -0
- package/src/types/InputTypes.tsx +13 -0
- package/src/types/PlatformTypes.tsx +9 -0
- package/src/types/SizeTypes.tsx +11 -0
- package/src/types/VariantTypes.tsx +19 -0
- package/src/types/Web/BootstrapInputShapeTypes.tsx +9 -0
- package/src/types/Web/BootstrapInputStyleTypes.tsx +8 -0
- package/src/types/Web/BootstrapShapeTypes.tsx +8 -0
- package/src/types/Web/BootstrapStyleTypes.tsx +7 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +23 -0
- package/vite.config.ts +30 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { VariantTypes } from "../../types/VariantTypes.tsx";
|
|
3
|
+
import { SizeTypes } from "../../types/SizeTypes.tsx";
|
|
4
|
+
import { ButtonTypes } from "../../types/ButtonTypes.tsx";
|
|
5
|
+
import { BootstrapStyleTypes } from "../../types/Web/BootstrapStyleTypes.tsx";
|
|
6
|
+
import { BootstrapShapeTypes } from "../../types/Web/BootstrapShapeTypes.tsx";
|
|
7
|
+
|
|
8
|
+
export interface BootstrapButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" | "size"> {
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
variant?: VariantTypes;
|
|
11
|
+
styleType?: BootstrapStyleTypes;
|
|
12
|
+
shape?: BootstrapShapeTypes;
|
|
13
|
+
size?: SizeTypes;
|
|
14
|
+
type?: ButtonTypes;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function BootstrapButton({
|
|
19
|
+
children,
|
|
20
|
+
variant = "primary" as VariantTypes,
|
|
21
|
+
styleType = BootstrapStyleTypes.FILLED,
|
|
22
|
+
shape = BootstrapShapeTypes.STANDARD,
|
|
23
|
+
size = "md" as SizeTypes,
|
|
24
|
+
type = "button" as ButtonTypes,
|
|
25
|
+
className = "",
|
|
26
|
+
...props
|
|
27
|
+
}: BootstrapButtonProps) {
|
|
28
|
+
// Construct standard Bootstrap button classes
|
|
29
|
+
const stylePrefix = styleType === BootstrapStyleTypes.OUTLINED ? "btn-outline-" : "btn-";
|
|
30
|
+
const variantClass = variant ? `${stylePrefix}${variant}` : "";
|
|
31
|
+
const sizeClass = size && size !== "md" ? `btn-${size}` : "";
|
|
32
|
+
|
|
33
|
+
// Mapping custom shape types to Bootstrap utilities or extended CSS
|
|
34
|
+
const shapeClass =
|
|
35
|
+
shape === BootstrapShapeTypes.PILL
|
|
36
|
+
? "rounded-pill"
|
|
37
|
+
: shape === BootstrapShapeTypes.SQUARE
|
|
38
|
+
? "rounded-0"
|
|
39
|
+
: "";
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
type={type}
|
|
44
|
+
className={`btn ${variantClass} ${sizeClass} ${shapeClass} ${className}`.trim()}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
</button>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { VariantTypes } from "../../types/VariantTypes";
|
|
3
|
+
import { SizeTypes } from "../../types/SizeTypes";
|
|
4
|
+
import { ButtonTypes } from "../../types/ButtonTypes";
|
|
5
|
+
import { IOSStyleTypes } from "../../types/IOS/IOSStyleTypes";
|
|
6
|
+
import { IOSShapeTypes } from "../../types/IOS/IOSShapeTypes";
|
|
7
|
+
|
|
8
|
+
export interface IOSButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" | "size"> {
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
variant?: VariantTypes;
|
|
11
|
+
styleType?: IOSStyleTypes;
|
|
12
|
+
shape?: IOSShapeTypes;
|
|
13
|
+
size?: SizeTypes;
|
|
14
|
+
type?: ButtonTypes;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function IOSButton({
|
|
19
|
+
children,
|
|
20
|
+
variant = VariantTypes.PRIMARY,
|
|
21
|
+
styleType = IOSStyleTypes.FILLED,
|
|
22
|
+
shape = IOSShapeTypes.ROUNDED,
|
|
23
|
+
size = SizeTypes.MD,
|
|
24
|
+
type = ButtonTypes.BUTTON,
|
|
25
|
+
className = "",
|
|
26
|
+
...props
|
|
27
|
+
}: IOSButtonProps) {
|
|
28
|
+
// Construct dynamic iOS class names
|
|
29
|
+
const styleClass = `ios-btn-${styleType}`;
|
|
30
|
+
const shapeClass = `ios-btn-${shape}`;
|
|
31
|
+
const variantClass = variant ? `btn-${variant}` : "";
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<button
|
|
35
|
+
type={type}
|
|
36
|
+
className={`ios-btn ios-btn-${size} ${styleClass} ${shapeClass} ${variantClass} ${className}`.trim()}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<span className="ios-btn-content">{children}</span>
|
|
40
|
+
</button>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import { useLionUI } from '../../context/LionUIComponentsContext.tsx';
|
|
3
|
+
import { PlatformTypes } from "../../types/PlatformTypes.tsx";
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { SizeTypes } from "../../types/SizeTypes.tsx";
|
|
6
|
+
import type { AndroidStyleTypes } from "../../types/Android/AndroidStyleTypes.tsx";
|
|
7
|
+
import type { AndroidShapeTypes } from "../../types/Android/AndroidShapeTypes.tsx";
|
|
8
|
+
import type { BootstrapShapeTypes } from "../../types/Web/BootstrapShapeTypes.tsx";
|
|
9
|
+
import type { BootstrapStyleTypes } from "../../types/Web/BootstrapStyleTypes.tsx";
|
|
10
|
+
import type { IOSShapeTypes } from "../../types/IOS/IOSShapeTypes.tsx";
|
|
11
|
+
import type { IOSStyleTypes } from "../../types/IOS/IOSStyleTypes.tsx";
|
|
12
|
+
import AndroidButton from "./AndroidButton.tsx";
|
|
13
|
+
import IOSButton from "./IOSButton.tsx";
|
|
14
|
+
import BootstrapButton from "./BootstrapButton.tsx";
|
|
15
|
+
|
|
16
|
+
type PlatformMapping<T> = {
|
|
17
|
+
[PlatformTypes.ANDROID]?: T;
|
|
18
|
+
[PlatformTypes.IOS]?: T;
|
|
19
|
+
[PlatformTypes.WEB]?: T;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type LionButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
variant?: string;
|
|
25
|
+
platform?: string;
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
size?: SizeTypes.SM | SizeTypes.MD | SizeTypes.LG;
|
|
28
|
+
shape?: BootstrapShapeTypes | AndroidShapeTypes | IOSShapeTypes | string | PlatformMapping<BootstrapShapeTypes | AndroidShapeTypes | IOSShapeTypes | string>;
|
|
29
|
+
styleType?: BootstrapStyleTypes | AndroidStyleTypes | IOSStyleTypes | string | PlatformMapping<BootstrapStyleTypes | AndroidStyleTypes | IOSStyleTypes | string>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default function LionButton({
|
|
33
|
+
children,
|
|
34
|
+
shape,
|
|
35
|
+
styleType,
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
platform = null,
|
|
38
|
+
...props
|
|
39
|
+
}: LionButtonProps) {
|
|
40
|
+
const { state } = useLionUI();
|
|
41
|
+
|
|
42
|
+
const activePlatform = platform ?? state.platform;
|
|
43
|
+
|
|
44
|
+
const resolvePlatformProp = <T,>(propValue?: T | PlatformMapping<T>): T | undefined => {
|
|
45
|
+
if (typeof propValue === 'object' && propValue !== null) {
|
|
46
|
+
const map = propValue as PlatformMapping<T>;
|
|
47
|
+
return map[activePlatform as keyof PlatformMapping<T>] ?? map[activePlatform.toLowerCase() as keyof PlatformMapping<T>];
|
|
48
|
+
}
|
|
49
|
+
return propValue as T;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const resolvedShape = resolvePlatformProp(shape);
|
|
53
|
+
const resolvedStyleType = resolvePlatformProp(styleType);
|
|
54
|
+
|
|
55
|
+
const buttonProps = {
|
|
56
|
+
...props,
|
|
57
|
+
...(resolvedShape !== undefined && { shape: resolvedShape }),
|
|
58
|
+
...(resolvedStyleType !== undefined && { styleType: resolvedStyleType }),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
switch (activePlatform) {
|
|
62
|
+
case PlatformTypes.ANDROID:
|
|
63
|
+
return <AndroidButton {...(buttonProps as any)}>{children}</AndroidButton>;
|
|
64
|
+
|
|
65
|
+
case PlatformTypes.IOS:
|
|
66
|
+
return <IOSButton {...(buttonProps as any)}>{children}</IOSButton>;
|
|
67
|
+
|
|
68
|
+
case PlatformTypes.WEB:
|
|
69
|
+
case PlatformTypes.DESKTOP:
|
|
70
|
+
default:
|
|
71
|
+
return <BootstrapButton {...(buttonProps as any)}>{children}</BootstrapButton>;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { InputTypes } from "../../types/InputTypes";
|
|
2
|
+
import { AndroidInputStyleTypes } from "../../types/Android/AndroidInputStyleTypes.tsx";
|
|
3
|
+
import { AndroidInputShapeTypes } from "../../types/Android/AndroidInputShapeTypes.tsx";
|
|
4
|
+
import {type ChangeEvent, type InputHTMLAttributes, type ReactNode, useId, useState} from "react";
|
|
5
|
+
|
|
6
|
+
export interface AndroidInputProps
|
|
7
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "shape"> {
|
|
8
|
+
type?: InputTypes;
|
|
9
|
+
styleType?: AndroidInputStyleTypes;
|
|
10
|
+
shape?: AndroidInputShapeTypes;
|
|
11
|
+
variant?: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
helperText?: string;
|
|
15
|
+
error?: boolean | string;
|
|
16
|
+
startIcon?: ReactNode;
|
|
17
|
+
endIcon?: ReactNode;
|
|
18
|
+
prefixText?: string;
|
|
19
|
+
suffixText?: string;
|
|
20
|
+
counter?: boolean | number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function AndroidInput({
|
|
24
|
+
type = InputTypes.TEXT,
|
|
25
|
+
styleType = AndroidInputStyleTypes.OUTLINED,
|
|
26
|
+
shape = AndroidInputShapeTypes.NORMAL,
|
|
27
|
+
variant = "primary",
|
|
28
|
+
label,
|
|
29
|
+
required = false,
|
|
30
|
+
helperText,
|
|
31
|
+
error,
|
|
32
|
+
startIcon,
|
|
33
|
+
endIcon,
|
|
34
|
+
prefixText,
|
|
35
|
+
suffixText,
|
|
36
|
+
counter,
|
|
37
|
+
disabled = false,
|
|
38
|
+
readOnly = false,
|
|
39
|
+
value,
|
|
40
|
+
defaultValue,
|
|
41
|
+
onChange,
|
|
42
|
+
onFocus,
|
|
43
|
+
onBlur,
|
|
44
|
+
id,
|
|
45
|
+
className = "",
|
|
46
|
+
maxLength,
|
|
47
|
+
placeholder = " ",
|
|
48
|
+
...rest
|
|
49
|
+
}: AndroidInputProps) {
|
|
50
|
+
const generatedId = useId();
|
|
51
|
+
const inputId = id || generatedId;
|
|
52
|
+
|
|
53
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
54
|
+
const [internalValue, setInternalValue] = useState(value ?? defaultValue ?? "");
|
|
55
|
+
|
|
56
|
+
const hasError = Boolean(error);
|
|
57
|
+
const errorMessage = typeof error === "string" ? error : undefined;
|
|
58
|
+
|
|
59
|
+
const normalizedStyle = String(styleType).toLowerCase().replace(/_/g, "-");
|
|
60
|
+
const normalizedShape = String(shape).toLowerCase();
|
|
61
|
+
|
|
62
|
+
const containerClasses = [
|
|
63
|
+
"android-input-container",
|
|
64
|
+
`android-variant-${variant}`,
|
|
65
|
+
className,
|
|
66
|
+
].filter(Boolean).join(" ");
|
|
67
|
+
|
|
68
|
+
const wrapperClasses = [
|
|
69
|
+
"android-input-field-wrapper",
|
|
70
|
+
`android-style-${normalizedStyle}`,
|
|
71
|
+
`android-size-${normalizedShape}`,
|
|
72
|
+
isFocused ? "is-focused" : "",
|
|
73
|
+
hasError ? "has-error" : "",
|
|
74
|
+
disabled ? "is-disabled" : "",
|
|
75
|
+
readOnly ? "is-readonly" : "",
|
|
76
|
+
].filter(Boolean).join(" ");
|
|
77
|
+
|
|
78
|
+
const inputClasses = [
|
|
79
|
+
"android-input-element",
|
|
80
|
+
hasError ? "is-invalid" : "",
|
|
81
|
+
].filter(Boolean).join(" ");
|
|
82
|
+
|
|
83
|
+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
84
|
+
setInternalValue(e.target.value);
|
|
85
|
+
if (onChange) onChange(e);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleFocus = (e: FocusEvent) => {
|
|
89
|
+
setIsFocused(true);
|
|
90
|
+
if (onFocus) { // @ts-ignore
|
|
91
|
+
onFocus(e);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleBlur = (e: FocusEvent) => {
|
|
96
|
+
setIsFocused(false);
|
|
97
|
+
if (onBlur) { // @ts-ignore
|
|
98
|
+
onBlur(e);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const currentValue = value !== undefined ? value : internalValue;
|
|
103
|
+
const currentLength = String(currentValue || "").length;
|
|
104
|
+
const maxLen = typeof counter === "number" ? counter : maxLength;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className={containerClasses}>
|
|
108
|
+
<div className={wrapperClasses}>
|
|
109
|
+
{startIcon && <div className="android-input-start-icon">{startIcon}</div>}
|
|
110
|
+
{prefixText && <span className="android-input-prefix">{prefixText}</span>}
|
|
111
|
+
|
|
112
|
+
<input
|
|
113
|
+
id={inputId}
|
|
114
|
+
type={type}
|
|
115
|
+
value={currentValue}
|
|
116
|
+
required={required}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
readOnly={readOnly}
|
|
119
|
+
maxLength={maxLen}
|
|
120
|
+
placeholder={placeholder}
|
|
121
|
+
onChange={handleChange}
|
|
122
|
+
// @ts-ignore
|
|
123
|
+
onFocus={handleFocus}
|
|
124
|
+
// @ts-ignore
|
|
125
|
+
onBlur={handleBlur}
|
|
126
|
+
className={inputClasses}
|
|
127
|
+
{...rest}
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
{label && (
|
|
131
|
+
<label htmlFor={inputId} className="android-input-label">
|
|
132
|
+
{label}
|
|
133
|
+
{required && <span className="text-danger ms-1">*</span>}
|
|
134
|
+
</label>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{suffixText && <span className="android-input-suffix">{suffixText}</span>}
|
|
138
|
+
{endIcon && <div className="android-input-end-icon">{endIcon}</div>}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
{(helperText || errorMessage || counter) && (
|
|
142
|
+
<div className="android-input-subtext-container">
|
|
143
|
+
{hasError ? (
|
|
144
|
+
<div className="android-input-error-text">{errorMessage}</div>
|
|
145
|
+
) : helperText ? (
|
|
146
|
+
<div className="android-input-helper-text">{helperText}</div>
|
|
147
|
+
) : <div />}
|
|
148
|
+
|
|
149
|
+
{counter && (
|
|
150
|
+
<span className="android-input-counter">
|
|
151
|
+
{currentLength}{maxLen ? `/${maxLen}` : ""}
|
|
152
|
+
</span>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type InputHTMLAttributes, useId } from "react";
|
|
2
|
+
import { InputTypes } from "../../types/InputTypes";
|
|
3
|
+
import { BootstrapInputShapeTypes } from "../../types/Web/BootstrapInputShapeTypes.tsx";
|
|
4
|
+
import { BootstrapInputStyleTypes } from "../../types/Web/BootstrapInputStyleTypes.tsx";
|
|
5
|
+
|
|
6
|
+
export interface BootstrapInputProps
|
|
7
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "shape"> {
|
|
8
|
+
type?: InputTypes;
|
|
9
|
+
styleType?: BootstrapInputStyleTypes;
|
|
10
|
+
shape?: BootstrapInputShapeTypes;
|
|
11
|
+
variant?: string;
|
|
12
|
+
sizeType?: "sm" | "md" | "lg";
|
|
13
|
+
label?: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
helperText?: string;
|
|
16
|
+
error?: boolean | string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function BootstrapInput({
|
|
20
|
+
type = InputTypes.TEXT,
|
|
21
|
+
styleType = BootstrapInputStyleTypes.FLOATING,
|
|
22
|
+
shape = BootstrapInputShapeTypes.ROUNDED,
|
|
23
|
+
variant = "primary",
|
|
24
|
+
sizeType = "md",
|
|
25
|
+
label,
|
|
26
|
+
required = false,
|
|
27
|
+
helperText,
|
|
28
|
+
error,
|
|
29
|
+
disabled = false,
|
|
30
|
+
readOnly = false,
|
|
31
|
+
id,
|
|
32
|
+
className = "",
|
|
33
|
+
placeholder = " ",
|
|
34
|
+
...rest
|
|
35
|
+
}: BootstrapInputProps) {
|
|
36
|
+
const generatedId = useId();
|
|
37
|
+
const inputId = id || generatedId;
|
|
38
|
+
|
|
39
|
+
const hasError = Boolean(error);
|
|
40
|
+
const errorMessage = typeof error === "string" ? error : undefined;
|
|
41
|
+
|
|
42
|
+
const sizeClass = sizeType === "sm" ? "form-control-sm" : sizeType === "lg" ? "form-control-lg" : "";
|
|
43
|
+
const variantClass = `bootstrap-variant-${variant}`;
|
|
44
|
+
|
|
45
|
+
const inputClasses = [
|
|
46
|
+
"form-control",
|
|
47
|
+
shape,
|
|
48
|
+
sizeClass,
|
|
49
|
+
variantClass,
|
|
50
|
+
hasError ? "is-invalid" : "",
|
|
51
|
+
className,
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join(" ");
|
|
55
|
+
|
|
56
|
+
const isFloating = styleType === BootstrapInputStyleTypes.FLOATING;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="mb-3 w-100">
|
|
60
|
+
{!isFloating && label && (
|
|
61
|
+
<label htmlFor={inputId} className="form-label">
|
|
62
|
+
{label}
|
|
63
|
+
{required && <span className="text-danger ms-1">*</span>}
|
|
64
|
+
</label>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{isFloating ? (
|
|
68
|
+
<div className="form-floating">
|
|
69
|
+
<input
|
|
70
|
+
id={inputId}
|
|
71
|
+
type={type}
|
|
72
|
+
className={inputClasses}
|
|
73
|
+
placeholder={placeholder}
|
|
74
|
+
disabled={disabled}
|
|
75
|
+
readOnly={readOnly}
|
|
76
|
+
required={required}
|
|
77
|
+
{...rest}
|
|
78
|
+
/>
|
|
79
|
+
{label && (
|
|
80
|
+
<label htmlFor={inputId}>
|
|
81
|
+
{label}
|
|
82
|
+
{required && <span className="text-danger ms-1">*</span>}
|
|
83
|
+
</label>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
) : (
|
|
87
|
+
<input
|
|
88
|
+
id={inputId}
|
|
89
|
+
type={type}
|
|
90
|
+
className={inputClasses}
|
|
91
|
+
placeholder={placeholder}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
readOnly={readOnly}
|
|
94
|
+
required={required}
|
|
95
|
+
{...rest}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{hasError ? (
|
|
100
|
+
<div className="invalid-feedback d-block">{errorMessage}</div>
|
|
101
|
+
) : helperText ? (
|
|
102
|
+
<div className="form-text">{helperText}</div>
|
|
103
|
+
) : null}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React, { useId, useState } from "react";
|
|
2
|
+
import { InputTypes } from "../../types/InputTypes";
|
|
3
|
+
import { IOSInputStyleTypes } from "../../types/IOS/IOSInputStyleTypes.tsx";
|
|
4
|
+
import { IOSInputShapeTypes } from "../../types/IOS/IOSInputShapeTypes.tsx";
|
|
5
|
+
|
|
6
|
+
export interface IOSInputProps
|
|
7
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "shape"> {
|
|
8
|
+
type?: InputTypes;
|
|
9
|
+
styleType?: IOSInputStyleTypes;
|
|
10
|
+
shape?: IOSInputShapeTypes;
|
|
11
|
+
variant?: string;
|
|
12
|
+
sizeType?: "sx" | "sm" | "md" | "lg";
|
|
13
|
+
label?: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
helperText?: string;
|
|
16
|
+
error?: boolean | string;
|
|
17
|
+
startIcon?: React.ReactNode;
|
|
18
|
+
endIcon?: React.ReactNode;
|
|
19
|
+
prefixText?: string;
|
|
20
|
+
suffixText?: string;
|
|
21
|
+
clearable?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function IOSInput({
|
|
25
|
+
type = InputTypes.TEXT,
|
|
26
|
+
styleType = IOSInputStyleTypes.ROUNDED_BORDER,
|
|
27
|
+
shape = IOSInputShapeTypes.ROUNDED,
|
|
28
|
+
variant = "primary",
|
|
29
|
+
sizeType = "md",
|
|
30
|
+
label,
|
|
31
|
+
required = false,
|
|
32
|
+
helperText,
|
|
33
|
+
error,
|
|
34
|
+
startIcon,
|
|
35
|
+
endIcon,
|
|
36
|
+
prefixText,
|
|
37
|
+
suffixText,
|
|
38
|
+
clearable = false,
|
|
39
|
+
disabled = false,
|
|
40
|
+
readOnly = false,
|
|
41
|
+
value,
|
|
42
|
+
defaultValue,
|
|
43
|
+
onChange,
|
|
44
|
+
onFocus,
|
|
45
|
+
onBlur,
|
|
46
|
+
id,
|
|
47
|
+
className = "",
|
|
48
|
+
maxLength,
|
|
49
|
+
placeholder,
|
|
50
|
+
...rest
|
|
51
|
+
}: IOSInputProps) {
|
|
52
|
+
const generatedId = useId();
|
|
53
|
+
const inputId = id || generatedId;
|
|
54
|
+
|
|
55
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
56
|
+
const [internalValue, setInternalValue] = useState(value ?? defaultValue ?? "");
|
|
57
|
+
|
|
58
|
+
const hasError = Boolean(error);
|
|
59
|
+
const errorMessage = typeof error === "string" ? error : undefined;
|
|
60
|
+
|
|
61
|
+
const normalizedStyle = String(styleType).toLowerCase().replace(/_/g, "-");
|
|
62
|
+
const normalizedShape = String(shape).toLowerCase();
|
|
63
|
+
|
|
64
|
+
const containerClasses = [
|
|
65
|
+
"ios-input-container",
|
|
66
|
+
`ios-variant-${variant}`,
|
|
67
|
+
className,
|
|
68
|
+
]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join(" ");
|
|
71
|
+
|
|
72
|
+
const wrapperClasses = [
|
|
73
|
+
"ios-input-wrapper",
|
|
74
|
+
`ios-style-${normalizedStyle}`,
|
|
75
|
+
`ios-shape-${normalizedShape}`,
|
|
76
|
+
`ios-size-${sizeType}`,
|
|
77
|
+
isFocused ? "is-focused" : "",
|
|
78
|
+
hasError ? "has-error" : "",
|
|
79
|
+
disabled ? "is-disabled" : "",
|
|
80
|
+
readOnly ? "is-readonly" : "",
|
|
81
|
+
]
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.join(" ");
|
|
84
|
+
|
|
85
|
+
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
86
|
+
setIsFocused(true);
|
|
87
|
+
if (onFocus) onFocus(e);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
91
|
+
setIsFocused(false);
|
|
92
|
+
if (onBlur) onBlur(e);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
96
|
+
setInternalValue(e.target.value);
|
|
97
|
+
if (onChange) onChange(e);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleClear = () => {
|
|
101
|
+
setInternalValue("");
|
|
102
|
+
if (onChange) {
|
|
103
|
+
const event = {
|
|
104
|
+
target: { value: "" },
|
|
105
|
+
} as React.ChangeEvent<HTMLInputElement>;
|
|
106
|
+
onChange(event);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const currentValue = value !== undefined ? value : internalValue;
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className={containerClasses}>
|
|
114
|
+
{label && (
|
|
115
|
+
<label htmlFor={inputId} className="ios-input-label">
|
|
116
|
+
{label}
|
|
117
|
+
{required && <span className="text-danger ms-1">*</span>}
|
|
118
|
+
</label>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
<div className={wrapperClasses}>
|
|
122
|
+
{startIcon && <div className="ios-input-icon-left">{startIcon}</div>}
|
|
123
|
+
{prefixText && <span className="input-prefix">{prefixText}</span>}
|
|
124
|
+
|
|
125
|
+
<input
|
|
126
|
+
id={inputId}
|
|
127
|
+
type={type}
|
|
128
|
+
value={currentValue}
|
|
129
|
+
required={required}
|
|
130
|
+
disabled={disabled}
|
|
131
|
+
readOnly={readOnly}
|
|
132
|
+
maxLength={maxLength}
|
|
133
|
+
placeholder={placeholder}
|
|
134
|
+
onChange={handleChange}
|
|
135
|
+
onFocus={handleFocus}
|
|
136
|
+
onBlur={handleBlur}
|
|
137
|
+
className="ios-input-element"
|
|
138
|
+
{...rest}
|
|
139
|
+
/>
|
|
140
|
+
|
|
141
|
+
{clearable && Boolean(currentValue) && !disabled && !readOnly && (
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
className="ios-clear-button"
|
|
145
|
+
onClick={handleClear}
|
|
146
|
+
tabIndex={-1}
|
|
147
|
+
>
|
|
148
|
+
<i className="bi bi-x"></i>
|
|
149
|
+
</button>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{suffixText && <span className="input-suffix">{suffixText}</span>}
|
|
153
|
+
{endIcon && <div className="ios-input-icon-right">{endIcon}</div>}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{hasError ? (
|
|
157
|
+
<div className="ios-input-error-text">{errorMessage}</div>
|
|
158
|
+
) : helperText ? (
|
|
159
|
+
<div className="ios-input-helper-text">{helperText}</div>
|
|
160
|
+
) : null}
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type InputHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { useLionUI } from "../../context/LionUIComponentsContext.tsx";
|
|
3
|
+
import { PlatformTypes } from "../../types/PlatformTypes.tsx";
|
|
4
|
+
import type { AndroidStyleTypes } from "../../types/Android/AndroidStyleTypes.tsx";
|
|
5
|
+
import type { AndroidShapeTypes } from "../../types/Android/AndroidShapeTypes.tsx";
|
|
6
|
+
import type { BootstrapShapeTypes } from "../../types/Web/BootstrapShapeTypes.tsx";
|
|
7
|
+
import type { BootstrapStyleTypes } from "../../types/Web/BootstrapStyleTypes.tsx";
|
|
8
|
+
import type { IOSShapeTypes } from "../../types/IOS/IOSShapeTypes.tsx";
|
|
9
|
+
import type { IOSStyleTypes } from "../../types/IOS/IOSStyleTypes.tsx";
|
|
10
|
+
import AndroidInput from "./AndroidInput.tsx";
|
|
11
|
+
import IOSInput from "./IOSInput.tsx";
|
|
12
|
+
import BootstrapInput from "./BootstrapInput.tsx";
|
|
13
|
+
|
|
14
|
+
type PlatformMapping<T> = {
|
|
15
|
+
[PlatformTypes.ANDROID]?: T;
|
|
16
|
+
[PlatformTypes.IOS]?: T;
|
|
17
|
+
[PlatformTypes.WEB]?: T;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type LionInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "shape"> & {
|
|
21
|
+
variant?: string;
|
|
22
|
+
platform?: string;
|
|
23
|
+
sizeType?: string;
|
|
24
|
+
shape?: BootstrapShapeTypes | AndroidShapeTypes | IOSShapeTypes | string | PlatformMapping<BootstrapShapeTypes | AndroidShapeTypes | IOSShapeTypes | string>;
|
|
25
|
+
styleType?: BootstrapStyleTypes | AndroidStyleTypes | IOSStyleTypes | string | PlatformMapping<BootstrapStyleTypes | AndroidStyleTypes | IOSStyleTypes | string>;
|
|
26
|
+
label?: string;
|
|
27
|
+
required?: boolean;
|
|
28
|
+
helperText?: string;
|
|
29
|
+
error?: boolean | string;
|
|
30
|
+
startIcon?: ReactNode;
|
|
31
|
+
endIcon?: ReactNode;
|
|
32
|
+
prefixText?: string;
|
|
33
|
+
suffixText?: string;
|
|
34
|
+
counter?: boolean | number;
|
|
35
|
+
clearable?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default function LionInput({
|
|
39
|
+
shape,
|
|
40
|
+
styleType,
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
platform = null,
|
|
43
|
+
...props
|
|
44
|
+
}: LionInputProps) {
|
|
45
|
+
const { state } = useLionUI();
|
|
46
|
+
|
|
47
|
+
// Resolve active platform from props or global context
|
|
48
|
+
const activePlatform = platform ?? state.platform;
|
|
49
|
+
|
|
50
|
+
// Helper function to resolve platform-specific property mappings
|
|
51
|
+
const resolvePlatformProp = <T,>(propValue?: T | PlatformMapping<T>): T | undefined => {
|
|
52
|
+
if (typeof propValue === "object" && propValue !== null) {
|
|
53
|
+
const map = propValue as PlatformMapping<T>;
|
|
54
|
+
return map[activePlatform as keyof PlatformMapping<T>] ?? map[activePlatform.toLowerCase() as keyof PlatformMapping<T>];
|
|
55
|
+
}
|
|
56
|
+
return propValue as T;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const resolvedShape = resolvePlatformProp(shape);
|
|
60
|
+
const resolvedStyleType = resolvePlatformProp(styleType);
|
|
61
|
+
|
|
62
|
+
const inputProps = {
|
|
63
|
+
...props,
|
|
64
|
+
...(resolvedShape !== undefined && { shape: resolvedShape }),
|
|
65
|
+
...(resolvedStyleType !== undefined && { styleType: resolvedStyleType }),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Render the appropriate input component based on the active platform
|
|
69
|
+
switch (activePlatform) {
|
|
70
|
+
case PlatformTypes.ANDROID:
|
|
71
|
+
return <AndroidInput {...(inputProps as any)} />;
|
|
72
|
+
|
|
73
|
+
case PlatformTypes.IOS:
|
|
74
|
+
return <IOSInput {...(inputProps as any)} />;
|
|
75
|
+
|
|
76
|
+
case PlatformTypes.WEB:
|
|
77
|
+
case PlatformTypes.DESKTOP:
|
|
78
|
+
default:
|
|
79
|
+
return <BootstrapInput {...(inputProps as any)} />;
|
|
80
|
+
}
|
|
81
|
+
}
|