spoko-design-system 0.5.7 → 0.5.9
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/package.json +124 -124
- package/src/components/Input.vue +153 -0
- package/src/pages/components/input.mdx +296 -13
- package/uno-config/index.ts +172 -1
- package/uno-config/theme/shortcuts/index.ts +2 -0
- package/uno-config/theme/shortcuts/inputs.ts +64 -0
- package/src/components/Input.astro +0 -86
package/package.json
CHANGED
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "spoko-design-system",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"private": false,
|
|
5
|
-
"main": "./index.ts",
|
|
6
|
-
"module": "./index.ts",
|
|
7
|
-
"types": "./index.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"import": "./index.ts",
|
|
11
|
-
"require": "./index.ts"
|
|
12
|
-
},
|
|
13
|
-
"./styles/*": "./src/styles/*",
|
|
14
|
-
"./icons": "./icon.config.ts",
|
|
15
|
-
"./icon-collections": "./icon.collections.ts",
|
|
16
|
-
"./uno-config": "./uno-config/index.ts"
|
|
17
|
-
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"dev": "astro dev",
|
|
20
|
-
"start": "astro dev",
|
|
21
|
-
"build": "astro build",
|
|
22
|
-
"preview": "astro preview"
|
|
23
|
-
},
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://github.com/polo-blue/sds"
|
|
27
|
-
},
|
|
28
|
-
"author": {
|
|
29
|
-
"name": "spokospace",
|
|
30
|
-
"email": "szymon@spoko.space",
|
|
31
|
-
"url": "https://spoko.space"
|
|
32
|
-
},
|
|
33
|
-
"homepage": "https://sds.spoko.space/",
|
|
34
|
-
"license": "MIT",
|
|
35
|
-
"keywords": [
|
|
36
|
-
"astro-starter",
|
|
37
|
-
"seo",
|
|
38
|
-
"astro",
|
|
39
|
-
"sds design system",
|
|
40
|
-
"spoko design system"
|
|
41
|
-
],
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@algolia/client-search": "^5.20.3",
|
|
44
|
-
"@astrojs/mdx": "^4.0.8",
|
|
45
|
-
"@astrojs/node": "^9.1.1",
|
|
46
|
-
"@astrojs/sitemap": "^3.2.1",
|
|
47
|
-
"@astrojs/ts-plugin": "^1.10.4",
|
|
48
|
-
"@astrojs/vue": "^5.0.7",
|
|
49
|
-
"@docsearch/css": "^3.9.0",
|
|
50
|
-
"@iconify-json/ant-design": "^1.2.5",
|
|
51
|
-
"@iconify-json/bi": "^1.2.2",
|
|
52
|
-
"@iconify-json/bx": "^1.2.2",
|
|
53
|
-
"@iconify-json/carbon": "^1.2.7",
|
|
54
|
-
"@iconify-json/circle-flags": "^1.2.6",
|
|
55
|
-
"@iconify-json/ei": "^1.2.2",
|
|
56
|
-
"@iconify-json/el": "^1.2.1",
|
|
57
|
-
"@iconify-json/eos-icons": "^1.2.2",
|
|
58
|
-
"@iconify-json/et": "^1.2.1",
|
|
59
|
-
"@iconify-json/flowbite": "^1.2.4",
|
|
60
|
-
"@iconify-json/fluent": "^1.2.14",
|
|
61
|
-
"@iconify-json/fluent-emoji": "1.2.3",
|
|
62
|
-
"@iconify-json/ic": "^1.2.2",
|
|
63
|
-
"@iconify-json/icon-park-outline": "^1.2.2",
|
|
64
|
-
"@iconify-json/la": "^1.2.1",
|
|
65
|
-
"@iconify-json/material-symbols-light": "^1.2.14",
|
|
66
|
-
"@iconify-json/mdi": "^1.2.3",
|
|
67
|
-
"@iconify-json/noto-v1": "^1.2.1",
|
|
68
|
-
"@iconify-json/octicon": "^1.2.5",
|
|
69
|
-
"@iconify-json/ph": "^1.2.2",
|
|
70
|
-
"@iconify-json/simple-icons": "^1.2.26",
|
|
71
|
-
"@iconify-json/system-uicons": "^1.2.2",
|
|
72
|
-
"@iconify-json/uil": "^1.2.3",
|
|
73
|
-
"@iconify/json": "^2.2.310",
|
|
74
|
-
"@iconify/vue": "^4.3.0",
|
|
75
|
-
"@playform/compress": "^0.1.7",
|
|
76
|
-
"@playform/inline": "^0.1.1",
|
|
77
|
-
"@unocss/astro": "
|
|
78
|
-
"@unocss/preset-attributify": "
|
|
79
|
-
"@unocss/preset-typography": "
|
|
80
|
-
"@unocss/preset-uno": "
|
|
81
|
-
"@unocss/preset-web-fonts": "
|
|
82
|
-
"@unocss/preset-wind": "
|
|
83
|
-
"@unocss/reset": "
|
|
84
|
-
"@vite-pwa/astro": "^0.5.0",
|
|
85
|
-
"@vueuse/core": "^12.7.0",
|
|
86
|
-
"astro-i18next": "1.0.0-beta.21",
|
|
87
|
-
"astro-icon": "^1.1.5",
|
|
88
|
-
"astro-meta-tags": "^0.3.1",
|
|
89
|
-
"astro-navbar": "^2.3.9",
|
|
90
|
-
"astro-pagefind": "^1.8.1",
|
|
91
|
-
"astro-remote": "^0.3.3",
|
|
92
|
-
"dotenv": "^16.4.7",
|
|
93
|
-
"i18next": "^24.2.2",
|
|
94
|
-
"i18next-browser-languagedetector": "^8.0.4",
|
|
95
|
-
"i18next-fs-backend": "^2.6.0",
|
|
96
|
-
"i18next-http-backend": "^3.0.2",
|
|
97
|
-
"i18next-vue": "^5.2.0",
|
|
98
|
-
"swiper": "^11.2.4",
|
|
99
|
-
"unocss": "
|
|
100
|
-
"vue": "^3.5.13"
|
|
101
|
-
},
|
|
102
|
-
"devDependencies": {
|
|
103
|
-
"@types/gtag.js": "^0.0.20",
|
|
104
|
-
"@types/node": "^22.13.5",
|
|
105
|
-
"@unocss/transformer-variant-group": "
|
|
106
|
-
"@vitejs/plugin-vue": "^5.2.1",
|
|
107
|
-
"@vue/compiler-sfc": "^3.5.13",
|
|
108
|
-
"astro": "^5.3.1",
|
|
109
|
-
"unocss": "^0.65.0",
|
|
110
|
-
"vite": "^6.2.0"
|
|
111
|
-
},
|
|
112
|
-
"packageManager": "pnpm@9.15.3",
|
|
113
|
-
"pnpm": {
|
|
114
|
-
"default": "9.15.3",
|
|
115
|
-
"overrides": {
|
|
116
|
-
"file-type@>=17.0.0 <17.1.3": ">=17.1.3",
|
|
117
|
-
"sharp@<0.30.5": ">=0.30.5"
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
"engines": {
|
|
121
|
-
"node": ">=18.14.1",
|
|
122
|
-
"pnpm": ">=9.15.3"
|
|
123
|
-
}
|
|
124
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "spoko-design-system",
|
|
3
|
+
"version": "0.5.9",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "./index.ts",
|
|
6
|
+
"module": "./index.ts",
|
|
7
|
+
"types": "./index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.ts",
|
|
11
|
+
"require": "./index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./styles/*": "./src/styles/*",
|
|
14
|
+
"./icons": "./icon.config.ts",
|
|
15
|
+
"./icon-collections": "./icon.collections.ts",
|
|
16
|
+
"./uno-config": "./uno-config/index.ts"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "astro dev",
|
|
20
|
+
"start": "astro dev",
|
|
21
|
+
"build": "astro build",
|
|
22
|
+
"preview": "astro preview"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/polo-blue/sds"
|
|
27
|
+
},
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "spokospace",
|
|
30
|
+
"email": "szymon@spoko.space",
|
|
31
|
+
"url": "https://spoko.space"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://sds.spoko.space/",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"keywords": [
|
|
36
|
+
"astro-starter",
|
|
37
|
+
"seo",
|
|
38
|
+
"astro",
|
|
39
|
+
"sds design system",
|
|
40
|
+
"spoko design system"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@algolia/client-search": "^5.20.3",
|
|
44
|
+
"@astrojs/mdx": "^4.0.8",
|
|
45
|
+
"@astrojs/node": "^9.1.1",
|
|
46
|
+
"@astrojs/sitemap": "^3.2.1",
|
|
47
|
+
"@astrojs/ts-plugin": "^1.10.4",
|
|
48
|
+
"@astrojs/vue": "^5.0.7",
|
|
49
|
+
"@docsearch/css": "^3.9.0",
|
|
50
|
+
"@iconify-json/ant-design": "^1.2.5",
|
|
51
|
+
"@iconify-json/bi": "^1.2.2",
|
|
52
|
+
"@iconify-json/bx": "^1.2.2",
|
|
53
|
+
"@iconify-json/carbon": "^1.2.7",
|
|
54
|
+
"@iconify-json/circle-flags": "^1.2.6",
|
|
55
|
+
"@iconify-json/ei": "^1.2.2",
|
|
56
|
+
"@iconify-json/el": "^1.2.1",
|
|
57
|
+
"@iconify-json/eos-icons": "^1.2.2",
|
|
58
|
+
"@iconify-json/et": "^1.2.1",
|
|
59
|
+
"@iconify-json/flowbite": "^1.2.4",
|
|
60
|
+
"@iconify-json/fluent": "^1.2.14",
|
|
61
|
+
"@iconify-json/fluent-emoji": "1.2.3",
|
|
62
|
+
"@iconify-json/ic": "^1.2.2",
|
|
63
|
+
"@iconify-json/icon-park-outline": "^1.2.2",
|
|
64
|
+
"@iconify-json/la": "^1.2.1",
|
|
65
|
+
"@iconify-json/material-symbols-light": "^1.2.14",
|
|
66
|
+
"@iconify-json/mdi": "^1.2.3",
|
|
67
|
+
"@iconify-json/noto-v1": "^1.2.1",
|
|
68
|
+
"@iconify-json/octicon": "^1.2.5",
|
|
69
|
+
"@iconify-json/ph": "^1.2.2",
|
|
70
|
+
"@iconify-json/simple-icons": "^1.2.26",
|
|
71
|
+
"@iconify-json/system-uicons": "^1.2.2",
|
|
72
|
+
"@iconify-json/uil": "^1.2.3",
|
|
73
|
+
"@iconify/json": "^2.2.310",
|
|
74
|
+
"@iconify/vue": "^4.3.0",
|
|
75
|
+
"@playform/compress": "^0.1.7",
|
|
76
|
+
"@playform/inline": "^0.1.1",
|
|
77
|
+
"@unocss/astro": "66.1.0-beta.2",
|
|
78
|
+
"@unocss/preset-attributify": "66.1.0-beta.2",
|
|
79
|
+
"@unocss/preset-typography": "66.1.0-beta.2",
|
|
80
|
+
"@unocss/preset-uno": "66.1.0-beta.2",
|
|
81
|
+
"@unocss/preset-web-fonts": "66.1.0-beta.2",
|
|
82
|
+
"@unocss/preset-wind": "66.1.0-beta.2",
|
|
83
|
+
"@unocss/reset": "66.1.0-beta.2",
|
|
84
|
+
"@vite-pwa/astro": "^0.5.0",
|
|
85
|
+
"@vueuse/core": "^12.7.0",
|
|
86
|
+
"astro-i18next": "1.0.0-beta.21",
|
|
87
|
+
"astro-icon": "^1.1.5",
|
|
88
|
+
"astro-meta-tags": "^0.3.1",
|
|
89
|
+
"astro-navbar": "^2.3.9",
|
|
90
|
+
"astro-pagefind": "^1.8.1",
|
|
91
|
+
"astro-remote": "^0.3.3",
|
|
92
|
+
"dotenv": "^16.4.7",
|
|
93
|
+
"i18next": "^24.2.2",
|
|
94
|
+
"i18next-browser-languagedetector": "^8.0.4",
|
|
95
|
+
"i18next-fs-backend": "^2.6.0",
|
|
96
|
+
"i18next-http-backend": "^3.0.2",
|
|
97
|
+
"i18next-vue": "^5.2.0",
|
|
98
|
+
"swiper": "^11.2.4",
|
|
99
|
+
"unocss": "66.1.0-beta.2",
|
|
100
|
+
"vue": "^3.5.13"
|
|
101
|
+
},
|
|
102
|
+
"devDependencies": {
|
|
103
|
+
"@types/gtag.js": "^0.0.20",
|
|
104
|
+
"@types/node": "^22.13.5",
|
|
105
|
+
"@unocss/transformer-variant-group": "66.1.0-beta.2",
|
|
106
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
107
|
+
"@vue/compiler-sfc": "^3.5.13",
|
|
108
|
+
"astro": "^5.3.1",
|
|
109
|
+
"unocss": "^0.65.0",
|
|
110
|
+
"vite": "^6.2.0"
|
|
111
|
+
},
|
|
112
|
+
"packageManager": "pnpm@9.15.3",
|
|
113
|
+
"pnpm": {
|
|
114
|
+
"default": "9.15.3",
|
|
115
|
+
"overrides": {
|
|
116
|
+
"file-type@>=17.0.0 <17.1.3": ">=17.1.3",
|
|
117
|
+
"sharp@<0.30.5": ">=0.30.5"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"engines": {
|
|
121
|
+
"node": ">=18.14.1",
|
|
122
|
+
"pnpm": ">=9.15.3"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useAttrs } from 'vue';
|
|
3
|
+
|
|
4
|
+
interface InputProps {
|
|
5
|
+
id?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
label: string;
|
|
8
|
+
variant?: 'filled' | 'standard';
|
|
9
|
+
type?: string;
|
|
10
|
+
modelValue?: string | number;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
rows?: number;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
error?: string | boolean;
|
|
15
|
+
success?: string | boolean;
|
|
16
|
+
size?: 'sm' | 'md' | 'lg';
|
|
17
|
+
class?: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<InputProps>(), {
|
|
22
|
+
id: () => `input-${Math.random().toString(36).substring(2, 9)}`,
|
|
23
|
+
name: undefined,
|
|
24
|
+
variant: 'standard',
|
|
25
|
+
type: 'text',
|
|
26
|
+
modelValue: '',
|
|
27
|
+
required: false,
|
|
28
|
+
rows: 3,
|
|
29
|
+
placeholder: ' ', // space for "floating label"
|
|
30
|
+
error: false,
|
|
31
|
+
success: false,
|
|
32
|
+
size: 'md',
|
|
33
|
+
class: ''
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const emit = defineEmits(['update:modelValue', 'input', 'focus', 'blur']);
|
|
37
|
+
|
|
38
|
+
// Handle external attrs
|
|
39
|
+
const attrs = useAttrs();
|
|
40
|
+
|
|
41
|
+
// Compute wrapper class - uses existing shortcut
|
|
42
|
+
const wrapperClass = computed(() => `input-wrapper-${props.variant}`);
|
|
43
|
+
|
|
44
|
+
// Compute input classes - uses shortcuts
|
|
45
|
+
const inputClass = computed(() => {
|
|
46
|
+
const classes = ['input-base', 'input-placeholder', `input-${props.variant}`];
|
|
47
|
+
|
|
48
|
+
// Add size class
|
|
49
|
+
if (props.size) classes.push(`input-${props.size}`);
|
|
50
|
+
|
|
51
|
+
// Add textarea class if needed
|
|
52
|
+
if (props.type === 'textarea') classes.push('input-textarea');
|
|
53
|
+
|
|
54
|
+
// Add status classes
|
|
55
|
+
if (props.error) classes.push('input-error');
|
|
56
|
+
else if (props.success) classes.push('input-success');
|
|
57
|
+
|
|
58
|
+
// Add custom classes
|
|
59
|
+
if (props.class) classes.push(props.class);
|
|
60
|
+
|
|
61
|
+
return classes.join(' ');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Compute label classes - using optimized shortcuts
|
|
65
|
+
const labelClass = computed(() => {
|
|
66
|
+
const classes = [
|
|
67
|
+
// Base label style
|
|
68
|
+
'input-label-base',
|
|
69
|
+
|
|
70
|
+
// Position styling
|
|
71
|
+
`input-label-${props.variant}`,
|
|
72
|
+
|
|
73
|
+
// State styling - contains all transformations for the specific variant
|
|
74
|
+
`input-label-${props.variant}-state`
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Add size class
|
|
78
|
+
if (props.size) classes.push(`input-label-${props.size}`);
|
|
79
|
+
|
|
80
|
+
// Add status classes
|
|
81
|
+
if (props.error) classes.push('input-label-error');
|
|
82
|
+
else if (props.success) classes.push('input-label-success');
|
|
83
|
+
|
|
84
|
+
return classes.join(' ');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Event handlers
|
|
88
|
+
const handleInput = (event: Event) => {
|
|
89
|
+
const target = event.target as HTMLInputElement | HTMLTextAreaElement;
|
|
90
|
+
emit('update:modelValue', target.value);
|
|
91
|
+
emit('input', event);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleFocus = (event: FocusEvent) => emit('focus', event);
|
|
95
|
+
const handleBlur = (event: FocusEvent) => emit('blur', event);
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<template>
|
|
99
|
+
<div :class="wrapperClass">
|
|
100
|
+
<textarea
|
|
101
|
+
v-if="type === 'textarea'"
|
|
102
|
+
:id="id"
|
|
103
|
+
:name="name || id"
|
|
104
|
+
:rows="rows"
|
|
105
|
+
:required="required"
|
|
106
|
+
:class="inputClass + ' peer'"
|
|
107
|
+
:placeholder="placeholder"
|
|
108
|
+
:value="modelValue"
|
|
109
|
+
@input="handleInput"
|
|
110
|
+
@focus="handleFocus"
|
|
111
|
+
@blur="handleBlur"
|
|
112
|
+
v-bind="attrs"
|
|
113
|
+
></textarea>
|
|
114
|
+
|
|
115
|
+
<input
|
|
116
|
+
v-else
|
|
117
|
+
:type="type"
|
|
118
|
+
:id="id"
|
|
119
|
+
:name="name || id"
|
|
120
|
+
:required="required"
|
|
121
|
+
:class="inputClass + ' peer'"
|
|
122
|
+
:placeholder="placeholder"
|
|
123
|
+
:value="modelValue"
|
|
124
|
+
@input="handleInput"
|
|
125
|
+
@focus="handleFocus"
|
|
126
|
+
@blur="handleBlur"
|
|
127
|
+
v-bind="attrs"
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
<label
|
|
131
|
+
:for="id"
|
|
132
|
+
:class="labelClass"
|
|
133
|
+
style="transform-origin: top left;"
|
|
134
|
+
>
|
|
135
|
+
{{ label }}
|
|
136
|
+
<span v-if="required" class="text-red-500 ml-1">*</span>
|
|
137
|
+
</label>
|
|
138
|
+
|
|
139
|
+
<div
|
|
140
|
+
v-if="error && typeof error === 'string'"
|
|
141
|
+
class="input-error-message"
|
|
142
|
+
>
|
|
143
|
+
{{ error }}
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div
|
|
147
|
+
v-if="success && typeof success === 'string'"
|
|
148
|
+
class="input-success-message"
|
|
149
|
+
>
|
|
150
|
+
{{ success }}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</template>
|
|
@@ -2,18 +2,7 @@
|
|
|
2
2
|
title: Input
|
|
3
3
|
layout: ../../layouts/MainLayout.astro
|
|
4
4
|
---
|
|
5
|
-
import
|
|
6
|
-
import Input from '../../components/Input.astro';
|
|
7
|
-
|
|
8
|
-
# Basic Input text
|
|
9
|
-
|
|
10
|
-
<div class="component-preview">
|
|
11
|
-
<MainInput label="Name"></MainInput>
|
|
12
|
-
</div>
|
|
13
|
-
|
|
14
|
-
```js
|
|
15
|
-
<MainInput type="text" value="Hello world!"></MainInput>
|
|
16
|
-
```
|
|
5
|
+
import Input from '../../components/Input.vue';
|
|
17
6
|
|
|
18
7
|
# Floating Label Input
|
|
19
8
|
<div class="component-preview">
|
|
@@ -55,6 +44,7 @@ import Input from '../../components/Input.astro';
|
|
|
55
44
|
id="name-filled"
|
|
56
45
|
label="Floating filled"
|
|
57
46
|
variant="filled"
|
|
47
|
+
|
|
58
48
|
type="textarea"
|
|
59
49
|
/>
|
|
60
50
|
<Input
|
|
@@ -82,4 +72,297 @@ import Input from '../../components/Input.astro';
|
|
|
82
72
|
variant="standard"
|
|
83
73
|
type="textarea"
|
|
84
74
|
/>
|
|
85
|
-
```
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
# Input with Error State
|
|
78
|
+
The Input component supports error states to provide validation feedback to users. You can pass a string as the error prop to display a specific error message.
|
|
79
|
+
|
|
80
|
+
<div class="component-preview">
|
|
81
|
+
<div class="bg-white grid items-end w-full gap-6 md:grid-cols-2 px-4 py-6">
|
|
82
|
+
<Input
|
|
83
|
+
id="error-filled"
|
|
84
|
+
label="With error"
|
|
85
|
+
variant="filled"
|
|
86
|
+
error="This field is required"
|
|
87
|
+
/>
|
|
88
|
+
<Input
|
|
89
|
+
id="error-standard"
|
|
90
|
+
label="With error"
|
|
91
|
+
variant="standard"
|
|
92
|
+
error="Please enter a valid email"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
// Error with filled variant
|
|
99
|
+
<Input
|
|
100
|
+
id="error-filled"
|
|
101
|
+
label="With error"
|
|
102
|
+
variant="filled"
|
|
103
|
+
error="This field is required"
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
// Error with standard variant
|
|
107
|
+
<Input
|
|
108
|
+
id="error-standard"
|
|
109
|
+
label="With error"
|
|
110
|
+
variant="standard"
|
|
111
|
+
error="Please enter a valid email"
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
You can also pass a boolean `true` to indicate an error state without a message:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
<Input
|
|
119
|
+
label="Username"
|
|
120
|
+
error={true}
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
# Input with Success State
|
|
125
|
+
Similar to errors, you can indicate a successful validation state using the success prop:
|
|
126
|
+
|
|
127
|
+
<div class="component-preview">
|
|
128
|
+
<div class="bg-white grid items-end w-full gap-6 md:grid-cols-2 px-4 py-6">
|
|
129
|
+
<Input
|
|
130
|
+
id="success-filled"
|
|
131
|
+
label="With success"
|
|
132
|
+
variant="filled"
|
|
133
|
+
success="Username is available"
|
|
134
|
+
/>
|
|
135
|
+
<Input
|
|
136
|
+
id="success-standard"
|
|
137
|
+
label="With success"
|
|
138
|
+
variant="standard"
|
|
139
|
+
success={true}
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
// Success with message
|
|
146
|
+
<Input
|
|
147
|
+
id="success-filled"
|
|
148
|
+
label="With success"
|
|
149
|
+
variant="filled"
|
|
150
|
+
success="Username is available"
|
|
151
|
+
/>
|
|
152
|
+
|
|
153
|
+
// Success without message
|
|
154
|
+
<Input
|
|
155
|
+
id="success-standard"
|
|
156
|
+
label="With success"
|
|
157
|
+
variant="standard"
|
|
158
|
+
success={true}
|
|
159
|
+
/>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
# Input Sizes
|
|
163
|
+
The Input component supports different sizes: `sm`, `md` (default), and `lg`.
|
|
164
|
+
|
|
165
|
+
<div class="component-preview">
|
|
166
|
+
<div class="bg-white grid items-end w-full gap-6 px-4 py-6">
|
|
167
|
+
<Input
|
|
168
|
+
id="small-input"
|
|
169
|
+
label="Small input"
|
|
170
|
+
variant="filled"
|
|
171
|
+
size="sm"
|
|
172
|
+
/>
|
|
173
|
+
<Input
|
|
174
|
+
id="medium-input"
|
|
175
|
+
label="Medium input (default)"
|
|
176
|
+
variant="filled"
|
|
177
|
+
size="md"
|
|
178
|
+
/>
|
|
179
|
+
<Input
|
|
180
|
+
id="large-input"
|
|
181
|
+
label="Large input"
|
|
182
|
+
variant="filled"
|
|
183
|
+
size="lg"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
// Small input
|
|
190
|
+
<Input
|
|
191
|
+
id="small-input"
|
|
192
|
+
label="Small input"
|
|
193
|
+
variant="filled"
|
|
194
|
+
size="sm"
|
|
195
|
+
/>
|
|
196
|
+
|
|
197
|
+
// Medium input (default)
|
|
198
|
+
<Input
|
|
199
|
+
id="medium-input"
|
|
200
|
+
label="Medium input (default)"
|
|
201
|
+
variant="filled"
|
|
202
|
+
size="md"
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
// Large input
|
|
206
|
+
<Input
|
|
207
|
+
id="large-input"
|
|
208
|
+
label="Large input"
|
|
209
|
+
variant="filled"
|
|
210
|
+
size="lg"
|
|
211
|
+
/>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
# Required Input
|
|
215
|
+
You can mark an input as required, which will add a red asterisk to the label:
|
|
216
|
+
|
|
217
|
+
<div class="component-preview">
|
|
218
|
+
<div class="bg-white grid items-end w-full gap-6 md:grid-cols-2 px-4 py-6">
|
|
219
|
+
<Input
|
|
220
|
+
id="required-filled"
|
|
221
|
+
label="Required field"
|
|
222
|
+
variant="filled"
|
|
223
|
+
required
|
|
224
|
+
/>
|
|
225
|
+
<Input
|
|
226
|
+
id="required-standard"
|
|
227
|
+
label="Required field"
|
|
228
|
+
variant="standard"
|
|
229
|
+
required
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
<Input
|
|
236
|
+
id="required-filled"
|
|
237
|
+
label="Required field"
|
|
238
|
+
variant="filled"
|
|
239
|
+
required
|
|
240
|
+
/>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
# Input Types
|
|
244
|
+
The Input component supports all standard HTML input types, plus a special `textarea` type:
|
|
245
|
+
|
|
246
|
+
<div class="component-preview">
|
|
247
|
+
<div class="bg-white grid items-end w-full gap-6 md:grid-cols-2 px-4 py-6">
|
|
248
|
+
<Input
|
|
249
|
+
id="email-input"
|
|
250
|
+
label="Email"
|
|
251
|
+
type="email"
|
|
252
|
+
variant="filled"
|
|
253
|
+
/>
|
|
254
|
+
<Input
|
|
255
|
+
id="password-input"
|
|
256
|
+
label="Password"
|
|
257
|
+
type="password"
|
|
258
|
+
variant="standard"
|
|
259
|
+
/>
|
|
260
|
+
<Input
|
|
261
|
+
id="number-input"
|
|
262
|
+
label="Age"
|
|
263
|
+
type="number"
|
|
264
|
+
variant="filled"
|
|
265
|
+
/>
|
|
266
|
+
<Input
|
|
267
|
+
id="date-input"
|
|
268
|
+
label="Date"
|
|
269
|
+
type="date"
|
|
270
|
+
variant="standard"
|
|
271
|
+
/>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
// Email input
|
|
277
|
+
<Input
|
|
278
|
+
id="email-input"
|
|
279
|
+
label="Email"
|
|
280
|
+
type="email"
|
|
281
|
+
variant="filled"
|
|
282
|
+
/>
|
|
283
|
+
|
|
284
|
+
// Password input
|
|
285
|
+
<Input
|
|
286
|
+
id="password-input"
|
|
287
|
+
label="Password"
|
|
288
|
+
type="password"
|
|
289
|
+
variant="standard"
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
// Number input
|
|
293
|
+
<Input
|
|
294
|
+
id="number-input"
|
|
295
|
+
label="Age"
|
|
296
|
+
type="number"
|
|
297
|
+
variant="filled"
|
|
298
|
+
/>
|
|
299
|
+
|
|
300
|
+
// Date input
|
|
301
|
+
<Input
|
|
302
|
+
id="date-input"
|
|
303
|
+
label="Date"
|
|
304
|
+
type="date"
|
|
305
|
+
variant="standard"
|
|
306
|
+
/>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
# Using with v-model
|
|
310
|
+
The Input component fully supports Vue's v-model for two-way binding:
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
<script setup>
|
|
314
|
+
import { ref } from 'vue';
|
|
315
|
+
import { Input } from 'spoko-design-system';
|
|
316
|
+
|
|
317
|
+
const username = ref('');
|
|
318
|
+
const email = ref('');
|
|
319
|
+
</script>
|
|
320
|
+
|
|
321
|
+
<template>
|
|
322
|
+
<Input
|
|
323
|
+
v-model="username"
|
|
324
|
+
label="Username"
|
|
325
|
+
variant="filled"
|
|
326
|
+
/>
|
|
327
|
+
|
|
328
|
+
<Input
|
|
329
|
+
v-model="email"
|
|
330
|
+
label="Email"
|
|
331
|
+
variant="standard"
|
|
332
|
+
type="email"
|
|
333
|
+
/>
|
|
334
|
+
|
|
335
|
+
<div>
|
|
336
|
+
Current values:
|
|
337
|
+
<p>Username: {{ username }}</p>
|
|
338
|
+
<p>Email: {{ email }}</p>
|
|
339
|
+
</div>
|
|
340
|
+
</template>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
# Props Reference
|
|
344
|
+
|
|
345
|
+
| Prop | Type | Default | Description |
|
|
346
|
+
|------|------|---------|-------------|
|
|
347
|
+
| `id` | `string` | Random ID | Unique identifier for the input |
|
|
348
|
+
| `name` | `string` | Same as id | Name attribute for the input field |
|
|
349
|
+
| `label` | `string` | Required | Label text for the input |
|
|
350
|
+
| `variant` | `'standard' \| 'filled'` | `'standard'` | Visual style variant |
|
|
351
|
+
| `type` | `string` | `'text'` | Input type (all HTML types + 'textarea') |
|
|
352
|
+
| `modelValue` | `string \| number` | `''` | Value for v-model binding |
|
|
353
|
+
| `required` | `boolean` | `false` | Whether the field is required |
|
|
354
|
+
| `rows` | `number` | `3` | Number of rows (for textarea only) |
|
|
355
|
+
| `placeholder` | `string` | `' '` | Placeholder text (space for floating label) |
|
|
356
|
+
| `error` | `string \| boolean` | `false` | Error state or message |
|
|
357
|
+
| `success` | `string \| boolean` | `false` | Success state or message |
|
|
358
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the input |
|
|
359
|
+
| `class` | `string` | `''` | Additional CSS classes |
|
|
360
|
+
|
|
361
|
+
# Events
|
|
362
|
+
|
|
363
|
+
| Event | Parameters | Description |
|
|
364
|
+
|-------|------------|-------------|
|
|
365
|
+
| `update:modelValue` | `(value: string \| number)` | Emitted when input value changes (for v-model) |
|
|
366
|
+
| `input` | `(event: Event)` | Native input event |
|
|
367
|
+
| `focus` | `(event: FocusEvent)` | Native focus event |
|
|
368
|
+
| `blur` | `(event: FocusEvent)` | Native blur event |
|
package/uno-config/index.ts
CHANGED
|
@@ -14,6 +14,26 @@ import {
|
|
|
14
14
|
import { shortcuts } from './theme/shortcuts';
|
|
15
15
|
import { theme } from './theme';
|
|
16
16
|
|
|
17
|
+
// List of peer selectors we want to preserve during build
|
|
18
|
+
const peerSelectorClasses = [
|
|
19
|
+
// Focus state classes
|
|
20
|
+
'peer-focus:text-blue-light',
|
|
21
|
+
'peer-focus:dark:text-blue-lightest',
|
|
22
|
+
'peer-focus:scale-75',
|
|
23
|
+
'peer-focus:-translate-y-6',
|
|
24
|
+
'peer-focus:-translate-y-4',
|
|
25
|
+
'peer-focus:start-0',
|
|
26
|
+
|
|
27
|
+
// Placeholder shown classes
|
|
28
|
+
'peer-placeholder-shown:scale-100',
|
|
29
|
+
'peer-placeholder-shown:translate-y-0',
|
|
30
|
+
|
|
31
|
+
// Not placeholder shown classes
|
|
32
|
+
'peer-not-placeholder-shown:scale-75',
|
|
33
|
+
'peer-not-placeholder-shown:-translate-y-6',
|
|
34
|
+
'peer-not-placeholder-shown:-translate-y-4',
|
|
35
|
+
];
|
|
36
|
+
|
|
17
37
|
interface CustomConfig extends Partial<UserConfig> {
|
|
18
38
|
shortcuts?: UserShortcuts;
|
|
19
39
|
theme?: Partial<typeof theme>;
|
|
@@ -33,8 +53,159 @@ export function createSdsConfig(customConfig: CustomConfig = {}) {
|
|
|
33
53
|
...theme,
|
|
34
54
|
...(customConfig.theme || {})
|
|
35
55
|
},
|
|
56
|
+
// Enhanced variants to better handle peer selectors
|
|
57
|
+
variants: [
|
|
58
|
+
// Add specific peer variant support
|
|
59
|
+
(matcher) => {
|
|
60
|
+
if (!matcher.startsWith('peer-'))
|
|
61
|
+
return matcher;
|
|
62
|
+
|
|
63
|
+
const peerVariant = matcher.slice(5);
|
|
64
|
+
const selectorMap = {
|
|
65
|
+
'focus:': (s) => `.peer:focus ~ ${s}`,
|
|
66
|
+
'hover:': (s) => `.peer:hover ~ ${s}`,
|
|
67
|
+
'placeholder-shown:': (s) => `.peer:placeholder-shown ~ ${s}`,
|
|
68
|
+
'not-placeholder-shown:': (s) => `.peer:not(:placeholder-shown) ~ ${s}`,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Check for nested variants like 'peer-focus:text-blue'
|
|
72
|
+
for (const [key, selectorFn] of Object.entries(selectorMap)) {
|
|
73
|
+
if (peerVariant.startsWith(key)) {
|
|
74
|
+
return {
|
|
75
|
+
matcher: peerVariant.slice(key.length),
|
|
76
|
+
selector: selectorFn,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Default peer handling
|
|
82
|
+
return {
|
|
83
|
+
matcher: peerVariant,
|
|
84
|
+
selector: (s) => `.peer:${peerVariant} ~ ${s}`,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
// Comprehensive safelist with all needed classes
|
|
36
89
|
safelist: [
|
|
37
|
-
|
|
90
|
+
// Existing safelist items
|
|
91
|
+
'md:grid-cols-product',
|
|
92
|
+
|
|
93
|
+
// Base peer class
|
|
94
|
+
'peer',
|
|
95
|
+
|
|
96
|
+
// All input component classes from shortcuts
|
|
97
|
+
'input-base',
|
|
98
|
+
'input-label-base',
|
|
99
|
+
'input-placeholder',
|
|
100
|
+
'input-standard',
|
|
101
|
+
'input-filled',
|
|
102
|
+
'input-wrapper-standard',
|
|
103
|
+
'input-wrapper-filled',
|
|
104
|
+
'input-label-standard',
|
|
105
|
+
'input-label-filled',
|
|
106
|
+
|
|
107
|
+
// Label state shortcuts
|
|
108
|
+
'input-label-focus-color',
|
|
109
|
+
'input-label-focus-scale',
|
|
110
|
+
'input-label-focus-translate-standard',
|
|
111
|
+
'input-label-focus-translate-filled',
|
|
112
|
+
'input-label-placeholder',
|
|
113
|
+
'input-label-filled-standard',
|
|
114
|
+
'input-label-filled-filled',
|
|
115
|
+
'input-label-standard-state',
|
|
116
|
+
'input-label-filled-state',
|
|
117
|
+
|
|
118
|
+
// Input types
|
|
119
|
+
'input-textarea',
|
|
120
|
+
'resize-none',
|
|
121
|
+
|
|
122
|
+
// Size variants
|
|
123
|
+
'input-sm',
|
|
124
|
+
'input-md',
|
|
125
|
+
'input-lg',
|
|
126
|
+
'input-label-sm',
|
|
127
|
+
'input-label-md',
|
|
128
|
+
'input-label-lg',
|
|
129
|
+
|
|
130
|
+
// Status classes
|
|
131
|
+
'input-error',
|
|
132
|
+
'input-label-error',
|
|
133
|
+
'input-error-message',
|
|
134
|
+
'input-success',
|
|
135
|
+
'input-label-success',
|
|
136
|
+
'input-success-message',
|
|
137
|
+
|
|
138
|
+
// Transform related classes
|
|
139
|
+
'origin-top-left',
|
|
140
|
+
'transform-gpu',
|
|
141
|
+
'translate-y-0',
|
|
142
|
+
'-translate-y-4',
|
|
143
|
+
'-translate-y-6',
|
|
144
|
+
'scale-75',
|
|
145
|
+
'scale-100',
|
|
146
|
+
|
|
147
|
+
// Every possible arbitrary selector used
|
|
148
|
+
'[&:focus~label]:scale-75',
|
|
149
|
+
'[&:focus~label]:-translate-y-4',
|
|
150
|
+
'[&:focus~label]:-translate-y-6',
|
|
151
|
+
'[&:focus~label]:text-blue-light',
|
|
152
|
+
'[&:focus~label]:dark:text-blue-lightest',
|
|
153
|
+
'[&:focus~label]:start-0',
|
|
154
|
+
'[&:placeholder-shown~label]:scale-100',
|
|
155
|
+
'[&:placeholder-shown~label]:translate-y-0',
|
|
156
|
+
'[&:not(:placeholder-shown)~label]:scale-75',
|
|
157
|
+
'[&:not(:placeholder-shown)~label]:-translate-y-4',
|
|
158
|
+
'[&:not(:placeholder-shown)~label]:-translate-y-6',
|
|
159
|
+
|
|
160
|
+
// Combinations of selectors
|
|
161
|
+
'peer:focus:text-blue-light',
|
|
162
|
+
'peer:focus:dark:text-blue-lightest',
|
|
163
|
+
'peer:focus:scale-75',
|
|
164
|
+
'peer:focus:-translate-y-4',
|
|
165
|
+
'peer:focus:-translate-y-6',
|
|
166
|
+
'peer:focus:start-0',
|
|
167
|
+
'peer-placeholder-shown:scale-100',
|
|
168
|
+
'peer-placeholder-shown:translate-y-0',
|
|
169
|
+
'peer-not-placeholder-shown:scale-75',
|
|
170
|
+
'peer-not-placeholder-shown:-translate-y-4',
|
|
171
|
+
'peer-not-placeholder-shown:-translate-y-6',
|
|
172
|
+
|
|
173
|
+
// With !important for good measure
|
|
174
|
+
'[&:focus~label]:!scale-75',
|
|
175
|
+
'[&:focus~label]:!-translate-y-4',
|
|
176
|
+
'[&:focus~label]:!-translate-y-6',
|
|
177
|
+
'[&:not(:placeholder-shown)~label]:!scale-75',
|
|
178
|
+
'[&:not(:placeholder-shown)~label]:!-translate-y-4',
|
|
179
|
+
'[&:not(:placeholder-shown)~label]:!-translate-y-6',
|
|
180
|
+
|
|
181
|
+
// Direct css vars that might be used
|
|
182
|
+
'--un-scale-x',
|
|
183
|
+
'--un-scale-y',
|
|
184
|
+
'--un-translate-y',
|
|
185
|
+
|
|
186
|
+
// All peer selectors from the list
|
|
187
|
+
...peerSelectorClasses,
|
|
188
|
+
],
|
|
189
|
+
// Custom extractors to ensure peer classes are preserved
|
|
190
|
+
extractors: [
|
|
191
|
+
{
|
|
192
|
+
name: 'vue-astro',
|
|
193
|
+
extract({ code }) {
|
|
194
|
+
const result = new Set();
|
|
195
|
+
|
|
196
|
+
// Extract all peer selectors in the code
|
|
197
|
+
const peerRegex = /peer-([a-zA-Z0-9-]+:[a-zA-Z0-9-]+)/g;
|
|
198
|
+
const peerMatches = code.match(peerRegex);
|
|
199
|
+
if (peerMatches) {
|
|
200
|
+
peerMatches.forEach(match => result.add(match));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Add all known peer selectors
|
|
204
|
+
peerSelectorClasses.forEach(cls => result.add(cls));
|
|
205
|
+
|
|
206
|
+
return result;
|
|
207
|
+
},
|
|
208
|
+
},
|
|
38
209
|
],
|
|
39
210
|
presets: [
|
|
40
211
|
presetUno(),
|
|
@@ -5,6 +5,7 @@ import { layoutShortcuts } from './layout';
|
|
|
5
5
|
import { componentShortcuts } from './components';
|
|
6
6
|
import { productShortcuts } from './product';
|
|
7
7
|
import { jumbotronShortcuts } from './jumbotron';
|
|
8
|
+
import { inputShortcuts } from './inputs';
|
|
8
9
|
|
|
9
10
|
const convertToShortcuts = (shortcuts: string[][]): UserShortcuts => {
|
|
10
11
|
return Object.fromEntries(shortcuts.map(([name, value]) => [name, value]));
|
|
@@ -16,4 +17,5 @@ export const shortcuts: UserShortcuts = {
|
|
|
16
17
|
...convertToShortcuts(componentShortcuts),
|
|
17
18
|
...convertToShortcuts(productShortcuts),
|
|
18
19
|
...convertToShortcuts(jumbotronShortcuts),
|
|
20
|
+
...convertToShortcuts(inputShortcuts),
|
|
19
21
|
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// uno-config\theme\shortcuts\inputs.ts
|
|
2
|
+
// Complete shortcuts for Input component with floating labels support
|
|
3
|
+
|
|
4
|
+
export const inputShortcuts = [
|
|
5
|
+
// Base input - core class for the input without peer selectors
|
|
6
|
+
['input-base', 'block w-full text-4.5 text-blue-medium border-0 border-b-1 border-neutral-light appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-lightest focus:outline-none focus:ring-0 focus:border-blue-medium'],
|
|
7
|
+
|
|
8
|
+
// Base label - basic styles for the label without transformations and states
|
|
9
|
+
['input-label-base', 'absolute text-sm text-slate-light dark:text-neutral-default origin-top-left transform-gpu transition-all duration-300 ease-in-out'],
|
|
10
|
+
|
|
11
|
+
// Base placeholders
|
|
12
|
+
['input-placeholder', 'placeholder:text-slate-light dark:placeholder:text-neutral-default placeholder:opacity-60'],
|
|
13
|
+
|
|
14
|
+
// Variant-specific container classes
|
|
15
|
+
['input-wrapper-standard', 'relative z-0'],
|
|
16
|
+
['input-wrapper-filled', 'relative'],
|
|
17
|
+
|
|
18
|
+
// Input variants - styling without peer selectors
|
|
19
|
+
['input-standard', 'py-2.5 px-0 bg-transparent'],
|
|
20
|
+
['input-filled', 'rounded-t-lg px-2.5 pb-2.5 pt-5 bg-gray-50 dark:bg-gray-700'],
|
|
21
|
+
|
|
22
|
+
// LABELS - positioning without transforms
|
|
23
|
+
['input-label-standard', 'top-3 -z-10'],
|
|
24
|
+
['input-label-filled', 'top-4 z-10 start-2.5'],
|
|
25
|
+
|
|
26
|
+
// Focus state transformations - explicitly defined
|
|
27
|
+
['input-label-focus-color', 'peer-focus:text-blue-light peer-focus:dark:text-blue-lightest'],
|
|
28
|
+
['input-label-focus-scale', 'peer-focus:scale-75'],
|
|
29
|
+
['input-label-focus-translate-standard', 'peer-focus:-translate-y-6'],
|
|
30
|
+
['input-label-focus-translate-filled', 'peer-focus:-translate-y-4'],
|
|
31
|
+
|
|
32
|
+
// Placeholder state transformations
|
|
33
|
+
['input-label-placeholder', 'peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0'],
|
|
34
|
+
|
|
35
|
+
// Not-placeholder-shown state transformations
|
|
36
|
+
['input-label-filled-standard', 'peer-not-placeholder-shown:scale-75 peer-not-placeholder-shown:-translate-y-6'],
|
|
37
|
+
['input-label-filled-filled', 'peer-not-placeholder-shown:scale-75 peer-not-placeholder-shown:-translate-y-4'],
|
|
38
|
+
|
|
39
|
+
// Standard input states - complete shortcuts for specific states
|
|
40
|
+
['input-label-standard-state', 'input-label-focus-color input-label-focus-scale input-label-focus-translate-standard input-label-placeholder input-label-filled-standard'],
|
|
41
|
+
['input-label-filled-state', 'input-label-focus-color input-label-focus-scale input-label-focus-translate-filled input-label-placeholder input-label-filled-filled'],
|
|
42
|
+
|
|
43
|
+
// Input types
|
|
44
|
+
['input-textarea', 'resize-none'],
|
|
45
|
+
|
|
46
|
+
// Input sizes
|
|
47
|
+
['input-sm', 'text-sm'],
|
|
48
|
+
['input-md', 'text-base'],
|
|
49
|
+
['input-lg', 'text-lg '],
|
|
50
|
+
|
|
51
|
+
// Label sizes
|
|
52
|
+
['input-label-sm', 'text-sm'],
|
|
53
|
+
['input-label-md', 'text-sm'],
|
|
54
|
+
['input-label-lg', 'text-sm'],
|
|
55
|
+
|
|
56
|
+
// Status classes
|
|
57
|
+
['input-error', 'border-red-500 focus:border-red-500 dark:border-red-400 dark:focus:border-red-400'],
|
|
58
|
+
['input-label-error', 'text-red-500 dark:text-red-400'],
|
|
59
|
+
['input-error-message', 'mt-1 text-xs text-red-500 dark:text-red-400'],
|
|
60
|
+
|
|
61
|
+
['input-success', 'border-green-500 focus:border-green-500 dark:border-green-400 dark:focus:border-green-400'],
|
|
62
|
+
['input-label-success', 'text-green-500 dark:text-green-400'],
|
|
63
|
+
['input-success-message', 'mt-1 text-xs text-green-500 dark:text-green-400'],
|
|
64
|
+
];
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
// Input.astro
|
|
3
|
-
interface Props {
|
|
4
|
-
id: string;
|
|
5
|
-
name?: string;
|
|
6
|
-
label: string;
|
|
7
|
-
variant?: 'filled' | 'standard';
|
|
8
|
-
type?: HTMLInputElement['type'] | 'textarea'; // support textarea
|
|
9
|
-
value?: string;
|
|
10
|
-
required?: boolean;
|
|
11
|
-
rows?: number; // rows for textarea
|
|
12
|
-
placeholder?: string;
|
|
13
|
-
class?: string; // additional classes
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
id,
|
|
18
|
-
name,
|
|
19
|
-
label,
|
|
20
|
-
variant = 'standard',
|
|
21
|
-
type = 'text',
|
|
22
|
-
value = '',
|
|
23
|
-
required = false,
|
|
24
|
-
rows = 3,
|
|
25
|
-
placeholder = " ", //space for "floating label")
|
|
26
|
-
class: additionalClasses = "",
|
|
27
|
-
...restProps
|
|
28
|
-
} = Astro.props;
|
|
29
|
-
|
|
30
|
-
// Common classes for both variants
|
|
31
|
-
const baseInputClasses = "block w-full text-4.5 text-blue-medium border-0 border-b-1 border-neutral-light appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-lightest focus:outline-none focus:ring-0 focus:border-blue-medium peer";
|
|
32
|
-
|
|
33
|
-
const baseLabelClasses = "absolute text-sm text-slate-medium dark:text-neutral-default transform scale-75 origin-[0] peer-focus:text-blue-medium peer-focus:dark:text-blue-lightest peer-placeholder-shown:scale-100 peer-focus:scale-75 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto transition-all duration-300 ease-in-out";
|
|
34
|
-
|
|
35
|
-
// Variant specific classes
|
|
36
|
-
const variantClasses = {
|
|
37
|
-
filled: {
|
|
38
|
-
wrapper: "relative",
|
|
39
|
-
input: `${baseInputClasses} rounded-t-lg px-2.5 pb-2.5 pt-5 bg-gray-50 dark:bg-gray-700 ${additionalClasses}`,
|
|
40
|
-
label: `${baseLabelClasses} -translate-y-4 top-4 z-10 start-2.5 peer-placeholder-shown:translate-y-0 peer-focus:-translate-y-4`
|
|
41
|
-
},
|
|
42
|
-
standard: {
|
|
43
|
-
wrapper: "relative z-0",
|
|
44
|
-
input: `${baseInputClasses} py-2.5 px-0 bg-transparent ${additionalClasses}`,
|
|
45
|
-
label: `${baseLabelClasses} -translate-y-6 top-3 -z-10 peer-focus:start-0 peer-placeholder-shown:translate-y-0 peer-focus:-translate-y-6`
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const classes = variantClasses[variant];
|
|
50
|
-
|
|
51
|
-
// Keep border-b-1 for textarea but add resize-none
|
|
52
|
-
const textareaClasses = type === 'textarea'
|
|
53
|
-
? `${classes.input} resize-none`
|
|
54
|
-
: classes.input;
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
<div class={classes.wrapper}>
|
|
58
|
-
{type === 'textarea' ? (
|
|
59
|
-
<textarea
|
|
60
|
-
name={name}
|
|
61
|
-
id={id}
|
|
62
|
-
rows={rows}
|
|
63
|
-
required={required}
|
|
64
|
-
class={textareaClasses}
|
|
65
|
-
placeholder={placeholder}
|
|
66
|
-
{...restProps}
|
|
67
|
-
>{value}</textarea>
|
|
68
|
-
) : (
|
|
69
|
-
<input
|
|
70
|
-
type={type}
|
|
71
|
-
name={name}
|
|
72
|
-
id={id}
|
|
73
|
-
value={value}
|
|
74
|
-
required={required}
|
|
75
|
-
class={classes.input}
|
|
76
|
-
placeholder={placeholder}
|
|
77
|
-
{...restProps}
|
|
78
|
-
/>
|
|
79
|
-
)}
|
|
80
|
-
<label
|
|
81
|
-
for={id}
|
|
82
|
-
class={classes.label}
|
|
83
|
-
>
|
|
84
|
-
{label}
|
|
85
|
-
</label>
|
|
86
|
-
</div>
|