@rovula/ui 0.0.77 → 0.0.79
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/dist/cjs/bundle.css +43 -3
- package/dist/cjs/bundle.js +3 -3
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +3 -0
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +5 -1
- package/dist/cjs/types/components/MaskedTextInput/MaskedTextInput.d.ts +75 -0
- package/dist/cjs/types/components/MaskedTextInput/MaskedTextInput.stories.d.ts +491 -0
- package/dist/cjs/types/components/MaskedTextInput/index.d.ts +3 -0
- package/dist/cjs/types/components/Menu/Menu.d.ts +65 -0
- package/dist/cjs/types/components/Menu/Menu.stories.d.ts +31 -0
- package/dist/cjs/types/components/Menu/helpers.d.ts +19 -0
- package/dist/cjs/types/components/Menu/index.d.ts +4 -0
- package/dist/cjs/types/components/Search/Search.d.ts +46 -3
- package/dist/cjs/types/components/Search/Search.stories.d.ts +46 -27
- package/dist/cjs/types/index.d.ts +3 -0
- package/dist/components/Dropdown/Dropdown.js +41 -19
- package/dist/components/Dropdown/Dropdown.stories.js +13 -0
- package/dist/components/MaskedTextInput/MaskedTextInput.js +267 -0
- package/dist/components/MaskedTextInput/MaskedTextInput.stories.js +167 -0
- package/dist/components/MaskedTextInput/index.js +2 -0
- package/dist/components/Menu/Menu.js +64 -0
- package/dist/components/Menu/Menu.stories.js +406 -0
- package/dist/components/Menu/helpers.js +28 -0
- package/dist/components/Menu/index.js +3 -0
- package/dist/components/Toast/Toast.styles.js +1 -1
- package/dist/esm/bundle.css +43 -3
- package/dist/esm/bundle.js +3 -3
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Dropdown/Dropdown.d.ts +3 -0
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +5 -1
- package/dist/esm/types/components/MaskedTextInput/MaskedTextInput.d.ts +75 -0
- package/dist/esm/types/components/MaskedTextInput/MaskedTextInput.stories.d.ts +491 -0
- package/dist/esm/types/components/MaskedTextInput/index.d.ts +3 -0
- package/dist/esm/types/components/Menu/Menu.d.ts +65 -0
- package/dist/esm/types/components/Menu/Menu.stories.d.ts +31 -0
- package/dist/esm/types/components/Menu/helpers.d.ts +19 -0
- package/dist/esm/types/components/Menu/index.d.ts +4 -0
- package/dist/esm/types/components/Search/Search.d.ts +46 -3
- package/dist/esm/types/components/Search/Search.stories.d.ts +46 -27
- package/dist/esm/types/index.d.ts +3 -0
- package/dist/index.d.ts +169 -3
- package/dist/index.js +2 -0
- package/dist/src/theme/global.css +55 -4
- package/package.json +1 -1
- package/src/components/Dropdown/Dropdown.stories.tsx +31 -0
- package/src/components/Dropdown/Dropdown.tsx +73 -54
- package/src/components/MaskedTextInput/MaskedTextInput.stories.tsx +414 -0
- package/src/components/MaskedTextInput/MaskedTextInput.tsx +391 -0
- package/src/components/MaskedTextInput/README.md +202 -0
- package/src/components/MaskedTextInput/index.ts +3 -0
- package/src/components/Menu/Menu.stories.tsx +586 -0
- package/src/components/Menu/Menu.tsx +235 -0
- package/src/components/Menu/helpers.ts +45 -0
- package/src/components/Menu/index.ts +7 -0
- package/src/components/Search/Search.tsx +24 -11
- package/src/components/Toast/Toast.styles.tsx +1 -1
- package/src/index.ts +6 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState, useEffect, } from "react";
|
|
14
|
+
import TextInput from "../TextInput/TextInput";
|
|
15
|
+
import { cn } from "@/utils/cn";
|
|
16
|
+
// Kendo UI style mask patterns
|
|
17
|
+
export const MASK_PATTERNS = {
|
|
18
|
+
PHONE: "(000) 000-0000",
|
|
19
|
+
PHONE_INTL: "+000 000 000 0000",
|
|
20
|
+
CREDIT_CARD: "0000 0000 0000 0000",
|
|
21
|
+
DATE: "00/00/0000",
|
|
22
|
+
TIME: "00:00",
|
|
23
|
+
SSN: "000-00-0000",
|
|
24
|
+
ZIP_CODE: "00000",
|
|
25
|
+
ZIP_CODE_EXT: "00000-0000",
|
|
26
|
+
CURRENCY: "$000,000.00",
|
|
27
|
+
PERCENTAGE: "000%",
|
|
28
|
+
LICENSE_PLATE: "AAA-0000",
|
|
29
|
+
PRODUCT_CODE: "AA-0000-AA",
|
|
30
|
+
ALPHANUMERIC: "AAAA-0000",
|
|
31
|
+
};
|
|
32
|
+
// Kendo UI mask rules
|
|
33
|
+
const KENDO_RULES = {
|
|
34
|
+
"0": { pattern: /[0-9]/, placeholder: "0" }, // Any digit 0-9
|
|
35
|
+
"9": { pattern: /[0-9\s]/, placeholder: "9" }, // Any digit 0-9 or space
|
|
36
|
+
"#": { pattern: /[0-9\s+\-]/, placeholder: "#" }, // Any digit, space, +, or -
|
|
37
|
+
L: { pattern: /[a-zA-Z]/, placeholder: "L" }, // Any letter
|
|
38
|
+
"?": { pattern: /[a-zA-Z\s]/, placeholder: "?" }, // Any letter or space
|
|
39
|
+
"&": { pattern: /[^\s]/, placeholder: "&" }, // Any character except space
|
|
40
|
+
C: { pattern: /./, placeholder: "C" }, // Any character including space
|
|
41
|
+
A: { pattern: /[a-zA-Z0-9]/, placeholder: "A" }, // Any alphanumeric
|
|
42
|
+
a: { pattern: /[a-zA-Z0-9\s]/, placeholder: "a" }, // Any alphanumeric or space
|
|
43
|
+
};
|
|
44
|
+
// Helper function to create mask pattern from string using Kendo UI rules
|
|
45
|
+
const createMaskPattern = (mask, customRules) => {
|
|
46
|
+
const rules = Object.assign(Object.assign({}, KENDO_RULES), customRules);
|
|
47
|
+
const pattern = [];
|
|
48
|
+
let i = 0;
|
|
49
|
+
while (i < mask.length) {
|
|
50
|
+
const char = mask[i];
|
|
51
|
+
if (char === "\\" && i + 1 < mask.length) {
|
|
52
|
+
// Escaped character - treat as literal
|
|
53
|
+
const nextChar = mask[i + 1];
|
|
54
|
+
pattern.push({
|
|
55
|
+
pattern: new RegExp(`^${nextChar}$`),
|
|
56
|
+
placeholder: nextChar,
|
|
57
|
+
isLiteral: true,
|
|
58
|
+
});
|
|
59
|
+
i += 2;
|
|
60
|
+
}
|
|
61
|
+
else if (rules[char]) {
|
|
62
|
+
// Apply Kendo rule
|
|
63
|
+
const rule = rules[char];
|
|
64
|
+
if (typeof rule === "function") {
|
|
65
|
+
pattern.push({
|
|
66
|
+
pattern: /./, // Accept any character, validate with function
|
|
67
|
+
placeholder: char,
|
|
68
|
+
validator: rule,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if (rule instanceof RegExp) {
|
|
72
|
+
pattern.push({
|
|
73
|
+
pattern: rule,
|
|
74
|
+
placeholder: char,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
pattern.push({
|
|
79
|
+
pattern: rule.pattern,
|
|
80
|
+
placeholder: rule.placeholder,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Literal character
|
|
87
|
+
pattern.push({
|
|
88
|
+
pattern: new RegExp(`^${char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
|
|
89
|
+
placeholder: char,
|
|
90
|
+
isLiteral: true,
|
|
91
|
+
});
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return pattern;
|
|
96
|
+
};
|
|
97
|
+
// Helper function to apply mask to value using Kendo UI rules
|
|
98
|
+
const applyMask = (value, maskPattern, maskChar = "_", showMask = true, guide = true) => {
|
|
99
|
+
let maskedValue = "";
|
|
100
|
+
let valueIndex = 0;
|
|
101
|
+
for (let i = 0; i < maskPattern.length; i++) {
|
|
102
|
+
const rule = maskPattern[i];
|
|
103
|
+
if (rule.isLiteral) {
|
|
104
|
+
// Literal character - always add
|
|
105
|
+
maskedValue += rule.placeholder;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Input character - find next valid character
|
|
109
|
+
let foundValid = false;
|
|
110
|
+
while (valueIndex < value.length) {
|
|
111
|
+
const char = value[valueIndex];
|
|
112
|
+
valueIndex++;
|
|
113
|
+
// Check with validator function first if exists
|
|
114
|
+
const isValid = rule.validator
|
|
115
|
+
? rule.validator(char)
|
|
116
|
+
: rule.pattern.test(char);
|
|
117
|
+
if (isValid) {
|
|
118
|
+
maskedValue += char;
|
|
119
|
+
foundValid = true;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
// Skip invalid characters and continue searching
|
|
123
|
+
}
|
|
124
|
+
if (!foundValid && guide && showMask) {
|
|
125
|
+
// No valid character found, fill with placeholder
|
|
126
|
+
maskedValue += maskChar;
|
|
127
|
+
}
|
|
128
|
+
else if (!foundValid) {
|
|
129
|
+
// No placeholder needed, stop building the mask
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return maskedValue;
|
|
135
|
+
};
|
|
136
|
+
// Helper function to extract raw value from masked value
|
|
137
|
+
const extractRawValue = (maskedValue, maskPattern) => {
|
|
138
|
+
let rawValue = "";
|
|
139
|
+
for (let i = 0; i < maskedValue.length && i < maskPattern.length; i++) {
|
|
140
|
+
const rule = maskPattern[i];
|
|
141
|
+
const char = maskedValue[i];
|
|
142
|
+
if (!rule.isLiteral) {
|
|
143
|
+
// Only include non-literal characters in raw value
|
|
144
|
+
rawValue += char;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return rawValue;
|
|
148
|
+
};
|
|
149
|
+
// Helper function to get cursor position after masking
|
|
150
|
+
const getCursorPosition = (maskedValue, rawInputLength, maskPattern) => {
|
|
151
|
+
let inputCount = 0;
|
|
152
|
+
let cursorPos = 0;
|
|
153
|
+
for (let i = 0; i < maskPattern.length && i < maskedValue.length; i++) {
|
|
154
|
+
const rule = maskPattern[i];
|
|
155
|
+
const char = maskedValue[i];
|
|
156
|
+
if (rule.isLiteral) {
|
|
157
|
+
cursorPos = i + 1;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
inputCount++;
|
|
161
|
+
cursorPos = i + 1;
|
|
162
|
+
if (inputCount >= rawInputLength) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return cursorPos;
|
|
168
|
+
};
|
|
169
|
+
export const MaskedTextInput = forwardRef((_a, ref) => {
|
|
170
|
+
var { mask, maskChar = "_", showMask = true, guide = true, keepCharPositions = false, rules, onMaskedChange, onChange, value, defaultValue } = _a, props = __rest(_a, ["mask", "maskChar", "showMask", "guide", "keepCharPositions", "rules", "onMaskedChange", "onChange", "value", "defaultValue"]);
|
|
171
|
+
const inputRef = useRef(null);
|
|
172
|
+
const [maskedValue, setMaskedValue] = useState("");
|
|
173
|
+
const [rawValue, setRawValue] = useState("");
|
|
174
|
+
// Parse mask pattern using Kendo UI rules
|
|
175
|
+
const maskPattern = React.useMemo(() => {
|
|
176
|
+
if (!mask)
|
|
177
|
+
return null;
|
|
178
|
+
return createMaskPattern(mask, rules);
|
|
179
|
+
}, [mask, rules]);
|
|
180
|
+
// Initialize values
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const initialValue = value || defaultValue || "";
|
|
183
|
+
if (maskPattern && initialValue) {
|
|
184
|
+
const masked = applyMask(initialValue, maskPattern, maskChar, showMask, guide);
|
|
185
|
+
const raw = extractRawValue(masked, maskPattern);
|
|
186
|
+
setMaskedValue(masked);
|
|
187
|
+
setRawValue(raw);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
setMaskedValue(initialValue);
|
|
191
|
+
setRawValue(initialValue);
|
|
192
|
+
}
|
|
193
|
+
}, [maskPattern, maskChar, showMask, guide, value, defaultValue]);
|
|
194
|
+
useImperativeHandle(ref, () => inputRef === null || inputRef === void 0 ? void 0 : inputRef.current);
|
|
195
|
+
const handleChange = useCallback((event) => {
|
|
196
|
+
const inputValue = event.target.value;
|
|
197
|
+
if (!maskPattern) {
|
|
198
|
+
setMaskedValue(inputValue);
|
|
199
|
+
setRawValue(inputValue);
|
|
200
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
201
|
+
onMaskedChange === null || onMaskedChange === void 0 ? void 0 : onMaskedChange(inputValue, inputValue);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const newMaskedValue = applyMask(inputValue, maskPattern, maskChar, showMask, guide);
|
|
205
|
+
const newRawValue = extractRawValue(newMaskedValue, maskPattern);
|
|
206
|
+
setMaskedValue(newMaskedValue);
|
|
207
|
+
setRawValue(newRawValue);
|
|
208
|
+
// Create synthetic event with masked value
|
|
209
|
+
const syntheticEvent = Object.assign(Object.assign({}, event), { target: Object.assign(Object.assign({}, event.target), { value: newMaskedValue }) });
|
|
210
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(syntheticEvent);
|
|
211
|
+
onMaskedChange === null || onMaskedChange === void 0 ? void 0 : onMaskedChange(newMaskedValue, newRawValue);
|
|
212
|
+
// Set cursor position after state update
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
if (inputRef.current) {
|
|
215
|
+
const rawLength = newRawValue.replace(/[_\s]/g, "").length;
|
|
216
|
+
const newCursorPos = getCursorPosition(newMaskedValue, rawLength, maskPattern);
|
|
217
|
+
inputRef.current.setSelectionRange(newCursorPos, newCursorPos);
|
|
218
|
+
}
|
|
219
|
+
}, 0);
|
|
220
|
+
}, [maskPattern, maskChar, showMask, guide, onChange, onMaskedChange]);
|
|
221
|
+
const handleKeyDown = useCallback((event) => {
|
|
222
|
+
var _a, _b, _c;
|
|
223
|
+
if (!maskPattern) {
|
|
224
|
+
(_a = props.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(props, event);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const { key, ctrlKey, metaKey } = event;
|
|
228
|
+
const input = event.target;
|
|
229
|
+
const cursorPos = input.selectionStart || 0;
|
|
230
|
+
// Allow navigation and editing keys
|
|
231
|
+
if (key === "Backspace" ||
|
|
232
|
+
key === "Delete" ||
|
|
233
|
+
key === "ArrowLeft" ||
|
|
234
|
+
key === "ArrowRight" ||
|
|
235
|
+
key === "Home" ||
|
|
236
|
+
key === "End" ||
|
|
237
|
+
key === "Tab" ||
|
|
238
|
+
key.length > 1 || // Allow other special keys
|
|
239
|
+
ctrlKey ||
|
|
240
|
+
metaKey) {
|
|
241
|
+
(_b = props.onKeyDown) === null || _b === void 0 ? void 0 : _b.call(props, event);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Find the next non-literal position from cursor
|
|
245
|
+
let targetPos = cursorPos;
|
|
246
|
+
while (targetPos < maskPattern.length &&
|
|
247
|
+
maskPattern[targetPos].isLiteral) {
|
|
248
|
+
targetPos++;
|
|
249
|
+
}
|
|
250
|
+
// Check if we have a valid position and the key matches
|
|
251
|
+
if (targetPos < maskPattern.length) {
|
|
252
|
+
const targetRule = maskPattern[targetPos];
|
|
253
|
+
// Check with validator function first if exists
|
|
254
|
+
const isValid = targetRule.validator
|
|
255
|
+
? targetRule.validator(key)
|
|
256
|
+
: targetRule.pattern.test(key);
|
|
257
|
+
if (!isValid) {
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
(_c = props.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(props, event);
|
|
263
|
+
}, [maskPattern, props]);
|
|
264
|
+
return (_jsx(TextInput, Object.assign({}, props, { ref: inputRef, value: maskedValue, onChange: handleChange, onKeyDown: handleKeyDown, className: cn(props.className) })));
|
|
265
|
+
});
|
|
266
|
+
MaskedTextInput.displayName = "MaskedTextInput";
|
|
267
|
+
export default MaskedTextInput;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import MaskedTextInput, { MASK_PATTERNS } from "./MaskedTextInput";
|
|
4
|
+
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/MaskedTextInput",
|
|
7
|
+
component: MaskedTextInput,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
parameters: {
|
|
10
|
+
// More on how to position stories at: https://storybook.js.org/docs/7.0/react/configure/story-layout
|
|
11
|
+
layout: "fullscreen",
|
|
12
|
+
},
|
|
13
|
+
decorators: [
|
|
14
|
+
(Story) => (_jsx("div", { className: "p-5 flex w-full bg-[rgb(var(--base-bg-2))] ", children: _jsx(Story, {}) })),
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
export default meta;
|
|
18
|
+
export const Default = {
|
|
19
|
+
args: {
|
|
20
|
+
label: "Phone Number",
|
|
21
|
+
mask: MASK_PATTERNS.PHONE,
|
|
22
|
+
fullwidth: true,
|
|
23
|
+
},
|
|
24
|
+
render: (args) => {
|
|
25
|
+
return (_jsxs("div", { className: "flex flex-row gap-4 w-full", children: [_jsx(MaskedTextInput, Object.assign({ id: "1", size: "lg" }, args)), _jsx(MaskedTextInput, Object.assign({ id: "2", size: "md" }, args)), _jsx(MaskedTextInput, Object.assign({ id: "3", size: "sm" }, args))] }));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export const PhoneNumber = {
|
|
29
|
+
args: {
|
|
30
|
+
label: "Phone Number",
|
|
31
|
+
mask: MASK_PATTERNS.PHONE,
|
|
32
|
+
fullwidth: true,
|
|
33
|
+
},
|
|
34
|
+
render: (args) => {
|
|
35
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args)), _jsx(MaskedTextInput, Object.assign({}, args, { label: "International Phone", mask: MASK_PATTERNS.PHONE_INTL }))] }));
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const CreditCard = {
|
|
39
|
+
args: {
|
|
40
|
+
label: "Credit Card Number",
|
|
41
|
+
mask: MASK_PATTERNS.CREDIT_CARD,
|
|
42
|
+
fullwidth: true,
|
|
43
|
+
},
|
|
44
|
+
render: (args) => {
|
|
45
|
+
return (_jsx("div", { className: "flex flex-col gap-4 w-full max-w-md", children: _jsx(MaskedTextInput, Object.assign({}, args)) }));
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
export const DateAndTime = {
|
|
49
|
+
args: {
|
|
50
|
+
label: "Date",
|
|
51
|
+
mask: MASK_PATTERNS.DATE,
|
|
52
|
+
fullwidth: true,
|
|
53
|
+
},
|
|
54
|
+
render: (args) => {
|
|
55
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args)), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Time", mask: MASK_PATTERNS.TIME }))] }));
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
export const SocialSecurityNumber = {
|
|
59
|
+
args: {
|
|
60
|
+
label: "Social Security Number",
|
|
61
|
+
mask: MASK_PATTERNS.SSN,
|
|
62
|
+
fullwidth: true,
|
|
63
|
+
},
|
|
64
|
+
render: (args) => {
|
|
65
|
+
return (_jsx("div", { className: "flex flex-col gap-4 w-full max-w-md", children: _jsx(MaskedTextInput, Object.assign({}, args)) }));
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export const ZipCode = {
|
|
69
|
+
args: {
|
|
70
|
+
label: "ZIP Code",
|
|
71
|
+
mask: MASK_PATTERNS.ZIP_CODE,
|
|
72
|
+
fullwidth: true,
|
|
73
|
+
},
|
|
74
|
+
render: (args) => {
|
|
75
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args)), _jsx(MaskedTextInput, Object.assign({}, args, { label: "ZIP Code + 4", mask: MASK_PATTERNS.ZIP_CODE_EXT }))] }));
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
export const Currency = {
|
|
79
|
+
args: {
|
|
80
|
+
label: "Amount",
|
|
81
|
+
mask: MASK_PATTERNS.CURRENCY,
|
|
82
|
+
fullwidth: true,
|
|
83
|
+
},
|
|
84
|
+
render: (args) => {
|
|
85
|
+
return (_jsx("div", { className: "flex flex-col gap-4 w-full max-w-md", children: _jsx(MaskedTextInput, Object.assign({}, args)) }));
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
export const CustomMask = {
|
|
89
|
+
args: {
|
|
90
|
+
label: "Custom Pattern",
|
|
91
|
+
mask: "AAA-999-AAA",
|
|
92
|
+
fullwidth: true,
|
|
93
|
+
},
|
|
94
|
+
render: (args) => {
|
|
95
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args)), _jsx(MaskedTextInput, Object.assign({}, args, { label: "License Plate", mask: "999-AAA" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Product Code", mask: "AA-9999-AA" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Time", mask: "00:00:00" }))] }));
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
export const MaskOptions = {
|
|
99
|
+
args: {
|
|
100
|
+
label: "Phone with Options",
|
|
101
|
+
mask: MASK_PATTERNS.PHONE,
|
|
102
|
+
fullwidth: true,
|
|
103
|
+
},
|
|
104
|
+
render: (args) => {
|
|
105
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args, { label: "With Guide", guide: true, showMask: true })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Without Guide", guide: false, showMask: true })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Custom Mask Char", maskChar: "*", guide: true, showMask: true }))] }));
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
export const WithCallbacks = {
|
|
109
|
+
args: {
|
|
110
|
+
label: "Phone with Callbacks",
|
|
111
|
+
mask: MASK_PATTERNS.PHONE,
|
|
112
|
+
fullwidth: true,
|
|
113
|
+
},
|
|
114
|
+
render: (args) => {
|
|
115
|
+
const [maskedValue, setMaskedValue] = useState("");
|
|
116
|
+
const [rawValue, setRawValue] = useState("");
|
|
117
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args, { onChange: (e) => {
|
|
118
|
+
setMaskedValue(e.target.value);
|
|
119
|
+
setRawValue(e.target.value);
|
|
120
|
+
}, onMaskedChange: (masked, raw) => {
|
|
121
|
+
setMaskedValue(masked);
|
|
122
|
+
setRawValue(raw);
|
|
123
|
+
} })), _jsxs("div", { className: "p-4 bg-gray-100 rounded", children: [_jsxs("p", { children: [_jsx("strong", { children: "Masked Value:" }), " ", maskedValue] }), _jsxs("p", { children: [_jsx("strong", { children: "Raw Value:" }), " ", rawValue] })] })] }));
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
export const AllPatterns = {
|
|
127
|
+
args: {
|
|
128
|
+
fullwidth: true,
|
|
129
|
+
},
|
|
130
|
+
render: (args) => {
|
|
131
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-2xl", children: [_jsx("h3", { className: "text-lg font-semibold", children: "All Mask Patterns" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(MaskedTextInput, Object.assign({}, args, { label: "Phone", mask: MASK_PATTERNS.PHONE })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "International Phone", mask: MASK_PATTERNS.PHONE_INTL })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Credit Card", mask: MASK_PATTERNS.CREDIT_CARD })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Date", mask: MASK_PATTERNS.DATE })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Time", mask: MASK_PATTERNS.TIME })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "SSN", mask: MASK_PATTERNS.SSN })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "ZIP Code", mask: MASK_PATTERNS.ZIP_CODE })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "ZIP Code + 4", mask: MASK_PATTERNS.ZIP_CODE_EXT })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Currency", mask: MASK_PATTERNS.CURRENCY })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Percentage", mask: MASK_PATTERNS.PERCENTAGE }))] })] }));
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
export const KendoRules = {
|
|
135
|
+
args: {
|
|
136
|
+
label: "Kendo UI Rules",
|
|
137
|
+
fullwidth: true,
|
|
138
|
+
},
|
|
139
|
+
render: (args) => {
|
|
140
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Kendo UI Mask Rules Examples" }), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: 0 (Digits Only)", mask: "000-000-0000", helperText: "0 = Required digit (0-9)" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: L (Letters Only)", mask: "LLL-LLLL", helperText: "L = Required letter (a-z, A-Z)" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: A (Alphanumeric)", mask: "AAAA-0000", helperText: "A = Letter or digit" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: # (Number with sign)", mask: "###", helperText: "# = Digit, space, + or -" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Mixed Pattern Example", mask: "(000) LLL-0000", helperText: "Format: (123) ABC-4567" }))] }));
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
export const OptionalRules = {
|
|
144
|
+
args: {
|
|
145
|
+
label: "Optional Rules (with space)",
|
|
146
|
+
fullwidth: true,
|
|
147
|
+
},
|
|
148
|
+
render: (args) => {
|
|
149
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Rules that accept space (optional input)" }), _jsx("p", { className: "text-xs text-gray-500", children: "Note: These rules allow space, making the position optional" }), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: 9 (Digit or Space)", mask: "999-999-9999", helperText: "9 = Optional digit (press space to skip)", guide: false })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: ? (Letter or Space)", mask: "???-????", helperText: "? = Optional letter (press space to skip)", guide: false })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: a (Alphanumeric or Space)", mask: "aaaa-aaaa", helperText: "a = Optional alphanumeric (press space to skip)", guide: false }))] }));
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
export const CustomRules = {
|
|
153
|
+
args: {
|
|
154
|
+
label: "Custom Rules",
|
|
155
|
+
fullwidth: true,
|
|
156
|
+
},
|
|
157
|
+
render: (args) => {
|
|
158
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Custom Validation Rules" }), _jsx("div", { className: "space-y-2", children: _jsx(MaskedTextInput, Object.assign({}, args, { label: "Custom Rule: Digits 3-9 only", mask: "~-~-~", rules: {
|
|
159
|
+
"~": /[3-9]/,
|
|
160
|
+
}, helperText: "Type only digits 3, 4, 5, 6, 7, 8, or 9" })) }), _jsx("div", { className: "space-y-2", children: _jsx(MaskedTextInput, Object.assign({}, args, { label: "Custom Rule: Uppercase letters only", mask: "***", rules: {
|
|
161
|
+
"*": (char) => char === char.toUpperCase() && /[A-Z]/.test(char),
|
|
162
|
+
}, helperText: "Type only uppercase letters (A-Z)" })) }), _jsx("div", { className: "space-y-2", children: _jsx(MaskedTextInput, Object.assign({}, args, { label: "Mixed Custom Rules", mask: "~*-~*", rules: {
|
|
163
|
+
"~": /[0-9]/,
|
|
164
|
+
"*": /[A-Z]/,
|
|
165
|
+
}, helperText: "Format: Digit-Letter-Digit-Letter" })) })] }));
|
|
166
|
+
},
|
|
167
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { forwardRef, } from "react";
|
|
3
|
+
import { cn } from "@/utils/cn";
|
|
4
|
+
import Icon from "../Icon/Icon";
|
|
5
|
+
// ==================== Menu Container ====================
|
|
6
|
+
export const Menu = forwardRef(({ items, selectedValues = [], onSelect, className, style, isAbove = false }, ref) => {
|
|
7
|
+
return (_jsx("div", { ref: ref, className: cn("z-50 min-w-[154px] overflow-hidden rounded-md bg-base-popup text-base-popup-foreground", "border border-base-popup", className), style: Object.assign({ boxShadow: "var(--dropdown-menu-shadow)" }, style), children: items.map((item, index) => {
|
|
8
|
+
var _a;
|
|
9
|
+
if (item.type === "separator") {
|
|
10
|
+
return _jsx(MenuSeparator, {}, `separator-${index}`);
|
|
11
|
+
}
|
|
12
|
+
if (item.type === "label") {
|
|
13
|
+
return _jsx(MenuLabel, { children: item.label }, `label-${index}`);
|
|
14
|
+
}
|
|
15
|
+
if (item.type === "custom") {
|
|
16
|
+
return (_jsx(React.Fragment, { children: item.render() }, `custom-${index}`));
|
|
17
|
+
}
|
|
18
|
+
const itemOption = item.item;
|
|
19
|
+
const visualType = itemOption.type || "default";
|
|
20
|
+
// Determine checked/selected state
|
|
21
|
+
let isChecked = false;
|
|
22
|
+
if (visualType === "checkbox" || visualType === "radio") {
|
|
23
|
+
isChecked =
|
|
24
|
+
(_a = itemOption.checked) !== null && _a !== void 0 ? _a : selectedValues.includes(itemOption.value);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
isChecked = selectedValues.includes(itemOption.value);
|
|
28
|
+
}
|
|
29
|
+
return (_jsx(MenuItem, { option: itemOption, visualType: visualType, isChecked: isChecked, onSelect: () => {
|
|
30
|
+
var _a;
|
|
31
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(itemOption.value, itemOption);
|
|
32
|
+
(_a = itemOption.onClick) === null || _a === void 0 ? void 0 : _a.call(itemOption);
|
|
33
|
+
} }, itemOption.value));
|
|
34
|
+
}) }));
|
|
35
|
+
});
|
|
36
|
+
Menu.displayName = "Menu";
|
|
37
|
+
export const MenuItem = forwardRef(({ option, visualType, isChecked, onSelect, className }, ref) => {
|
|
38
|
+
// Render indicator based on visual type
|
|
39
|
+
const renderIndicator = () => {
|
|
40
|
+
if (visualType === "checkbox" && isChecked) {
|
|
41
|
+
return (_jsx("span", { className: "absolute left-4 flex items-center justify-center", children: _jsx(Icon, { type: "heroicons", name: "check", className: "size-4" }) }));
|
|
42
|
+
}
|
|
43
|
+
if (visualType === "radio" && isChecked) {
|
|
44
|
+
return (_jsx("span", { className: "absolute left-4 flex items-center justify-center", children: _jsx(Icon, { type: "heroicons", name: "circle", className: "h-2 w-2 fill-current" }) }));
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
return (_jsxs("div", { ref: ref, className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-9 pr-4 typography-subtitile4 outline-none transition-colors", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "hover:bg-[var(--dropdown-menu-hover-bg)] hover:text-[var(--dropdown-menu-hover-text)]", {
|
|
49
|
+
"bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)] typography-subtitile5": isChecked,
|
|
50
|
+
"pointer-events-none opacity-50": option.disabled,
|
|
51
|
+
"text-red-500": option.danger,
|
|
52
|
+
}, className), onClick: option.disabled ? undefined : onSelect, children: [renderIndicator(), option.icon && _jsx("span", { className: "flex-shrink-0", children: option.icon }), option.label] }));
|
|
53
|
+
});
|
|
54
|
+
MenuItem.displayName = "MenuItem";
|
|
55
|
+
export const MenuSeparator = forwardRef(({ className }, ref) => {
|
|
56
|
+
return (_jsx("div", { ref: ref, className: cn("-mx-2 my-2 h-px bg-[var(--dropdown-menu-seperator-bg)]", className) }));
|
|
57
|
+
});
|
|
58
|
+
MenuSeparator.displayName = "MenuSeparator";
|
|
59
|
+
export const MenuLabel = forwardRef(({ children, className }, ref) => {
|
|
60
|
+
return (_jsx("div", { ref: ref, className: cn("px-3 py-2 typography-small4 text-text-grey-medium", className), children: children }));
|
|
61
|
+
});
|
|
62
|
+
MenuLabel.displayName = "MenuLabel";
|
|
63
|
+
// ==================== Exports ====================
|
|
64
|
+
export default Menu;
|