windly-ui 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -33
  3. package/package.json +47 -15
  4. package/ui-library/dist/module.d.mts +3 -0
  5. package/ui-library/dist/module.json +8 -0
  6. package/ui-library/dist/module.mjs +22 -0
  7. package/ui-library/dist/types.d.mts +7 -0
  8. package/ui-library/components/Accordion.vue +0 -118
  9. package/ui-library/components/Avatar.vue +0 -121
  10. package/ui-library/components/Badge.vue +0 -116
  11. package/ui-library/components/Breadcrumbs.vue +0 -138
  12. package/ui-library/components/Button.vue +0 -154
  13. package/ui-library/components/Card.vue +0 -84
  14. package/ui-library/components/Checkbox.vue +0 -148
  15. package/ui-library/components/CodeBlock.vue +0 -99
  16. package/ui-library/components/ColorPicker.vue +0 -302
  17. package/ui-library/components/Date.vue +0 -240
  18. package/ui-library/components/Dialog.vue +0 -187
  19. package/ui-library/components/Divider.vue +0 -78
  20. package/ui-library/components/Drawer.vue +0 -67
  21. package/ui-library/components/Dropdown.vue +0 -248
  22. package/ui-library/components/FileUploader.vue +0 -330
  23. package/ui-library/components/Icon.vue +0 -82
  24. package/ui-library/components/Image.vue +0 -78
  25. package/ui-library/components/Input.vue +0 -531
  26. package/ui-library/components/Radio.vue +0 -356
  27. package/ui-library/components/ScrollArea.vue +0 -43
  28. package/ui-library/components/Select.vue +0 -309
  29. package/ui-library/components/Stepper.vue +0 -361
  30. package/ui-library/components/TabPanel.vue +0 -25
  31. package/ui-library/components/Table.vue +0 -152
  32. package/ui-library/components/Tabs.vue +0 -71
  33. package/ui-library/components/Textarea.vue +0 -233
  34. package/ui-library/components/Toggle.vue +0 -319
  35. package/ui-library/components/Tooltip.vue +0 -200
  36. package/ui-library/components/plugin.ts +0 -8
  37. package/ui-library/components/uiProps.ts +0 -11
  38. package/ui-library/components/useUiClasses.ts +0 -159
  39. package/ui-library/module.ts +0 -20
