velto-ui 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.
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports["default"] = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _reactNative = require("react-native");
10
+ var _theme = require("../../theme");
11
+ var _button = require("./button.config");
12
+ var _button2 = require("./button.utils");
13
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
14
+ var Button = function Button(_ref) {
15
+ var title = _ref.title,
16
+ _ref$variant = _ref.variant,
17
+ variant = _ref$variant === void 0 ? 'solid' : _ref$variant,
18
+ _ref$size = _ref.size,
19
+ size = _ref$size === void 0 ? 'md' : _ref$size,
20
+ _ref$intent = _ref.intent,
21
+ intent = _ref$intent === void 0 ? 'primary' : _ref$intent,
22
+ _ref$shape = _ref.shape,
23
+ shape = _ref$shape === void 0 ? 'rounded' : _ref$shape,
24
+ _ref$loading = _ref.loading,
25
+ loading = _ref$loading === void 0 ? false : _ref$loading,
26
+ loadingText = _ref.loadingText,
27
+ _ref$loadingPosition = _ref.loadingPosition,
28
+ loadingPosition = _ref$loadingPosition === void 0 ? 'center' : _ref$loadingPosition,
29
+ _ref$disabled = _ref.disabled,
30
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
31
+ _ref$fullWidth = _ref.fullWidth,
32
+ fullWidth = _ref$fullWidth === void 0 ? false : _ref$fullWidth,
33
+ _ref$textTransform = _ref.textTransform,
34
+ textTransform = _ref$textTransform === void 0 ? 'none' : _ref$textTransform,
35
+ bg = _ref.bg,
36
+ textColor = _ref.textColor,
37
+ disabledBg = _ref.disabledBg,
38
+ disabledTextColor = _ref.disabledTextColor,
39
+ borderRadius = _ref.borderRadius,
40
+ paddingVertical = _ref.paddingVertical,
41
+ paddingHorizontal = _ref.paddingHorizontal,
42
+ leftIcon = _ref.leftIcon,
43
+ rightIcon = _ref.rightIcon,
44
+ _style = _ref.style,
45
+ textStyle = _ref.textStyle,
46
+ sx = _ref.sx,
47
+ onPress = _ref.onPress;
48
+ var _useTheme = (0, _theme.useTheme)(),
49
+ theme = _useTheme.theme;
50
+ var colors = theme.colors;
51
+
52
+ // resolve size, intent and variant styles based on props
53
+ var sizeConfig = _button.BUTTON_SIZES[size] || _button.BUTTON_SIZES.md;
54
+ var intentColor = (0, _button2.getIntentColor)(intent, colors);
55
+ var variantStyle = (0, _button2.getVariantStyle)(variant, intentColor);
56
+
57
+ // button becomes non-clickable when disabled or loading
58
+ var isDisabled = disabled || loading;
59
+
60
+ // only apply disabled styling when explicitly disabled (not during loading)
61
+ var showDisabledStyle = disabled;
62
+
63
+ // final background color with override priority
64
+ var finalBg = bg !== null && bg !== void 0 ? bg : showDisabledStyle ? disabledBg || colors.disabled : variantStyle.backgroundColor;
65
+
66
+ // final text color with override priority
67
+ var finalTextColor = textColor !== null && textColor !== void 0 ? textColor : showDisabledStyle ? disabledTextColor || '#888' : variantStyle.textColor;
68
+
69
+ // padding controls actual button size (not just text)
70
+ var finalPV = paddingVertical !== null && paddingVertical !== void 0 ? paddingVertical : sizeConfig.pv;
71
+ var finalPH = paddingHorizontal !== null && paddingHorizontal !== void 0 ? paddingHorizontal : sizeConfig.ph;
72
+
73
+ // pill shape only affects border radius, not height
74
+ var finalRadius = borderRadius !== null && borderRadius !== void 0 ? borderRadius : shape === 'pill' ? 999 : _button.BUTTON_SHAPES[shape] || _button.BUTTON_SHAPES.rounded;
75
+
76
+ // switch text when loadingText is provided
77
+ var finalText = loading && loadingText ? loadingText : title;
78
+ return /*#__PURE__*/_react["default"].createElement(_reactNative.Pressable, {
79
+ onPress: onPress,
80
+ disabled: isDisabled,
81
+ android_ripple: _reactNative.Platform.OS === 'android' ? {
82
+ color: '#ccc'
83
+ } : undefined,
84
+ style: function style(_ref2) {
85
+ var pressed = _ref2.pressed;
86
+ return [styles.base, {
87
+ backgroundColor: finalBg,
88
+ borderWidth: variantStyle.borderWidth || 0,
89
+ borderColor: variantStyle.borderColor || 'transparent',
90
+ paddingVertical: finalPV,
91
+ paddingHorizontal: finalPH,
92
+ borderRadius: finalRadius,
93
+ // handles both normal and full width layouts
94
+ alignSelf: fullWidth ? 'stretch' : 'flex-start',
95
+ width: fullWidth ? '100%' : undefined,
96
+ opacity: pressed ? 0.85 : 1
97
+ }, sx, _style];
98
+ }
99
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
100
+ style: styles.row
101
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
102
+ style: styles.side
103
+ }, loading && loadingPosition === 'left' ? /*#__PURE__*/_react["default"].createElement(_reactNative.ActivityIndicator, {
104
+ color: finalTextColor
105
+ }) : !loading && leftIcon), /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
106
+ style: styles.center
107
+ }, loading && loadingPosition === 'center' ? /*#__PURE__*/_react["default"].createElement(_reactNative.ActivityIndicator, {
108
+ color: finalTextColor
109
+ }) : /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
110
+ numberOfLines: 1,
111
+ style: [styles.text, {
112
+ color: finalTextColor,
113
+ fontSize: sizeConfig.fontSize,
114
+ textTransform: textTransform
115
+ }, textStyle]
116
+ }, finalText)), /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
117
+ style: styles.side
118
+ }, loading && loadingPosition === 'right' ? /*#__PURE__*/_react["default"].createElement(_reactNative.ActivityIndicator, {
119
+ color: finalTextColor
120
+ }) : !loading && rightIcon)));
121
+ };
122
+ var _default = exports["default"] = /*#__PURE__*/(0, _react.memo)(Button);
123
+ var styles = _reactNative.StyleSheet.create({
124
+ base: {
125
+ justifyContent: 'center',
126
+ alignItems: 'center'
127
+ },
128
+ row: {
129
+ flexDirection: 'row',
130
+ alignItems: 'center'
131
+ },
132
+ side: {
133
+ minWidth: 24,
134
+ alignItems: 'center'
135
+ },
136
+ center: {
137
+ marginHorizontal: 6
138
+ },
139
+ text: {
140
+ fontWeight: '600'
141
+ }
142
+ });
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BUTTON_VARIANTS = exports.BUTTON_SIZES = exports.BUTTON_SHAPES = void 0;
7
+ var BUTTON_VARIANTS = exports.BUTTON_VARIANTS = ['solid', 'outline', 'ghost', 'soft', 'link'];
8
+ var BUTTON_SIZES = exports.BUTTON_SIZES = {
9
+ xs: {
10
+ pv: 6,
11
+ ph: 10,
12
+ fontSize: 10
13
+ },
14
+ sm: {
15
+ pv: 8,
16
+ ph: 12,
17
+ fontSize: 12
18
+ },
19
+ md: {
20
+ pv: 12,
21
+ ph: 16,
22
+ fontSize: 16
23
+ },
24
+ lg: {
25
+ pv: 16,
26
+ ph: 20,
27
+ fontSize: 18
28
+ },
29
+ xl: {
30
+ pv: 20,
31
+ ph: 24,
32
+ fontSize: 20
33
+ },
34
+ icon: {
35
+ pv: 10,
36
+ ph: 10,
37
+ fontSize: 16
38
+ }
39
+ };
40
+ var BUTTON_SHAPES = exports.BUTTON_SHAPES = {
41
+ rounded: 10,
42
+ pill: 999,
43
+ square: 2
44
+ };
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getVariantStyle = exports.getIntentColor = void 0;
7
+ var getIntentColor = exports.getIntentColor = function getIntentColor(intent, colors) {
8
+ var map = {
9
+ primary: colors.primary,
10
+ success: colors.secondary,
11
+ error: colors.danger,
12
+ warning: '#FFA500',
13
+ info: '#3B82F6'
14
+ };
15
+ return map[intent] || colors.primary;
16
+ };
17
+ var getVariantStyle = exports.getVariantStyle = function getVariantStyle(variant, intentColor) {
18
+ switch (variant) {
19
+ case 'outline':
20
+ return {
21
+ backgroundColor: 'transparent',
22
+ borderColor: intentColor,
23
+ borderWidth: 1,
24
+ textColor: intentColor
25
+ };
26
+ case 'ghost':
27
+ return {
28
+ backgroundColor: 'transparent',
29
+ textColor: intentColor
30
+ };
31
+ case 'soft':
32
+ return {
33
+ backgroundColor: intentColor + '20',
34
+ textColor: intentColor
35
+ };
36
+ case 'link':
37
+ return {
38
+ backgroundColor: 'transparent',
39
+ textColor: intentColor
40
+ };
41
+ case 'solid':
42
+ default:
43
+ return {
44
+ backgroundColor: intentColor,
45
+ textColor: '#fff'
46
+ };
47
+ }
48
+ };
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports["default"] = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _reactNative = require("react-native");
10
+ var _theme = require("../../theme");
11
+ var _input = require("./input.config");
12
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
13
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
14
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
15
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
16
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
17
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
18
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
19
+ var Input = function Input(_ref) {
20
+ var value = _ref.value,
21
+ onChangeText = _ref.onChangeText,
22
+ label = _ref.label,
23
+ placeholder = _ref.placeholder,
24
+ helperText = _ref.helperText,
25
+ error = _ref.error,
26
+ success = _ref.success,
27
+ _ref$variant = _ref.variant,
28
+ variant = _ref$variant === void 0 ? 'outline' : _ref$variant,
29
+ _ref$size = _ref.size,
30
+ size = _ref$size === void 0 ? 'md' : _ref$size,
31
+ _ref$disabled = _ref.disabled,
32
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
33
+ _ref$readOnly = _ref.readOnly,
34
+ readOnly = _ref$readOnly === void 0 ? false : _ref$readOnly,
35
+ _ref$secureTextEntry = _ref.secureTextEntry,
36
+ secureTextEntry = _ref$secureTextEntry === void 0 ? false : _ref$secureTextEntry,
37
+ _ref$multiline = _ref.multiline,
38
+ multiline = _ref$multiline === void 0 ? false : _ref$multiline,
39
+ _ref$clearable = _ref.clearable,
40
+ clearable = _ref$clearable === void 0 ? false : _ref$clearable,
41
+ bg = _ref.bg,
42
+ textColor = _ref.textColor,
43
+ borderRadius = _ref.borderRadius,
44
+ paddingVertical = _ref.paddingVertical,
45
+ paddingHorizontal = _ref.paddingHorizontal,
46
+ style = _ref.style,
47
+ inputStyle = _ref.inputStyle;
48
+ var _useTheme = (0, _theme.useTheme)(),
49
+ theme = _useTheme.theme;
50
+ var colors = theme.colors;
51
+ var _useState = (0, _react.useState)(false),
52
+ _useState2 = _slicedToArray(_useState, 2),
53
+ focused = _useState2[0],
54
+ setFocused = _useState2[1];
55
+
56
+ // password toggle state
57
+ var _useState3 = (0, _react.useState)(secureTextEntry),
58
+ _useState4 = _slicedToArray(_useState3, 2),
59
+ secure = _useState4[0],
60
+ setSecure = _useState4[1];
61
+
62
+ // ✅ sync with prop change (IMPORTANT FIX)
63
+ (0, _react.useEffect)(function () {
64
+ setSecure(secureTextEntry);
65
+ }, [secureTextEntry]);
66
+ var sizeConfig = _input.INPUT_SIZES[size] || _input.INPUT_SIZES.md;
67
+
68
+ /* ---------------- STATE PRIORITY ---------------- */
69
+ var state = disabled ? 'disabled' : error ? 'error' : success ? 'success' : focused ? 'focus' : 'default';
70
+
71
+ /* ---------------- COLORS ---------------- */
72
+
73
+ var finalBorderColor = state === 'disabled' ? colors.border : state === 'error' ? colors.error : state === 'success' ? colors.success : state === 'focus' ? colors.primary : colors.border;
74
+ var backgroundColor = bg !== null && bg !== void 0 ? bg : variant === 'solid' ? colors.surface || colors.background : 'transparent';
75
+ var finalTextColor = textColor !== null && textColor !== void 0 ? textColor : disabled ? colors.textSecondary || '#999' : colors.text;
76
+ var finalPV = paddingVertical !== null && paddingVertical !== void 0 ? paddingVertical : sizeConfig.pv;
77
+ var finalPH = paddingHorizontal !== null && paddingHorizontal !== void 0 ? paddingHorizontal : sizeConfig.ph;
78
+ var showClear = clearable && (value === null || value === void 0 ? void 0 : value.length) > 0 && !disabled && !readOnly;
79
+
80
+ /* ---------------- VARIANT STYLE ---------------- */
81
+
82
+ var getVariantStyle = function getVariantStyle() {
83
+ switch (variant) {
84
+ case 'solid':
85
+ return {
86
+ borderWidth: 1
87
+ };
88
+ case 'outline':
89
+ return {
90
+ borderWidth: 1
91
+ };
92
+ case 'underline':
93
+ return {
94
+ borderBottomWidth: 1
95
+ };
96
+ case 'ghost':
97
+ return {
98
+ borderWidth: 0
99
+ };
100
+ default:
101
+ return {
102
+ borderWidth: 1
103
+ };
104
+ }
105
+ };
106
+
107
+ /* ---------------- RENDER ---------------- */
108
+
109
+ return /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
110
+ style: {
111
+ width: '100%'
112
+ }
113
+ }, label && /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
114
+ style: [styles.label, {
115
+ color: colors.text
116
+ }]
117
+ }, label), /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
118
+ style: [styles.wrapper, getVariantStyle(), {
119
+ backgroundColor: backgroundColor,
120
+ borderColor: finalBorderColor,
121
+ borderRadius: borderRadius !== null && borderRadius !== void 0 ? borderRadius : 8,
122
+ paddingVertical: finalPV,
123
+ paddingHorizontal: finalPH,
124
+ minHeight: multiline ? 80 : 44,
125
+ opacity: disabled ? 0.5 : 1
126
+ }, style]
127
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.TextInput, {
128
+ value: value,
129
+ onChangeText: onChangeText,
130
+ placeholder: placeholder,
131
+ editable: !disabled && !readOnly,
132
+ secureTextEntry: secure // ✅ controlled by state
133
+ ,
134
+ multiline: multiline,
135
+ placeholderTextColor: colors.border,
136
+ onFocus: function onFocus() {
137
+ return setFocused(true);
138
+ },
139
+ onBlur: function onBlur() {
140
+ return setFocused(false);
141
+ },
142
+ style: [styles.input, {
143
+ color: finalTextColor,
144
+ fontSize: sizeConfig.fontSize,
145
+ textAlignVertical: multiline ? 'top' : 'center'
146
+ }, inputStyle]
147
+ }), /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
148
+ style: styles.right
149
+ }, showClear && /*#__PURE__*/_react["default"].createElement(_reactNative.Pressable, {
150
+ onPress: function onPress() {
151
+ return onChangeText('');
152
+ }
153
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
154
+ style: {
155
+ color: colors.text
156
+ }
157
+ }, "\u2715")), secureTextEntry && /*#__PURE__*/_react["default"].createElement(_reactNative.Pressable, {
158
+ onPress: function onPress() {
159
+ return setSecure(function (prev) {
160
+ return !prev;
161
+ });
162
+ }
163
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
164
+ style: {
165
+ color: colors.text,
166
+ fontSize: 16
167
+ }
168
+ }, secure ? '👁️' : '🙈')))), (helperText || error) && /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
169
+ style: {
170
+ marginTop: 4,
171
+ fontSize: 12,
172
+ color: error ? colors.error : success ? colors.success : colors.textSecondary || colors.text
173
+ }
174
+ }, error || helperText));
175
+ };
176
+ var _default = exports["default"] = /*#__PURE__*/(0, _react.memo)(Input);
177
+ /* ---------------- STYLES ---------------- */
178
+ var styles = _reactNative.StyleSheet.create({
179
+ label: {
180
+ marginBottom: 4,
181
+ fontWeight: '500'
182
+ },
183
+ wrapper: {
184
+ flexDirection: 'row',
185
+ alignItems: 'center'
186
+ },
187
+ input: {
188
+ flex: 1
189
+ },
190
+ right: {
191
+ flexDirection: 'row',
192
+ alignItems: 'center',
193
+ gap: 6
194
+ }
195
+ });
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.INPUT_VARIANTS = exports.INPUT_SIZES = void 0;
7
+ var INPUT_SIZES = exports.INPUT_SIZES = {
8
+ xs: {
9
+ fontSize: 12,
10
+ pv: 6,
11
+ ph: 8
12
+ },
13
+ sm: {
14
+ fontSize: 14,
15
+ pv: 8,
16
+ ph: 10
17
+ },
18
+ md: {
19
+ fontSize: 16,
20
+ pv: 10,
21
+ ph: 12
22
+ },
23
+ lg: {
24
+ fontSize: 18,
25
+ pv: 12,
26
+ ph: 14
27
+ },
28
+ xl: {
29
+ fontSize: 20,
30
+ pv: 14,
31
+ ph: 16
32
+ }
33
+ };
34
+ var INPUT_VARIANTS = exports.INPUT_VARIANTS = {
35
+ solid: function solid(c) {
36
+ return {
37
+ backgroundColor: c.background,
38
+ borderWidth: 0
39
+ };
40
+ },
41
+ outline: function outline(c) {
42
+ return {
43
+ backgroundColor: 'transparent',
44
+ borderWidth: 1,
45
+ borderColor: c.border
46
+ };
47
+ },
48
+ underline: function underline(c) {
49
+ return {
50
+ backgroundColor: 'transparent',
51
+ borderBottomWidth: 1,
52
+ borderColor: c.border
53
+ };
54
+ },
55
+ ghost: function ghost() {
56
+ return {
57
+ backgroundColor: 'transparent',
58
+ borderWidth: 0
59
+ };
60
+ }
61
+ };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getStateColor = void 0;
7
+ var getStateColor = exports.getStateColor = function getStateColor(state, colors) {
8
+ switch (state) {
9
+ case 'error':
10
+ return colors.error;
11
+ case 'success':
12
+ return colors.success;
13
+ default:
14
+ return colors.border;
15
+ }
16
+ };
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ var _exportNames = {
7
+ Button: true,
8
+ Input: true
9
+ };
10
+ Object.defineProperty(exports, "Button", {
11
+ enumerable: true,
12
+ get: function get() {
13
+ return _Button["default"];
14
+ }
15
+ });
16
+ Object.defineProperty(exports, "Input", {
17
+ enumerable: true,
18
+ get: function get() {
19
+ return _Input["default"];
20
+ }
21
+ });
22
+ var _Button = _interopRequireDefault(require("./components/Button/Button"));
23
+ var _Input = _interopRequireDefault(require("./components/Input/Input"));
24
+ var _theme = require("./theme");
25
+ Object.keys(_theme).forEach(function (key) {
26
+ if (key === "default" || key === "__esModule") return;
27
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
28
+ if (key in exports && exports[key] === _theme[key]) return;
29
+ Object.defineProperty(exports, key, {
30
+ enumerable: true,
31
+ get: function get() {
32
+ return _theme[key];
33
+ }
34
+ });
35
+ });
36
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.useTheme = exports.ThemeProvider = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _light = require("./light");
10
+ var _dark = require("./dark");
11
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
12
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
13
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
14
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
15
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
16
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
17
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
18
+ var ThemeContext = /*#__PURE__*/(0, _react.createContext)();
19
+ var ThemeProvider = exports.ThemeProvider = function ThemeProvider(_ref) {
20
+ var children = _ref.children;
21
+ var _useState = (0, _react.useState)('light'),
22
+ _useState2 = _slicedToArray(_useState, 2),
23
+ mode = _useState2[0],
24
+ setMode = _useState2[1];
25
+ var theme = (0, _react.useMemo)(function () {
26
+ return mode === 'light' ? _light.lightTheme : _dark.darkTheme;
27
+ }, [mode]);
28
+ var toggleTheme = function toggleTheme() {
29
+ setMode(function (prev) {
30
+ return prev === 'light' ? 'dark' : 'light';
31
+ });
32
+ };
33
+ return /*#__PURE__*/_react["default"].createElement(ThemeContext.Provider, {
34
+ value: {
35
+ theme: theme,
36
+ mode: mode,
37
+ toggleTheme: toggleTheme
38
+ }
39
+ }, children);
40
+ };
41
+ var useTheme = exports.useTheme = function useTheme() {
42
+ var context = (0, _react.useContext)(ThemeContext);
43
+ if (!context) {
44
+ throw new Error('useTheme must be used inside ThemeProvider');
45
+ }
46
+ return context;
47
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.darkTheme = void 0;
7
+ var darkTheme = exports.darkTheme = {
8
+ mode: 'dark',
9
+ colors: {
10
+ primary: '#3b82f6',
11
+ background: '#111827',
12
+ surface: '#1f2937',
13
+ text: '#f9fafb',
14
+ textSecondary: '#9ca3af',
15
+ border: '#374151',
16
+ error: '#ef4444',
17
+ success: '#22c55e',
18
+ disabled: '#374151'
19
+ }
20
+ };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ var _ThemeProvider = require("./ThemeProvider");
7
+ Object.keys(_ThemeProvider).forEach(function (key) {
8
+ if (key === "default" || key === "__esModule") return;
9
+ if (key in exports && exports[key] === _ThemeProvider[key]) return;
10
+ Object.defineProperty(exports, key, {
11
+ enumerable: true,
12
+ get: function get() {
13
+ return _ThemeProvider[key];
14
+ }
15
+ });
16
+ });
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.lightTheme = void 0;
7
+ var lightTheme = exports.lightTheme = {
8
+ mode: 'light',
9
+ colors: {
10
+ primary: '#2563eb',
11
+ background: '#ffffff',
12
+ surface: '#f9fafb',
13
+ text: '#111827',
14
+ textSecondary: '#6b7280',
15
+ border: '#d1d5db',
16
+ error: '#dc2626',
17
+ success: '#16a34a',
18
+ disabled: '#e5e7eb'
19
+ }
20
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "velto-ui",
3
+ "version": "1.0.0",
4
+ "description": "VeltoUI - React Native UI component library",
5
+ "main": "dist/index.js",
6
+ "react-native": "src/index.js",
7
+ "author": "Kanhu Charan Sahoo",
8
+ "license": "MIT",
9
+ "keywords": [
10
+ "react-native",
11
+ "ui",
12
+ "components",
13
+ "design-system",
14
+ "velto-ui"
15
+ ],
16
+ "peerDependencies": {
17
+ "react": ">=16.8.0",
18
+ "react-native": ">=0.60.0"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src"
23
+ ],
24
+ "scripts": {
25
+ "build": "babel src -d dist"
26
+ },
27
+ "devDependencies": {
28
+ "@babel/cli": "^7.29.7",
29
+ "@babel/core": "^7.29.7",
30
+ "@babel/preset-env": "^7.29.7",
31
+ "@babel/preset-react": "^7.29.7"
32
+ }
33
+ }
@@ -0,0 +1,165 @@
1
+ import React, { memo } from 'react';
2
+ import { Pressable, Text, ActivityIndicator, View, StyleSheet, Platform } from 'react-native';
3
+ import { useTheme } from '../../theme';
4
+ import { BUTTON_SIZES, BUTTON_SHAPES } from './button.config';
5
+ import { getIntentColor, getVariantStyle } from './button.utils';
6
+
7
+ const Button = ({
8
+ title,
9
+ variant = 'solid',
10
+ size = 'md',
11
+ intent = 'primary',
12
+ shape = 'rounded',
13
+
14
+ loading = false,
15
+ loadingText,
16
+ loadingPosition = 'center',
17
+ disabled = false,
18
+
19
+ fullWidth = false,
20
+ textTransform = 'none',
21
+
22
+ bg,
23
+ textColor,
24
+ disabledBg,
25
+ disabledTextColor,
26
+ borderRadius,
27
+ paddingVertical,
28
+ paddingHorizontal,
29
+
30
+ leftIcon,
31
+ rightIcon,
32
+
33
+ style,
34
+ textStyle,
35
+ sx,
36
+
37
+ onPress,
38
+ }) => {
39
+ const { theme } = useTheme();
40
+ const colors = theme.colors;
41
+
42
+ // resolve size, intent and variant styles based on props
43
+ const sizeConfig = BUTTON_SIZES[size] || BUTTON_SIZES.md;
44
+ const intentColor = getIntentColor(intent, colors);
45
+ const variantStyle = getVariantStyle(variant, intentColor);
46
+
47
+ // button becomes non-clickable when disabled or loading
48
+ const isDisabled = disabled || loading;
49
+
50
+ // only apply disabled styling when explicitly disabled (not during loading)
51
+ const showDisabledStyle = disabled;
52
+
53
+ // final background color with override priority
54
+ const finalBg =
55
+ bg ?? (showDisabledStyle ? disabledBg || colors.disabled : variantStyle.backgroundColor);
56
+
57
+ // final text color with override priority
58
+ const finalTextColor =
59
+ textColor ?? (showDisabledStyle ? disabledTextColor || '#888' : variantStyle.textColor);
60
+
61
+ // padding controls actual button size (not just text)
62
+ const finalPV = paddingVertical ?? sizeConfig.pv;
63
+ const finalPH = paddingHorizontal ?? sizeConfig.ph;
64
+
65
+ // pill shape only affects border radius, not height
66
+ const finalRadius =
67
+ borderRadius ?? (shape === 'pill' ? 999 : BUTTON_SHAPES[shape] || BUTTON_SHAPES.rounded);
68
+
69
+ // switch text when loadingText is provided
70
+ const finalText = loading && loadingText ? loadingText : title;
71
+
72
+ return (
73
+ <Pressable
74
+ onPress={onPress}
75
+ disabled={isDisabled}
76
+ android_ripple={Platform.OS === 'android' ? { color: '#ccc' } : undefined}
77
+ style={({ pressed }) => [
78
+ styles.base,
79
+ {
80
+ backgroundColor: finalBg,
81
+ borderWidth: variantStyle.borderWidth || 0,
82
+ borderColor: variantStyle.borderColor || 'transparent',
83
+
84
+ paddingVertical: finalPV,
85
+ paddingHorizontal: finalPH,
86
+
87
+ borderRadius: finalRadius,
88
+
89
+ // handles both normal and full width layouts
90
+ alignSelf: fullWidth ? 'stretch' : 'flex-start',
91
+ width: fullWidth ? '100%' : undefined,
92
+
93
+ opacity: pressed ? 0.85 : 1,
94
+ },
95
+ sx,
96
+ style,
97
+ ]}
98
+ >
99
+ <View style={styles.row}>
100
+ {/* left side: icon or loader */}
101
+ <View style={styles.side}>
102
+ {loading && loadingPosition === 'left' ? (
103
+ <ActivityIndicator color={finalTextColor} />
104
+ ) : (
105
+ !loading && leftIcon
106
+ )}
107
+ </View>
108
+
109
+ {/* center: text or loader */}
110
+ <View style={styles.center}>
111
+ {loading && loadingPosition === 'center' ? (
112
+ <ActivityIndicator color={finalTextColor} />
113
+ ) : (
114
+ <Text
115
+ numberOfLines={1}
116
+ style={[
117
+ styles.text,
118
+ {
119
+ color: finalTextColor,
120
+ fontSize: sizeConfig.fontSize,
121
+ textTransform,
122
+ },
123
+ textStyle,
124
+ ]}
125
+ >
126
+ {finalText}
127
+ </Text>
128
+ )}
129
+ </View>
130
+
131
+ {/* right side: icon or loader */}
132
+ <View style={styles.side}>
133
+ {loading && loadingPosition === 'right' ? (
134
+ <ActivityIndicator color={finalTextColor} />
135
+ ) : (
136
+ !loading && rightIcon
137
+ )}
138
+ </View>
139
+ </View>
140
+ </Pressable>
141
+ );
142
+ };
143
+
144
+ export default memo(Button);
145
+
146
+ const styles = StyleSheet.create({
147
+ base: {
148
+ justifyContent: 'center',
149
+ alignItems: 'center',
150
+ },
151
+ row: {
152
+ flexDirection: 'row',
153
+ alignItems: 'center',
154
+ },
155
+ side: {
156
+ minWidth: 24,
157
+ alignItems: 'center',
158
+ },
159
+ center: {
160
+ marginHorizontal: 6,
161
+ },
162
+ text: {
163
+ fontWeight: '600',
164
+ },
165
+ });
@@ -0,0 +1,16 @@
1
+ export const BUTTON_VARIANTS = ['solid', 'outline', 'ghost', 'soft', 'link'];
2
+
3
+ export const BUTTON_SIZES = {
4
+ xs: { pv: 6, ph: 10, fontSize: 10 },
5
+ sm: { pv: 8, ph: 12, fontSize: 12 },
6
+ md: { pv: 12, ph: 16, fontSize: 16 },
7
+ lg: { pv: 16, ph: 20, fontSize: 18 },
8
+ xl: { pv: 20, ph: 24, fontSize: 20 },
9
+ icon: { pv: 10, ph: 10, fontSize: 16 },
10
+ };
11
+
12
+ export const BUTTON_SHAPES = {
13
+ rounded: 10,
14
+ pill: 999,
15
+ square: 2,
16
+ };
@@ -0,0 +1,48 @@
1
+ export const getIntentColor = (intent, colors) => {
2
+ const map = {
3
+ primary: colors.primary,
4
+ success: colors.secondary,
5
+ error: colors.danger,
6
+ warning: '#FFA500',
7
+ info: '#3B82F6',
8
+ };
9
+
10
+ return map[intent] || colors.primary;
11
+ };
12
+
13
+ export const getVariantStyle = (variant, intentColor) => {
14
+ switch (variant) {
15
+ case 'outline':
16
+ return {
17
+ backgroundColor: 'transparent',
18
+ borderColor: intentColor,
19
+ borderWidth: 1,
20
+ textColor: intentColor,
21
+ };
22
+
23
+ case 'ghost':
24
+ return {
25
+ backgroundColor: 'transparent',
26
+ textColor: intentColor,
27
+ };
28
+
29
+ case 'soft':
30
+ return {
31
+ backgroundColor: intentColor + '20',
32
+ textColor: intentColor,
33
+ };
34
+
35
+ case 'link':
36
+ return {
37
+ backgroundColor: 'transparent',
38
+ textColor: intentColor,
39
+ };
40
+
41
+ case 'solid':
42
+ default:
43
+ return {
44
+ backgroundColor: intentColor,
45
+ textColor: '#fff',
46
+ };
47
+ }
48
+ };
@@ -0,0 +1,206 @@
1
+ import React, { useState, useEffect, memo } from 'react';
2
+ import { View, Text, TextInput, StyleSheet, Pressable } from 'react-native';
3
+ import { useTheme } from '../../theme';
4
+ import { INPUT_SIZES } from './input.config';
5
+
6
+ const Input = ({
7
+ value,
8
+ onChangeText,
9
+
10
+ label,
11
+ placeholder,
12
+ helperText,
13
+ error,
14
+ success,
15
+
16
+ variant = 'outline',
17
+ size = 'md',
18
+
19
+ disabled = false,
20
+ readOnly = false,
21
+
22
+ secureTextEntry = false,
23
+ multiline = false,
24
+
25
+ clearable = false,
26
+
27
+ bg,
28
+ textColor,
29
+ borderRadius,
30
+ paddingVertical,
31
+ paddingHorizontal,
32
+
33
+ style,
34
+ inputStyle,
35
+ }) => {
36
+ const { theme } = useTheme();
37
+ const colors = theme.colors;
38
+
39
+ const [focused, setFocused] = useState(false);
40
+
41
+ // password toggle state
42
+ const [secure, setSecure] = useState(secureTextEntry);
43
+
44
+ // ✅ sync with prop change (IMPORTANT FIX)
45
+ useEffect(() => {
46
+ setSecure(secureTextEntry);
47
+ }, [secureTextEntry]);
48
+
49
+ const sizeConfig = INPUT_SIZES[size] || INPUT_SIZES.md;
50
+
51
+ /* ---------------- STATE PRIORITY ---------------- */
52
+ const state = disabled
53
+ ? 'disabled'
54
+ : error
55
+ ? 'error'
56
+ : success
57
+ ? 'success'
58
+ : focused
59
+ ? 'focus'
60
+ : 'default';
61
+
62
+ /* ---------------- COLORS ---------------- */
63
+
64
+ const finalBorderColor =
65
+ state === 'disabled'
66
+ ? colors.border
67
+ : state === 'error'
68
+ ? colors.error
69
+ : state === 'success'
70
+ ? colors.success
71
+ : state === 'focus'
72
+ ? colors.primary
73
+ : colors.border;
74
+
75
+ const backgroundColor =
76
+ bg ?? (variant === 'solid' ? colors.surface || colors.background : 'transparent');
77
+
78
+ const finalTextColor = textColor ?? (disabled ? colors.textSecondary || '#999' : colors.text);
79
+
80
+ const finalPV = paddingVertical ?? sizeConfig.pv;
81
+ const finalPH = paddingHorizontal ?? sizeConfig.ph;
82
+
83
+ const showClear = clearable && value?.length > 0 && !disabled && !readOnly;
84
+
85
+ /* ---------------- VARIANT STYLE ---------------- */
86
+
87
+ const getVariantStyle = () => {
88
+ switch (variant) {
89
+ case 'solid':
90
+ return { borderWidth: 1 };
91
+ case 'outline':
92
+ return { borderWidth: 1 };
93
+ case 'underline':
94
+ return { borderBottomWidth: 1 };
95
+ case 'ghost':
96
+ return { borderWidth: 0 };
97
+ default:
98
+ return { borderWidth: 1 };
99
+ }
100
+ };
101
+
102
+ /* ---------------- RENDER ---------------- */
103
+
104
+ return (
105
+ <View style={{ width: '100%' }}>
106
+ {/* LABEL */}
107
+ {label && <Text style={[styles.label, { color: colors.text }]}>{label}</Text>}
108
+
109
+ {/* INPUT */}
110
+ <View
111
+ style={[
112
+ styles.wrapper,
113
+ getVariantStyle(),
114
+ {
115
+ backgroundColor,
116
+ borderColor: finalBorderColor,
117
+ borderRadius: borderRadius ?? 8,
118
+ paddingVertical: finalPV,
119
+ paddingHorizontal: finalPH,
120
+ minHeight: multiline ? 80 : 44,
121
+ opacity: disabled ? 0.5 : 1,
122
+ },
123
+ style,
124
+ ]}
125
+ >
126
+ <TextInput
127
+ value={value}
128
+ onChangeText={onChangeText}
129
+ placeholder={placeholder}
130
+ editable={!disabled && !readOnly}
131
+ secureTextEntry={secure} // ✅ controlled by state
132
+ multiline={multiline}
133
+ placeholderTextColor={colors.border}
134
+ onFocus={() => setFocused(true)}
135
+ onBlur={() => setFocused(false)}
136
+ style={[
137
+ styles.input,
138
+ {
139
+ color: finalTextColor,
140
+ fontSize: sizeConfig.fontSize,
141
+ textAlignVertical: multiline ? 'top' : 'center',
142
+ },
143
+ inputStyle,
144
+ ]}
145
+ />
146
+
147
+ {/* RIGHT SIDE ACTIONS */}
148
+ <View style={styles.right}>
149
+ {/* Clear button */}
150
+ {showClear && (
151
+ <Pressable onPress={() => onChangeText('')}>
152
+ <Text style={{ color: colors.text }}>✕</Text>
153
+ </Pressable>
154
+ )}
155
+
156
+ {/* Password toggle */}
157
+ {secureTextEntry && (
158
+ <Pressable onPress={() => setSecure((prev) => !prev)}>
159
+ <Text style={{ color: colors.text, fontSize: 16 }}>{secure ? '👁️' : '🙈'}</Text>
160
+ </Pressable>
161
+ )}
162
+ </View>
163
+ </View>
164
+
165
+ {/* HELPER / ERROR */}
166
+ {(helperText || error) && (
167
+ <Text
168
+ style={{
169
+ marginTop: 4,
170
+ fontSize: 12,
171
+ color: error
172
+ ? colors.error
173
+ : success
174
+ ? colors.success
175
+ : colors.textSecondary || colors.text,
176
+ }}
177
+ >
178
+ {error || helperText}
179
+ </Text>
180
+ )}
181
+ </View>
182
+ );
183
+ };
184
+
185
+ export default memo(Input);
186
+
187
+ /* ---------------- STYLES ---------------- */
188
+
189
+ const styles = StyleSheet.create({
190
+ label: {
191
+ marginBottom: 4,
192
+ fontWeight: '500',
193
+ },
194
+ wrapper: {
195
+ flexDirection: 'row',
196
+ alignItems: 'center',
197
+ },
198
+ input: {
199
+ flex: 1,
200
+ },
201
+ right: {
202
+ flexDirection: 'row',
203
+ alignItems: 'center',
204
+ gap: 6,
205
+ },
206
+ });
@@ -0,0 +1,28 @@
1
+ export const INPUT_SIZES = {
2
+ xs: { fontSize: 12, pv: 6, ph: 8 },
3
+ sm: { fontSize: 14, pv: 8, ph: 10 },
4
+ md: { fontSize: 16, pv: 10, ph: 12 },
5
+ lg: { fontSize: 18, pv: 12, ph: 14 },
6
+ xl: { fontSize: 20, pv: 14, ph: 16 },
7
+ };
8
+
9
+ export const INPUT_VARIANTS = {
10
+ solid: (c) => ({
11
+ backgroundColor: c.background,
12
+ borderWidth: 0,
13
+ }),
14
+ outline: (c) => ({
15
+ backgroundColor: 'transparent',
16
+ borderWidth: 1,
17
+ borderColor: c.border,
18
+ }),
19
+ underline: (c) => ({
20
+ backgroundColor: 'transparent',
21
+ borderBottomWidth: 1,
22
+ borderColor: c.border,
23
+ }),
24
+ ghost: () => ({
25
+ backgroundColor: 'transparent',
26
+ borderWidth: 0,
27
+ }),
28
+ };
@@ -0,0 +1,10 @@
1
+ export const getStateColor = (state, colors) => {
2
+ switch (state) {
3
+ case 'error':
4
+ return colors.error;
5
+ case 'success':
6
+ return colors.success;
7
+ default:
8
+ return colors.border;
9
+ }
10
+ };
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { default as Button } from './components/Button/Button';
2
+ export { default as Input } from './components/Input/Input';
3
+
4
+ // theme exports
5
+ export * from './theme';
@@ -0,0 +1,29 @@
1
+ import React, { createContext, useContext, useMemo, useState } from 'react';
2
+ import { lightTheme } from './light';
3
+ import { darkTheme } from './dark';
4
+
5
+ const ThemeContext = createContext();
6
+
7
+ export const ThemeProvider = ({ children }) => {
8
+ const [mode, setMode] = useState('light');
9
+
10
+ const theme = useMemo(() => {
11
+ return mode === 'light' ? lightTheme : darkTheme;
12
+ }, [mode]);
13
+
14
+ const toggleTheme = () => {
15
+ setMode((prev) => (prev === 'light' ? 'dark' : 'light'));
16
+ };
17
+
18
+ return (
19
+ <ThemeContext.Provider value={{ theme, mode, toggleTheme }}>{children}</ThemeContext.Provider>
20
+ );
21
+ };
22
+
23
+ export const useTheme = () => {
24
+ const context = useContext(ThemeContext);
25
+ if (!context) {
26
+ throw new Error('useTheme must be used inside ThemeProvider');
27
+ }
28
+ return context;
29
+ };
@@ -0,0 +1,19 @@
1
+ export const darkTheme = {
2
+ mode: 'dark',
3
+ colors: {
4
+ primary: '#3b82f6',
5
+
6
+ background: '#111827',
7
+ surface: '#1f2937',
8
+
9
+ text: '#f9fafb',
10
+ textSecondary: '#9ca3af',
11
+
12
+ border: '#374151',
13
+
14
+ error: '#ef4444',
15
+ success: '#22c55e',
16
+
17
+ disabled: '#374151',
18
+ },
19
+ };
@@ -0,0 +1 @@
1
+ export * from './ThemeProvider';
@@ -0,0 +1,19 @@
1
+ export const lightTheme = {
2
+ mode: 'light',
3
+ colors: {
4
+ primary: '#2563eb',
5
+
6
+ background: '#ffffff',
7
+ surface: '#f9fafb',
8
+
9
+ text: '#111827',
10
+ textSecondary: '#6b7280',
11
+
12
+ border: '#d1d5db',
13
+
14
+ error: '#dc2626',
15
+ success: '#16a34a',
16
+
17
+ disabled: '#e5e7eb',
18
+ },
19
+ };