windly-ui 1.0.0 → 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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -33
  3. package/package.json +49 -11
  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/app/app.vue +0 -10
  9. package/app/assets/css/tailwind.css +0 -3
  10. package/app/components/doc/component.vue +0 -34
  11. package/app/components/doc/megaSection.vue +0 -56
  12. package/app/components/doc/section.vue +0 -56
  13. package/app/pages/_docs.vue +0 -2420
  14. package/app/pages/index.vue +0 -137
  15. package/nuxt.config.ts +0 -19
  16. package/public/doc/avatar.avif +0 -0
  17. package/public/favicon.ico +0 -0
  18. package/public/robots.txt +0 -2
  19. package/tailwind.config.ts +0 -28
  20. package/tsconfig.json +0 -18
  21. package/ui-library/components/Accordion.vue +0 -118
  22. package/ui-library/components/Avatar.vue +0 -121
  23. package/ui-library/components/Badge.vue +0 -116
  24. package/ui-library/components/Breadcrumbs.vue +0 -138
  25. package/ui-library/components/Button.vue +0 -154
  26. package/ui-library/components/Card.vue +0 -84
  27. package/ui-library/components/Checkbox.vue +0 -148
  28. package/ui-library/components/CodeBlock.vue +0 -99
  29. package/ui-library/components/ColorPicker.vue +0 -302
  30. package/ui-library/components/Date.vue +0 -240
  31. package/ui-library/components/Dialog.vue +0 -187
  32. package/ui-library/components/Divider.vue +0 -78
  33. package/ui-library/components/Drawer.vue +0 -67
  34. package/ui-library/components/Dropdown.vue +0 -248
  35. package/ui-library/components/FileUploader.vue +0 -330
  36. package/ui-library/components/Icon.vue +0 -82
  37. package/ui-library/components/Image.vue +0 -78
  38. package/ui-library/components/Input.vue +0 -531
  39. package/ui-library/components/Radio.vue +0 -356
  40. package/ui-library/components/ScrollArea.vue +0 -43
  41. package/ui-library/components/Select.vue +0 -309
  42. package/ui-library/components/Stepper.vue +0 -361
  43. package/ui-library/components/TabPanel.vue +0 -25
  44. package/ui-library/components/Table.vue +0 -152
  45. package/ui-library/components/Tabs.vue +0 -71
  46. package/ui-library/components/Textarea.vue +0 -233
  47. package/ui-library/components/Toggle.vue +0 -319
  48. package/ui-library/components/Tooltip.vue +0 -200
  49. package/ui-library/components/plugin.ts +0 -8
  50. package/ui-library/components/uiProps.ts +0 -11
  51. package/ui-library/components/useUiClasses.ts +0 -159
  52. 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>