@@ -1,531 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref } from "vue";
3
-
4
- type SupportedInputType =
5
- | 'text'
6
- | 'email'
7
- | 'password'
8
- | 'number'
9
- | 'tel'
10
- | 'url'
11
- | 'search'
12
- | 'date'
13
- | 'datetime-local'
14
- | 'month'
15
- | 'week'
16
- | 'time';
17
-
18
- type ValidationRule =
19
- | "alphabets"
20
- | "numbers"
21
- | "spaces"
22
- | "name"
23
- | "punctuation"
24
- | "integer"
25
- | "positive"
26
- | "negative"
27
- | "decimal"
28
- | "scientific"
29
- | "uppercase"
30
- | "lowercase";
31
-
32
- type ValidateOn = "input" | "blur";
33
-
34
- const props = withDefaults(
35
- defineProps<{
36
- modelValue: string | number;
37
-
38
- label: string;
39
- hint?: string;
40
- placeholder?: string;
41
-
42
- case?: "upper" | "lower" | "capitalize" | "normal" | null;
43
-
44
- size?: string;
45
- color?: string;
46
- rounded?: string;
47
- dense?: boolean;
48
-
49
- outlined?: boolean;
50
- borderless?: boolean;
51
- flat?: boolean;
52
-
53
- type?: SupportedInputType;
54
-
55
- allow?: ValidationRule[];
56
- deny?: ValidationRule[];
57
-
58
- supportedValues?: Array<string | number>;
59
- unsupportedValues?: Array<string | number>;
60
-
61
- required?: boolean;
62
-
63
- regex?: string | RegExp;
64
-
65
- minLength?: number;
66
- maxLength?: number;
67
-
68
- disabled?: boolean;
69
- readonly?: boolean;
70
- autocomplete?: string;
71
-
72
- validateOn?: ValidateOn;
73
- }>(),
74
- {
75
- modelValue: "",
76
-
77
- hint: "",
78
- placeholder: "",
79
-
80
- case: null,
81
-
82
- size: "md",
83
- color: "primary",
84
- rounded: "md",
85
- dense: false,
86
-
87
- outlined: false,
88
- borderless: false,
89
- flat: false,
90
-
91
- type: "text",
92
-
93
- allow: () => [],
94
- deny: () => [],
95
-
96
- supportedValues: () => [],
97
- unsupportedValues: () => [],
98
-
99
- required: false,
100
-
101
- regex: "",
102
-
103
- minLength: undefined,
104
- maxLength: undefined,
105
-
106
- disabled: false,
107
- readonly: false,
108
- autocomplete: "off",
109
-
110
- validateOn: "input",
111
- }
112
- );
113
-
114
- const emit = defineEmits<{
115
- (e: "update:modelValue", value: string): void;
116
- (e: "valid"): void;
117
- (e: "invalid", error: string): void;
118
- }>();
119
-
120
- const validationError = ref("");
121
-
122
- const NATIVE_PATTERNS: Record<string, RegExp> = {
123
- email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
124
- url: /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/\S*)?$/i,
125
- tel: /^\+?[1-9]\d{6,14}$/,
126
- };
127
-
128
- const RULE_PATTERNS: Record<ValidationRule, RegExp> = {
129
- alphabets: /^[A-Za-z]+$/,
130
- numbers: /^[0-9]+$/,
131
- spaces: /\s/,
132
- name: /^[A-Za-z.'-\s]+$/,
133
- punctuation: /[.,!?;:'"()-]/,
134
- integer: /^-?\d+$/,
135
- positive: /^\d+$/,
136
- negative: /^-\d+$/,
137
- decimal: /^-?\d*\.?\d+$/,
138
- scientific: /^-?\d+(\.\d+)?e[-+]?\d+$/i,
139
- uppercase: /^[A-Z]+$/,
140
- lowercase: /^[a-z]+$/,
141
- };
142
-
143
- function validate(value: string): boolean {
144
- validationError.value = "";
145
-
146
- if (props.required && !value.trim()) {
147
- validationError.value = "This field is required.";
148
- return false;
149
- }
150
-
151
- if (
152
- props.minLength !== undefined &&
153
- value.length < props.minLength
154
- ) {
155
- validationError.value =
156
- `Minimum ${props.minLength} characters required.`;
157
- return false;
158
- }
159
-
160
- if (
161
- props.maxLength !== undefined &&
162
- value.length > props.maxLength
163
- ) {
164
- validationError.value =
165
- `Maximum ${props.maxLength} characters allowed.`;
166
- return false;
167
- }
168
-
169
- if (
170
- props.supportedValues.length &&
171
- !props.supportedValues.includes(value)
172
- ) {
173
- validationError.value = "Value is not allowed.";
174
- return false;
175
- }
176
-
177
- if (
178
- props.unsupportedValues.includes(value)
179
- ) {
180
- validationError.value = "Value is not permitted.";
181
- return false;
182
- }
183
-
184
- const nativePattern = NATIVE_PATTERNS[props.type];
185
-
186
- if (
187
- nativePattern &&
188
- value &&
189
- !nativePattern.test(value)
190
- ) {
191
- validationError.value =
192
- `Invalid ${props.type} format.`;
193
- return false;
194
- }
195
-
196
- for (const rule of props.allow) {
197
- const regex = RULE_PATTERNS[rule];
198
-
199
- if (
200
- regex &&
201
- value &&
202
- !regex.test(value)
203
- ) {
204
- validationError.value =
205
- `Failed validation: ${rule}`;
206
- return false;
207
- }
208
- }
209
-
210
- for (const rule of props.deny) {
211
- const regex = RULE_PATTERNS[rule];
212
-
213
- if (
214
- regex &&
215
- regex.test(value)
216
- ) {
217
- validationError.value =
218
- `Contains unsupported value: ${rule}`;
219
- return false;
220
- }
221
- }
222
-
223
- if (props.regex) {
224
- const regex =
225
- props.regex instanceof RegExp
226
- ? props.regex
227
- : new RegExp(props.regex);
228
-
229
- if (!regex.test(value)) {
230
- validationError.value =
231
- "Custom validation failed.";
232
- return false;
233
- }
234
- }
235
-
236
- return true;
237
- }
238
-
239
- function runValidation(value: string) {
240
- const isValid = validate(value);
241
-
242
- if (isValid) {
243
- emit("valid");
244
- } else {
245
- emit("invalid", validationError.value);
246
- }
247
-
248
- return isValid;
249
- }
250
-
251
- function handleInput(event: Event) {
252
- const target = event.target as HTMLInputElement;
253
- let value = target.value;
254
-
255
- if (
256
- props.maxLength !== undefined &&
257
- value.length > props.maxLength
258
- ) {
259
- value = value.slice(0, props.maxLength);
260
- }
261
-
262
- emit("update:modelValue", value);
263
-
264
- if (props.validateOn === "input") {
265
- runValidation(value);
266
- }
267
- }
268
-
269
- function handleBlur(event: Event) {
270
- if (props.validateOn !== "blur") return;
271
-
272
- const target = event.target as HTMLInputElement;
273
-
274
- runValidation(target.value);
275
- }
276
-
277
- const isHex = (val: string) => val.startsWith('#')
278
-
279
- const roundedMap = {
280
- none: "rounded-none",
281
- sm: "rounded-sm",
282
- md: "rounded-md",
283
- lg: "rounded-lg",
284
- xl: "rounded-xl",
285
- full: "rounded-full",
286
- };
287
-
288
- const computedClass = computed(() => {
289
- let classes =
290
- "input block w-full transition-all duration-300";
291
-
292
- // style mode
293
- if (props.borderless) {
294
- classes += " border-none bg-transparent";
295
- } else if (props.outlined) {
296
- classes += " border";
297
- } else {
298
- classes += " border-0 border-b";
299
- }
300
-
301
- // rounded
302
- classes += ` ${
303
- roundedMap[props.rounded as keyof typeof roundedMap] ?? roundedMap.md
304
- }`;
305
-
306
- // flat
307
- if (!props.flat) {
308
- classes += " shadow-sm";
309
- }
310
-
311
- // disabled
312
- if (props.disabled) {
313
- classes += " opacity-50 cursor-not-allowed";
314
- }
315
-
316
- // size
317
- if (props.size === "sm") {
318
- classes += " text-sm";
319
- } else if (props.size === "lg") {
320
- classes += " text-lg";
321
- } else {
322
- classes += " text-base";
323
- }
324
-
325
- if (props.case === "upper") {
326
- classes += " uppercase";
327
- } else if (props.case === "lower") {
328
- classes += " lowercase";
329
- } else if (props.case === "capitalize") {
330
- classes += " capitalize";
331
- }
332
-
333
- // color / error
334
- if (validationError.value) {
335
- classes += " border-red-500";
336
- } else {
337
- classes += ` ${
338
- props.color && !isHex(props.color)
339
- ? `border-${props.color} focus:ring-${props.color} text-${props.color} `
340
- : ""
341
- }`;
342
- }
343
-
344
- if (props.modelValue !== null && props.modelValue !== '' && props.modelValue !== undefined)
345
- classes += " has-value";
346
-
347
- return classes;
348
- });
349
-
350
- const computedStyle = computed(() => {
351
- let styles: Record<string, string> = {};
352
-
353
- if (props.color && isHex(props.color)) {
354
- styles["--tw-ring-color"] = props.color;
355
- styles["border-color"] = props.color;
356
- }
357
-
358
- return styles;
359
- });
360
-
361
- const visibility = ref(false);
362
-
363
- const inputType = computed(() => props.type === "password" ? visibility.value ? "text" : "password" : props.type);
364
-
365
- const inputAttrs = computed(() => {
366
- let inputAttrs = {}
367
- if (props.type) inputAttrs = { ...inputAttrs, type: props.type === "password" ? inputType.value : props.type }
368
- if (props.placeholder) inputAttrs = { ...inputAttrs, placeholder: props.placeholder }
369
- if (props.disabled) inputAttrs = { ...inputAttrs, disabled: props.disabled }
370
- if (props.readonly) inputAttrs = { ...inputAttrs, readonly: props.readonly }
371
- if (props.autocomplete) inputAttrs = { ...inputAttrs, autocomplete: props.autocomplete }
372
- if (props.required) inputAttrs = { ...inputAttrs, required: props.required }
373
- if (props.maxLength) inputAttrs = { ...inputAttrs, maxlength: props.maxLength }
374
- return inputAttrs
375
- });
376
- </script>
377
-
378
- <template>
379
- <div class="input-container" :class="{ dense: !outlined || dense, [`input-${size}`]: size }">
380
- <input
381
- :value="modelValue"
382
- :class="computedClass"
383
- :style="computedStyle"
384
- @input="handleInput"
385
- @blur="handleBlur"
386
- v-bind="inputAttrs"
387
- />
388
-
389
- <label class="label" :class="!isHex(color) ? `text-${color}` : ''" :style="isHex(color) ? { color: color } : {}">
390
- {{ label }}
391
- </label>
392
-
393
- <div class="absolute right-0 top-1/2 transform -translate-y-1/2">
394
- <UIButton v-if="type==='password'" :icon="visibility ? 'visibility_off' : 'visibility'" flat dense color="gray-400" @click="visibility = !visibility" />
395
- </div>
396
- <p
397
- v-if="hint && !validationError"
398
- class="hint-text"
399
- >
400
- {{ hint }}
401
- </p>
402
-
403
- <p
404
- v-if="validationError"
405
- class="error-text"
406
- >
407
- {{ validationError }}
408
- </p>
409
- </div>
410
- </template>
411
- <style scoped>
412
- .input-container {
413
- position: relative;
414
- margin: 20px 0px 10px 0px;
415
- }
416
-
417
- .input-container.dense {
418
- margin: 10px 0;
419
- }
420
-
421
- .input-container .input {
422
- padding: 10px;
423
- }
424
-
425
- .input-container.dense .input {
426
- padding: 6px 10px;
427
- }
428
-
429
- .input:focus {
430
- outline: none;
431
- }
432
-
433
- .label {
434
- position: absolute;
435
- left: 10px;
436
- top: 50%;
437
- transform: translateY(-50%);
438
- color: #9ca3af;
439
- padding: 0 5px;
440
- pointer-events: none;
441
- transition:
442
- top 0.2s ease,
443
- font-size 0.2s ease,
444
- color 0.2s ease,
445
- transform 0.2s ease;
446
- }
447
-
448
-
449
- .input-container:has(input[type="date"]) .label,
450
- .input-container:has(input[type="datetime-local"]) .label,
451
- .input-container:has(input[type="time"]) .label,
452
- .input-container:has(input[type="month"]) .label,
453
- .input-container:has(input[type="week"]) .label {
454
- top: -20%;
455
- left: 5px;
456
- transform: none;
457
- color: gray;
458
- z-index: 1;
459
- }
460
-
461
- .input:focus + .label,
462
- .input.has-value + .label,
463
- .input:placeholder-shown + .label,
464
- .input-container.input-lg.dense .input:focus + .label,
465
- .input-container.input-lg.dense .input.has-value + .label,
466
- .input-container.input-lg.dense .input:placeholder-shown + .label {
467
- top: -20px;
468
- left: 5px;
469
- transform: none;
470
- color: gray;
471
- z-index: 1;
472
- }
473
-
474
- .input-container.dense .input:focus + .label,
475
- .input-container.dense .input.has-value + .label,
476
- .input-container.dense .input:placeholder-shown + .label {
477
- top: -10px;
478
- }
479
-
480
- .input-container.input-lg .input:focus + .label,
481
- .input-container.input-lg .input.has-value + .label,
482
- .input-container.input-lg .input:placeholder-shown + .label {
483
- top: -24px;
484
- }
485
-
486
- .input-container.input-sm .label {
487
- font-size: 14px;
488
- }
489
- .input-container.input-md .label {
490
- font-size: 16px;
491
- }
492
- .input-container.input-lg .label {
493
- font-size: 18px;
494
- }
495
-
496
- .input-container.input-sm .input:focus + .label,
497
- .input-container.input-sm .input.has-value + .label,
498
- .input-container.input-sm .input:placeholder-shown + .label {
499
- font-size: 12px;
500
- }
501
-
502
- .input-container.input-md .input:focus + .label,
503
- .input-container.input-md .input.has-value + .label,
504
- .input-container.input-md .input:placeholder-shown + .label {
505
- font-size: 14px;
506
- }
507
-
508
- .input-container.input-lg .input:focus + .label,
509
- .input-container.input-lg .input.has-value + .label,
510
- .input-container.input-lg .input:placeholder-shown + .label {
511
- font-size: 16px;
512
- }
513
-
514
- .hint-text {
515
- font-size: 12px;
516
- color: #6b7280;
517
- margin-left: 5px;
518
- }
519
-
520
- .char-count {
521
- margin-top: 4px;
522
- font-size: 0.875rem;
523
- color: #6b7280;
524
- }
525
-
526
- .error-text {
527
- margin-top: 4px;
528
- font-size: 0.875rem;
529
- color: #ef4444;
530
- }
531
- </style>