@lion-npm/ui 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.oxlintrc.json +8 -0
  2. package/AGENTS.md +0 -0
  3. package/Dockerfile +7 -0
  4. package/LICENSE +21 -0
  5. package/README.md +31 -2
  6. package/compose.yml +14 -0
  7. package/index.html +15 -0
  8. package/package.json +27 -4
  9. package/public/favicon.svg +1 -0
  10. package/public/icons.svg +24 -0
  11. package/src/App.tsx +81 -0
  12. package/src/assets/css/buttons/buttons-android.css +251 -0
  13. package/src/assets/css/buttons/buttons-ios.css +213 -0
  14. package/src/assets/css/buttons/buttons-web.css +138 -0
  15. package/src/assets/css/custom.css +8 -0
  16. package/src/assets/css/index.css +60 -0
  17. package/src/assets/css/inputs/inputs-android.css +259 -0
  18. package/src/assets/css/inputs/inputs-bootstrap.css +74 -0
  19. package/src/assets/css/inputs/inputs-ios.css +268 -0
  20. package/src/assets/css/loadings/loading-bar.css +86 -0
  21. package/src/assets/css/modals/modals.css +236 -0
  22. package/src/components/buttons/AndroidButton.tsx +43 -0
  23. package/src/components/buttons/BootstrapButton.tsx +50 -0
  24. package/src/components/buttons/IOSButton.tsx +42 -0
  25. package/src/components/buttons/LionButton.tsx +76 -0
  26. package/src/components/inputs/AndroidInput.tsx +158 -0
  27. package/src/components/inputs/BootstrapInput.tsx +106 -0
  28. package/src/components/inputs/IOSInput.tsx +163 -0
  29. package/src/components/loadings/LoadingBar.tsx +21 -0
  30. package/src/components/modals/LionModal.tsx +157 -0
  31. package/src/context/LionUIComponentsContext.tsx +65 -0
  32. package/src/features/Header.tsx +77 -0
  33. package/src/features/VariantList.tsx +19 -0
  34. package/src/features/buttons/ButtonsSection.tsx +7 -0
  35. package/src/features/buttons/components/ButtonsAndroidSection.tsx +79 -0
  36. package/src/features/buttons/components/ButtonsBootstrapSection.tsx +77 -0
  37. package/src/features/buttons/components/ButtonsCustomSection.tsx +234 -0
  38. package/src/features/buttons/components/ButtonsIOSSection.tsx +78 -0
  39. package/src/features/inputs/InputsSection.tsx +7 -0
  40. package/src/features/inputs/components/InputsAndroidSection.tsx +155 -0
  41. package/src/features/inputs/components/InputsBootstrapSection.tsx +154 -0
  42. package/src/features/inputs/components/InputsIOSSection.tsx +156 -0
  43. package/src/features/loadings/LoadingSection.tsx +5 -0
  44. package/src/features/loadings/components/LoadingBarSection.tsx +74 -0
  45. package/src/features/modals/ModalsSection.tsx +5 -0
  46. package/src/features/modals/components/ModalsAndroidSection.tsx +103 -0
  47. package/src/features/modals/components/ModalsDefaultForm.tsx +31 -0
  48. package/src/features/modals/components/ModalsDefaultInput.tsx +22 -0
  49. package/src/features/modals/components/ModalsIOSSection.tsx +106 -0
  50. package/src/features/modals/components/ModalsWebSection.tsx +99 -0
  51. package/src/interfaces/LionUIComponentsProviderProps.tsx +7 -0
  52. package/src/interfaces/LionUIContextProps.tsx +6 -0
  53. package/src/interfaces/LionUIState.tsx +8 -0
  54. package/src/main.tsx +9 -0
  55. package/src/types/Android/AndroidInputShapeTypes.tsx +7 -0
  56. package/src/types/Android/AndroidInputStyleTypes.tsx +7 -0
  57. package/src/types/Android/AndroidShapeTypes.tsx +6 -0
  58. package/src/types/Android/AndroidStyleTypes.tsx +9 -0
  59. package/src/types/ButtonTypes.tsx +9 -0
  60. package/src/types/IOS/IOSShapeTypes.tsx +7 -0
  61. package/src/types/IOS/IOSStyleTypes.tsx +9 -0
  62. package/src/types/IOS/iOSInputShapeTypes.tsx +9 -0
  63. package/src/types/IOS/iOSInputStyleTypes.tsx +11 -0
  64. package/src/types/InputTypes.tsx +13 -0
  65. package/src/types/PlatformTypes.tsx +9 -0
  66. package/src/types/SizeTypes.tsx +11 -0
  67. package/src/types/VariantTypes.tsx +19 -0
  68. package/src/types/Web/BootstrapInputShapeTypes.tsx +9 -0
  69. package/src/types/Web/BootstrapInputStyleTypes.tsx +8 -0
  70. package/src/types/Web/BootstrapShapeTypes.tsx +8 -0
  71. package/src/types/Web/BootstrapStyleTypes.tsx +7 -0
  72. package/tsconfig.app.json +26 -0
  73. package/tsconfig.json +7 -0
  74. package/tsconfig.node.json +23 -0
  75. package/vite.config.ts +11 -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.DEFAULT,
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,76 @@
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
+ android?: T;
21
+ ios?: T;
22
+ web?: T;
23
+ };
24
+
25
+ export type LionButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
26
+ children: ReactNode;
27
+ variant?: string;
28
+ platform?: string;
29
+ // @ts-ignore
30
+ size?: SizeTypes.SM | SizeTypes.MD | SizeTypes.LG;
31
+ shape?: BootstrapShapeTypes | AndroidShapeTypes | IOSShapeTypes | string | PlatformMapping<BootstrapShapeTypes | AndroidShapeTypes | IOSShapeTypes | string>;
32
+ styleType?: BootstrapStyleTypes | AndroidStyleTypes | IOSStyleTypes | string | PlatformMapping<BootstrapStyleTypes | AndroidStyleTypes | IOSStyleTypes | string>;
33
+ };
34
+
35
+ export default function LionButton({
36
+ children,
37
+ shape,
38
+ styleType,
39
+ // @ts-ignore
40
+ platform = null,
41
+ ...props
42
+ }: LionButtonProps) {
43
+ const { state } = useLionUI();
44
+
45
+ const activePlatform = platform ?? state.platform;
46
+
47
+ const resolvePlatformProp = <T,>(propValue?: T | PlatformMapping<T>): T | undefined => {
48
+ if (typeof propValue === 'object' && propValue !== null) {
49
+ const map = propValue as PlatformMapping<T>;
50
+ return map[activePlatform as keyof PlatformMapping<T>] ?? map[activePlatform.toLowerCase() as keyof PlatformMapping<T>];
51
+ }
52
+ return propValue as T;
53
+ };
54
+
55
+ const resolvedShape = resolvePlatformProp(shape);
56
+ const resolvedStyleType = resolvePlatformProp(styleType);
57
+
58
+ const buttonProps = {
59
+ ...props,
60
+ ...(resolvedShape !== undefined && { shape: resolvedShape }),
61
+ ...(resolvedStyleType !== undefined && { styleType: resolvedStyleType }),
62
+ };
63
+
64
+ switch (activePlatform) {
65
+ case PlatformTypes.ANDROID:
66
+ return <AndroidButton {...(buttonProps as any)}>{children}</AndroidButton>;
67
+
68
+ case PlatformTypes.IOS:
69
+ return <IOSButton {...(buttonProps as any)}>{children}</IOSButton>;
70
+
71
+ case PlatformTypes.WEB:
72
+ case PlatformTypes.DESKTOP:
73
+ default:
74
+ return <BootstrapButton {...(buttonProps as any)}>{children}</BootstrapButton>;
75
+ }
76
+ }
@@ -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 { 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,21 @@
1
+ import {useLionUI} from "../../context/LionUIComponentsContext.tsx";
2
+ import {VariantTypes} from "../../types/VariantTypes.tsx";
3
+
4
+ export interface LoadingBarProps {
5
+ variant?: VariantTypes | string;
6
+ }
7
+
8
+ export default function LoadingBar({ variant = VariantTypes.PRIMARY }: LoadingBarProps) {
9
+ const { state } = useLionUI();
10
+
11
+ const normalizedVariant = String(variant).toLowerCase().replace(/_/g, "-");
12
+ const variantClass = variant ? `loading-variant-${normalizedVariant}` : "";
13
+
14
+ return state.loading ? (
15
+ <div className="sticky-top">
16
+ <div className={`custom-progress-bar ${variantClass}`}>
17
+ <div className="indeterminate"></div>
18
+ </div>
19
+ </div>
20
+ ) : null;
21
+ }