@seedgrid/fe-components 0.2.8 → 0.2.10
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/commons/SgToaster.d.ts +9 -0
- package/dist/commons/SgToaster.d.ts.map +1 -1
- package/dist/commons/SgToaster.js +86 -17
- package/dist/gadgets/qr-code/SgQRCode.d.ts +25 -0
- package/dist/gadgets/qr-code/SgQRCode.d.ts.map +1 -0
- package/dist/gadgets/qr-code/SgQRCode.js +75 -0
- package/dist/gadgets/qr-code/index.d.ts +3 -0
- package/dist/gadgets/qr-code/index.d.ts.map +1 -0
- package/dist/gadgets/qr-code/index.js +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/inputs/SgCombobox.d.ts +26 -0
- package/dist/inputs/SgCombobox.d.ts.map +1 -0
- package/dist/inputs/SgCombobox.js +349 -0
- package/dist/inputs/SgInputOTP.d.ts +74 -0
- package/dist/inputs/SgInputOTP.d.ts.map +1 -0
- package/dist/inputs/SgInputOTP.js +499 -0
- package/dist/inputs/SgToggleSwitch.d.ts +36 -0
- package/dist/inputs/SgToggleSwitch.d.ts.map +1 -0
- package/dist/inputs/SgToggleSwitch.js +174 -0
- package/dist/others/SgPlayground.d.ts.map +1 -1
- package/dist/others/SgPlayground.js +2 -1
- package/package.json +8 -2
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { Controller } from "react-hook-form";
|
|
6
|
+
import { t, useComponentsI18n } from "../i18n";
|
|
7
|
+
const DEFAULT_MASK = "999999";
|
|
8
|
+
const DIGIT_RE = /^[0-9]$/;
|
|
9
|
+
const ALPHANUMERIC_RE = /^[A-Za-z0-9]$/;
|
|
10
|
+
function normalizeExternalValue(value) {
|
|
11
|
+
if (value == null)
|
|
12
|
+
return "";
|
|
13
|
+
if (typeof value === "string")
|
|
14
|
+
return value;
|
|
15
|
+
if (typeof value === "number")
|
|
16
|
+
return String(value);
|
|
17
|
+
return String(value);
|
|
18
|
+
}
|
|
19
|
+
function toCssSize(value) {
|
|
20
|
+
if (value === undefined)
|
|
21
|
+
return "fit-content";
|
|
22
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
23
|
+
}
|
|
24
|
+
function mergeRefs(...refs) {
|
|
25
|
+
return (node) => {
|
|
26
|
+
for (const ref of refs) {
|
|
27
|
+
if (!ref)
|
|
28
|
+
continue;
|
|
29
|
+
if (typeof ref === "function") {
|
|
30
|
+
ref(node);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (typeof ref === "object" && "current" in ref) {
|
|
34
|
+
ref.current = node;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function buildMask(source) {
|
|
40
|
+
const tokens = [];
|
|
41
|
+
const slots = [];
|
|
42
|
+
let nextSlotIndex = 0;
|
|
43
|
+
for (const char of source) {
|
|
44
|
+
if (char === "#" || char === "9") {
|
|
45
|
+
const slot = {
|
|
46
|
+
type: "slot",
|
|
47
|
+
slotIndex: nextSlotIndex,
|
|
48
|
+
slotKind: char === "#" ? "alphanumeric" : "digit"
|
|
49
|
+
};
|
|
50
|
+
tokens.push(slot);
|
|
51
|
+
slots.push(slot);
|
|
52
|
+
nextSlotIndex += 1;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
tokens.push({ type: "separator", value: char });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { tokens, slots };
|
|
59
|
+
}
|
|
60
|
+
function parseMask(mask) {
|
|
61
|
+
const source = mask && mask.length > 0 ? mask : DEFAULT_MASK;
|
|
62
|
+
const parsed = buildMask(source);
|
|
63
|
+
if (parsed.slots.length > 0)
|
|
64
|
+
return parsed;
|
|
65
|
+
return buildMask(DEFAULT_MASK);
|
|
66
|
+
}
|
|
67
|
+
function buildEmptySlotValues(slots) {
|
|
68
|
+
return Array.from({ length: slots.length }, () => "");
|
|
69
|
+
}
|
|
70
|
+
function isAcceptedChar(slot, char) {
|
|
71
|
+
if (char.length !== 1)
|
|
72
|
+
return false;
|
|
73
|
+
if (slot.slotKind === "digit")
|
|
74
|
+
return DIGIT_RE.test(char);
|
|
75
|
+
return ALPHANUMERIC_RE.test(char);
|
|
76
|
+
}
|
|
77
|
+
function textToSlotValues(text, slots) {
|
|
78
|
+
const next = buildEmptySlotValues(slots);
|
|
79
|
+
let nextSlotIndex = 0;
|
|
80
|
+
for (const char of text) {
|
|
81
|
+
if (nextSlotIndex >= slots.length)
|
|
82
|
+
break;
|
|
83
|
+
const slot = slots[nextSlotIndex];
|
|
84
|
+
if (!slot)
|
|
85
|
+
break;
|
|
86
|
+
if (!isAcceptedChar(slot, char))
|
|
87
|
+
continue;
|
|
88
|
+
next[nextSlotIndex] = char;
|
|
89
|
+
nextSlotIndex += 1;
|
|
90
|
+
}
|
|
91
|
+
return next;
|
|
92
|
+
}
|
|
93
|
+
function slotValuesToRaw(slotValues) {
|
|
94
|
+
return slotValues.join("");
|
|
95
|
+
}
|
|
96
|
+
function hasRemainingSlotBeforeLastFilled(tokens, tokenIndex, lastFilledSlotIndex) {
|
|
97
|
+
for (let index = tokenIndex + 1; index < tokens.length; index += 1) {
|
|
98
|
+
const token = tokens[index];
|
|
99
|
+
if (!token || token.type !== "slot")
|
|
100
|
+
continue;
|
|
101
|
+
return token.slotIndex <= lastFilledSlotIndex;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
function slotValuesToMasked(tokens, slotValues) {
|
|
106
|
+
let lastFilledSlotIndex = -1;
|
|
107
|
+
for (let index = slotValues.length - 1; index >= 0; index -= 1) {
|
|
108
|
+
const value = slotValues[index];
|
|
109
|
+
if (!value)
|
|
110
|
+
continue;
|
|
111
|
+
lastFilledSlotIndex = index;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
if (lastFilledSlotIndex < 0)
|
|
115
|
+
return "";
|
|
116
|
+
let masked = "";
|
|
117
|
+
let hasWrittenAnySlot = false;
|
|
118
|
+
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
|
|
119
|
+
const token = tokens[tokenIndex];
|
|
120
|
+
if (!token)
|
|
121
|
+
continue;
|
|
122
|
+
if (token.type === "separator") {
|
|
123
|
+
if (!hasWrittenAnySlot)
|
|
124
|
+
continue;
|
|
125
|
+
if (!hasRemainingSlotBeforeLastFilled(tokens, tokenIndex, lastFilledSlotIndex))
|
|
126
|
+
continue;
|
|
127
|
+
masked += token.value;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (token.slotIndex > lastFilledSlotIndex)
|
|
131
|
+
break;
|
|
132
|
+
const value = slotValues[token.slotIndex] ?? "";
|
|
133
|
+
if (!value)
|
|
134
|
+
continue;
|
|
135
|
+
masked += value;
|
|
136
|
+
hasWrittenAnySlot = true;
|
|
137
|
+
}
|
|
138
|
+
return masked;
|
|
139
|
+
}
|
|
140
|
+
function areSlotValuesEqual(left, right) {
|
|
141
|
+
if (left.length !== right.length)
|
|
142
|
+
return false;
|
|
143
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
144
|
+
if ((left[index] ?? "") !== (right[index] ?? ""))
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
function clampIndex(index, slotCount) {
|
|
150
|
+
if (slotCount <= 0)
|
|
151
|
+
return 0;
|
|
152
|
+
if (index < 0)
|
|
153
|
+
return 0;
|
|
154
|
+
if (index > slotCount - 1)
|
|
155
|
+
return slotCount - 1;
|
|
156
|
+
return index;
|
|
157
|
+
}
|
|
158
|
+
const SgInputOTPBase = React.forwardRef(function SgInputOTPBase(props, ref) {
|
|
159
|
+
const i18n = useComponentsI18n();
|
|
160
|
+
const { id, label, hintText, mask, value, defaultValue, error, className, groupClassName, slotClassName, separatorClassName, inputProps, width, enabled = true, readOnly = false, required = false, requiredMessage, validation, validateOnBlur = true, onValidation, onChange, onRawChange, onComplete, onEnter, onExit, onClear, name, onFieldChange, onFieldBlur, hiddenFieldRef } = props;
|
|
161
|
+
const parsedMask = React.useMemo(() => parseMask(mask), [mask]);
|
|
162
|
+
const tokens = parsedMask.tokens;
|
|
163
|
+
const slots = parsedMask.slots;
|
|
164
|
+
const slotCount = slots.length;
|
|
165
|
+
const makeSlotValues = React.useCallback((text) => textToSlotValues(text, slots), [slots]);
|
|
166
|
+
const [slotValues, setSlotValues] = React.useState(() => {
|
|
167
|
+
const seeded = normalizeExternalValue(value ?? defaultValue ?? "");
|
|
168
|
+
return makeSlotValues(seeded);
|
|
169
|
+
});
|
|
170
|
+
const slotValuesRef = React.useRef(slotValues);
|
|
171
|
+
const slotRefs = React.useRef([]);
|
|
172
|
+
const hiddenInputRef = React.useRef(null);
|
|
173
|
+
const hasInteractedRef = React.useRef(false);
|
|
174
|
+
const focusWithinRef = React.useRef(false);
|
|
175
|
+
const [internalError, setInternalError] = React.useState(null);
|
|
176
|
+
const { ref: inputRef, className: inputClassName, onChange: onSlotInputChange, onPaste: onSlotInputPaste, onKeyDown: onSlotInputKeyDown, onFocus: onSlotInputFocus, onBlur: onSlotInputBlur, autoComplete: slotAutoComplete, inputMode: slotInputMode, pattern: slotPattern, disabled: inputDisabled, readOnly: inputReadOnly, autoFocus: slotAutoFocus, ...restSlotInputProps } = inputProps ?? {};
|
|
177
|
+
React.useEffect(() => {
|
|
178
|
+
slotValuesRef.current = slotValues;
|
|
179
|
+
}, [slotValues]);
|
|
180
|
+
React.useEffect(() => {
|
|
181
|
+
if (slotRefs.current.length <= slotCount)
|
|
182
|
+
return;
|
|
183
|
+
slotRefs.current = slotRefs.current.slice(0, slotCount);
|
|
184
|
+
}, [slotCount]);
|
|
185
|
+
React.useEffect(() => {
|
|
186
|
+
if (value === undefined)
|
|
187
|
+
return;
|
|
188
|
+
const next = makeSlotValues(normalizeExternalValue(value));
|
|
189
|
+
setSlotValues((prev) => (areSlotValuesEqual(prev, next) ? prev : next));
|
|
190
|
+
}, [makeSlotValues, value]);
|
|
191
|
+
React.useEffect(() => {
|
|
192
|
+
if (value !== undefined)
|
|
193
|
+
return;
|
|
194
|
+
setSlotValues((prev) => {
|
|
195
|
+
const next = makeSlotValues(slotValuesToRaw(prev));
|
|
196
|
+
return areSlotValuesEqual(prev, next) ? prev : next;
|
|
197
|
+
});
|
|
198
|
+
}, [makeSlotValues, value]);
|
|
199
|
+
const focusSlot = React.useCallback((requestedIndex) => {
|
|
200
|
+
const nextIndex = clampIndex(requestedIndex, slotCount);
|
|
201
|
+
const node = slotRefs.current[nextIndex];
|
|
202
|
+
if (!node)
|
|
203
|
+
return;
|
|
204
|
+
node.focus();
|
|
205
|
+
node.select();
|
|
206
|
+
}, [slotCount]);
|
|
207
|
+
const runValidation = React.useCallback((rawValue, maskedValue) => {
|
|
208
|
+
if (!rawValue && required) {
|
|
209
|
+
const message = requiredMessage ?? t(i18n, "components.inputs.required");
|
|
210
|
+
setInternalError(message);
|
|
211
|
+
onValidation?.(message);
|
|
212
|
+
return message;
|
|
213
|
+
}
|
|
214
|
+
if (validation) {
|
|
215
|
+
const message = validation(rawValue, maskedValue);
|
|
216
|
+
setInternalError(message);
|
|
217
|
+
onValidation?.(message ?? null);
|
|
218
|
+
return message ?? null;
|
|
219
|
+
}
|
|
220
|
+
setInternalError(null);
|
|
221
|
+
onValidation?.(null);
|
|
222
|
+
return null;
|
|
223
|
+
}, [i18n, onValidation, required, requiredMessage, validation]);
|
|
224
|
+
const commitSlotValues = React.useCallback((nextSlotValues, options) => {
|
|
225
|
+
const previousRawValue = slotValuesToRaw(slotValuesRef.current);
|
|
226
|
+
const nextRawValue = slotValuesToRaw(nextSlotValues);
|
|
227
|
+
const nextMaskedValue = slotValuesToMasked(tokens, nextSlotValues);
|
|
228
|
+
hasInteractedRef.current = true;
|
|
229
|
+
slotValuesRef.current = nextSlotValues;
|
|
230
|
+
setSlotValues(nextSlotValues);
|
|
231
|
+
if (hiddenInputRef.current) {
|
|
232
|
+
hiddenInputRef.current.value = nextRawValue;
|
|
233
|
+
}
|
|
234
|
+
onChange?.(nextMaskedValue);
|
|
235
|
+
onRawChange?.(nextRawValue);
|
|
236
|
+
onFieldChange?.(nextRawValue);
|
|
237
|
+
const shouldValidateNow = options?.triggerValidation ?? !validateOnBlur;
|
|
238
|
+
if (shouldValidateNow) {
|
|
239
|
+
runValidation(nextRawValue, nextMaskedValue);
|
|
240
|
+
}
|
|
241
|
+
const isComplete = nextSlotValues.length > 0 && nextSlotValues.every((part) => part.length === 1);
|
|
242
|
+
if (isComplete) {
|
|
243
|
+
onComplete?.(nextMaskedValue);
|
|
244
|
+
}
|
|
245
|
+
if (previousRawValue.length > 0 && nextRawValue.length === 0) {
|
|
246
|
+
onClear?.();
|
|
247
|
+
}
|
|
248
|
+
if (options?.focusIndex === undefined)
|
|
249
|
+
return;
|
|
250
|
+
const nextFocusIndex = clampIndex(options.focusIndex, slotCount);
|
|
251
|
+
if (typeof window !== "undefined") {
|
|
252
|
+
window.requestAnimationFrame(() => focusSlot(nextFocusIndex));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
focusSlot(nextFocusIndex);
|
|
256
|
+
}, [
|
|
257
|
+
focusSlot,
|
|
258
|
+
onChange,
|
|
259
|
+
onClear,
|
|
260
|
+
onComplete,
|
|
261
|
+
onFieldChange,
|
|
262
|
+
onRawChange,
|
|
263
|
+
runValidation,
|
|
264
|
+
slotCount,
|
|
265
|
+
tokens,
|
|
266
|
+
validateOnBlur
|
|
267
|
+
]);
|
|
268
|
+
const applyTextFrom = React.useCallback((startSlotIndex, text, replaceTail) => {
|
|
269
|
+
if (!text)
|
|
270
|
+
return;
|
|
271
|
+
const next = [...slotValuesRef.current];
|
|
272
|
+
const boundedStart = clampIndex(startSlotIndex, slotCount);
|
|
273
|
+
if (replaceTail) {
|
|
274
|
+
for (let index = boundedStart; index < next.length; index += 1) {
|
|
275
|
+
next[index] = "";
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
let pointer = boundedStart;
|
|
279
|
+
for (const char of text) {
|
|
280
|
+
if (pointer >= slots.length)
|
|
281
|
+
break;
|
|
282
|
+
const slot = slots[pointer];
|
|
283
|
+
if (!slot)
|
|
284
|
+
break;
|
|
285
|
+
if (!isAcceptedChar(slot, char))
|
|
286
|
+
continue;
|
|
287
|
+
next[pointer] = char;
|
|
288
|
+
pointer += 1;
|
|
289
|
+
}
|
|
290
|
+
const focusIndex = clampIndex(pointer, slotCount);
|
|
291
|
+
commitSlotValues(next, { focusIndex });
|
|
292
|
+
}, [commitSlotValues, slotCount, slots]);
|
|
293
|
+
React.useImperativeHandle(ref, () => ({
|
|
294
|
+
focus: (slotIndex = 0) => {
|
|
295
|
+
focusSlot(slotIndex);
|
|
296
|
+
},
|
|
297
|
+
clear: () => {
|
|
298
|
+
commitSlotValues(buildEmptySlotValues(slots), {
|
|
299
|
+
focusIndex: 0,
|
|
300
|
+
triggerValidation: !validateOnBlur
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
getRawValue: () => slotValuesToRaw(slotValuesRef.current),
|
|
304
|
+
getMaskedValue: () => slotValuesToMasked(tokens, slotValuesRef.current)
|
|
305
|
+
}), [commitSlotValues, focusSlot, slots, tokens, validateOnBlur]);
|
|
306
|
+
const handleGroupFocusCapture = React.useCallback(() => {
|
|
307
|
+
if (focusWithinRef.current)
|
|
308
|
+
return;
|
|
309
|
+
focusWithinRef.current = true;
|
|
310
|
+
onEnter?.();
|
|
311
|
+
}, [onEnter]);
|
|
312
|
+
const handleGroupBlurCapture = React.useCallback((event) => {
|
|
313
|
+
const nextTarget = event.relatedTarget;
|
|
314
|
+
if (nextTarget && event.currentTarget.contains(nextTarget))
|
|
315
|
+
return;
|
|
316
|
+
focusWithinRef.current = false;
|
|
317
|
+
const nextRawValue = slotValuesToRaw(slotValuesRef.current);
|
|
318
|
+
const nextMaskedValue = slotValuesToMasked(tokens, slotValuesRef.current);
|
|
319
|
+
if (validateOnBlur || hasInteractedRef.current) {
|
|
320
|
+
runValidation(nextRawValue, nextMaskedValue);
|
|
321
|
+
}
|
|
322
|
+
onExit?.();
|
|
323
|
+
onFieldBlur?.(nextRawValue);
|
|
324
|
+
}, [onExit, onFieldBlur, runValidation, tokens, validateOnBlur]);
|
|
325
|
+
const handleSlotChange = React.useCallback((slotIndex, event) => {
|
|
326
|
+
onSlotInputChange?.(event);
|
|
327
|
+
if (event.defaultPrevented)
|
|
328
|
+
return;
|
|
329
|
+
const slot = slots[slotIndex];
|
|
330
|
+
if (!slot)
|
|
331
|
+
return;
|
|
332
|
+
const typed = event.currentTarget.value ?? "";
|
|
333
|
+
if (!typed) {
|
|
334
|
+
const next = [...slotValuesRef.current];
|
|
335
|
+
next[slotIndex] = "";
|
|
336
|
+
commitSlotValues(next, { focusIndex: slotIndex });
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (typed.length > 1) {
|
|
340
|
+
applyTextFrom(slotIndex, typed, true);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const nextChar = typed.slice(-1);
|
|
344
|
+
if (!isAcceptedChar(slot, nextChar)) {
|
|
345
|
+
event.currentTarget.value = slotValuesRef.current[slotIndex] ?? "";
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const next = [...slotValuesRef.current];
|
|
349
|
+
next[slotIndex] = nextChar;
|
|
350
|
+
commitSlotValues(next, { focusIndex: slotIndex + 1 });
|
|
351
|
+
}, [applyTextFrom, commitSlotValues, onSlotInputChange, slots]);
|
|
352
|
+
const handleSlotPaste = React.useCallback((slotIndex, event) => {
|
|
353
|
+
onSlotInputPaste?.(event);
|
|
354
|
+
if (event.defaultPrevented)
|
|
355
|
+
return;
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
const pasted = event.clipboardData.getData("text") ?? "";
|
|
358
|
+
if (!pasted)
|
|
359
|
+
return;
|
|
360
|
+
applyTextFrom(slotIndex, pasted, true);
|
|
361
|
+
}, [applyTextFrom, onSlotInputPaste]);
|
|
362
|
+
const handleSlotKeyDown = React.useCallback((slotIndex, event) => {
|
|
363
|
+
onSlotInputKeyDown?.(event);
|
|
364
|
+
if (event.defaultPrevented)
|
|
365
|
+
return;
|
|
366
|
+
const slot = slots[slotIndex];
|
|
367
|
+
if (!slot)
|
|
368
|
+
return;
|
|
369
|
+
if (event.key === "ArrowLeft") {
|
|
370
|
+
event.preventDefault();
|
|
371
|
+
focusSlot(slotIndex - 1);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (event.key === "ArrowRight") {
|
|
375
|
+
event.preventDefault();
|
|
376
|
+
focusSlot(slotIndex + 1);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (event.key === "Backspace") {
|
|
380
|
+
event.preventDefault();
|
|
381
|
+
const next = [...slotValuesRef.current];
|
|
382
|
+
const currentValue = next[slotIndex] ?? "";
|
|
383
|
+
if (currentValue) {
|
|
384
|
+
next[slotIndex] = "";
|
|
385
|
+
commitSlotValues(next, { focusIndex: slotIndex });
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (slotIndex === 0)
|
|
389
|
+
return;
|
|
390
|
+
next[slotIndex - 1] = "";
|
|
391
|
+
commitSlotValues(next, { focusIndex: slotIndex - 1 });
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (event.key === "Delete") {
|
|
395
|
+
event.preventDefault();
|
|
396
|
+
const next = [...slotValuesRef.current];
|
|
397
|
+
next[slotIndex] = "";
|
|
398
|
+
commitSlotValues(next, { focusIndex: slotIndex });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
402
|
+
if (!isAcceptedChar(slot, event.key)) {
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}, [commitSlotValues, focusSlot, onSlotInputKeyDown, slots]);
|
|
407
|
+
const hasError = Boolean(error ?? internalError);
|
|
408
|
+
const resolvedError = error ?? internalError ?? undefined;
|
|
409
|
+
const isDisabled = enabled === false || Boolean(inputDisabled);
|
|
410
|
+
const isReadOnly = readOnly || Boolean(inputReadOnly);
|
|
411
|
+
const mergedSlotClass = [
|
|
412
|
+
"h-12 w-11 border bg-white px-0 text-center text-base font-medium shadow-sm outline-none transition-all",
|
|
413
|
+
hasError
|
|
414
|
+
? "border-[hsl(var(--destructive))] focus:border-[hsl(var(--destructive))] focus:ring-2 focus:ring-[hsl(var(--destructive)/0.25)]"
|
|
415
|
+
: "border-border focus:border-[hsl(var(--primary))] focus:ring-2 focus:ring-[hsl(var(--primary)/0.25)]",
|
|
416
|
+
"disabled:cursor-not-allowed disabled:bg-muted/40 disabled:text-foreground/40",
|
|
417
|
+
"read-only:cursor-default read-only:bg-muted/30",
|
|
418
|
+
inputClassName ?? "",
|
|
419
|
+
slotClassName ?? ""
|
|
420
|
+
]
|
|
421
|
+
.filter(Boolean)
|
|
422
|
+
.join(" ");
|
|
423
|
+
const setHiddenInputRef = React.useMemo(() => mergeRefs(hiddenFieldRef, hiddenInputRef), [hiddenFieldRef]);
|
|
424
|
+
const setSlotRef = React.useCallback((slotIndex, node) => {
|
|
425
|
+
slotRefs.current[slotIndex] = node;
|
|
426
|
+
if (slotIndex !== 0)
|
|
427
|
+
return;
|
|
428
|
+
if (!inputRef)
|
|
429
|
+
return;
|
|
430
|
+
if (typeof inputRef === "function") {
|
|
431
|
+
inputRef(node);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (typeof inputRef === "object" && "current" in inputRef) {
|
|
435
|
+
inputRef.current = node;
|
|
436
|
+
}
|
|
437
|
+
}, [inputRef]);
|
|
438
|
+
const labelText = label ?? "";
|
|
439
|
+
const firstSlotId = `${id}-slot-0`;
|
|
440
|
+
return (_jsxs("div", { className: className, style: { width: toCssSize(width) }, children: [labelText ? (_jsxs("label", { htmlFor: firstSlotId, className: "mb-2 block text-sm font-medium text-foreground/80", children: [labelText, required ? (_jsx("span", { className: "ml-1 text-[hsl(var(--destructive))]", "aria-hidden": "true", children: "*" })) : null] })) : null, _jsx("div", { className: groupClassName ?? "inline-flex items-center gap-2", onFocusCapture: handleGroupFocusCapture, onBlurCapture: handleGroupBlurCapture, role: "group", "aria-label": labelText || id, "aria-invalid": hasError || undefined, children: tokens.map((token, tokenIndex) => {
|
|
441
|
+
if (token.type === "separator") {
|
|
442
|
+
return (_jsx("span", { className: separatorClassName ??
|
|
443
|
+
"select-none px-1 text-lg font-semibold leading-none text-foreground/60", "aria-hidden": "true", children: token.value }, `${id}-separator-${tokenIndex}`));
|
|
444
|
+
}
|
|
445
|
+
const slotIndex = token.slotIndex;
|
|
446
|
+
const slotValue = slotValues[slotIndex] ?? "";
|
|
447
|
+
const computedInputMode = token.slotKind === "digit" ? "numeric" : slotInputMode ?? "text";
|
|
448
|
+
const computedPattern = token.slotKind === "digit"
|
|
449
|
+
? "[0-9]*"
|
|
450
|
+
: slotPattern ?? "[A-Za-z0-9]*";
|
|
451
|
+
const slotShapeClass = slotCount === 1
|
|
452
|
+
? "rounded-2xl"
|
|
453
|
+
: slotIndex === 0
|
|
454
|
+
? "rounded-l-2xl rounded-r-md"
|
|
455
|
+
: slotIndex === slotCount - 1
|
|
456
|
+
? "rounded-r-2xl rounded-l-md"
|
|
457
|
+
: "rounded-md";
|
|
458
|
+
return (_createElement("input", { ...restSlotInputProps, key: `${id}-slot-${slotIndex}`, id: `${id}-slot-${slotIndex}`, type: "text", value: slotValue, maxLength: 1, autoComplete: slotIndex === 0 ? slotAutoComplete ?? "one-time-code" : "off", autoFocus: slotIndex === 0 ? slotAutoFocus : false, inputMode: computedInputMode, pattern: computedPattern, className: `${mergedSlotClass} ${slotShapeClass}`, disabled: isDisabled, readOnly: isReadOnly, "aria-label": labelText ? `${labelText} ${slotIndex + 1}` : `OTP ${slotIndex + 1}`, ref: (node) => setSlotRef(slotIndex, node), onFocus: (event) => {
|
|
459
|
+
event.currentTarget.select();
|
|
460
|
+
onSlotInputFocus?.(event);
|
|
461
|
+
}, onBlur: (event) => {
|
|
462
|
+
onSlotInputBlur?.(event);
|
|
463
|
+
}, onChange: (event) => handleSlotChange(slotIndex, event), onPaste: (event) => handleSlotPaste(slotIndex, event), onKeyDown: (event) => handleSlotKeyDown(slotIndex, event) }));
|
|
464
|
+
}) }), hintText ? _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: hintText }) : null, resolvedError ? _jsx("p", { "data-sg-error": true, className: "mt-1 text-xs text-red-600", children: resolvedError }) : null, _jsx("input", { id: `${id}-value`, type: "hidden", name: name, value: slotValuesToRaw(slotValues), ref: setHiddenInputRef, readOnly: true, "aria-hidden": "true" })] }));
|
|
465
|
+
});
|
|
466
|
+
function createSyntheticChangeEvent(name, value) {
|
|
467
|
+
const target = { name, value };
|
|
468
|
+
return {
|
|
469
|
+
target,
|
|
470
|
+
currentTarget: target
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function createSyntheticBlurEvent(name, value) {
|
|
474
|
+
const target = { name, value };
|
|
475
|
+
return {
|
|
476
|
+
target,
|
|
477
|
+
currentTarget: target,
|
|
478
|
+
relatedTarget: null
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
export const SgInputOTP = React.forwardRef(function SgInputOTP(props, ref) {
|
|
482
|
+
const { control, name, register, rules, ...rest } = props;
|
|
483
|
+
if (name && register) {
|
|
484
|
+
const registration = register(name, rules);
|
|
485
|
+
return (_jsx(SgInputOTPBase, { ...rest, ref: ref, name: name, hiddenFieldRef: registration.ref, onFieldChange: (rawValue) => {
|
|
486
|
+
registration.onChange(createSyntheticChangeEvent(name, rawValue));
|
|
487
|
+
}, onFieldBlur: (rawValue) => {
|
|
488
|
+
registration.onBlur(createSyntheticBlurEvent(name, rawValue));
|
|
489
|
+
} }));
|
|
490
|
+
}
|
|
491
|
+
if (control && name) {
|
|
492
|
+
return (_jsx(Controller, { name: name, control: control, render: ({ field, fieldState }) => (_jsx(SgInputOTPBase, { ...rest, ref: ref, name: name, value: normalizeExternalValue(field.value), error: rest.error ?? fieldState.error?.message, hiddenFieldRef: field.ref, onFieldChange: (rawValue) => {
|
|
493
|
+
field.onChange(rawValue);
|
|
494
|
+
}, onFieldBlur: () => {
|
|
495
|
+
field.onBlur();
|
|
496
|
+
} })) }));
|
|
497
|
+
}
|
|
498
|
+
return _jsx(SgInputOTPBase, { ...rest, ref: ref, name: name });
|
|
499
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { FieldValues, RegisterOptions, UseFormRegister } from "react-hook-form";
|
|
3
|
+
import type { RhfFieldProps } from "../rhf";
|
|
4
|
+
export type SgToggleSwitchProps = {
|
|
5
|
+
id: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
labelClassName?: string;
|
|
11
|
+
switchClassName?: string;
|
|
12
|
+
trackClassName?: string;
|
|
13
|
+
thumbClassName?: string;
|
|
14
|
+
onIcon?: React.ReactNode;
|
|
15
|
+
offIcon?: React.ReactNode;
|
|
16
|
+
checked?: boolean;
|
|
17
|
+
defaultChecked?: boolean;
|
|
18
|
+
width?: number | string;
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
readOnly?: boolean;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
requiredMessage?: string;
|
|
23
|
+
validateOnBlur?: boolean;
|
|
24
|
+
validation?: (checked: boolean) => string | null;
|
|
25
|
+
onValidation?: (message: string | null) => void;
|
|
26
|
+
onChange?: (checked: boolean) => void;
|
|
27
|
+
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & {
|
|
28
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
29
|
+
};
|
|
30
|
+
register?: UseFormRegister<FieldValues>;
|
|
31
|
+
rules?: RegisterOptions<FieldValues>;
|
|
32
|
+
} & RhfFieldProps;
|
|
33
|
+
export declare function SgToggleSwitch(props: Readonly<SgToggleSwitchProps>): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export declare const SgSwitch: typeof SgToggleSwitch;
|
|
35
|
+
export type SgSwitchProps = SgToggleSwitchProps;
|
|
36
|
+
//# sourceMappingURL=SgToggleSwitch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SgToggleSwitch.d.ts","sourceRoot":"","sources":["../../src/inputs/SgToggleSwitch.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAGV,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAuC5C,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,GAAG;QACzD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;KACnC,CAAC;IACF,QAAQ,CAAC,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;CACtC,GAAG,aAAa,CAAC;AAsNlB,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,mBAAmB,CAAC,2CAmDlE;AAED,eAAO,MAAM,QAAQ,uBAAiB,CAAC;AACvC,MAAM,MAAM,aAAa,GAAG,mBAAmB,CAAC"}
|