@stonecrop/aform 0.2.5 → 0.2.7
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 +33 -18
- package/src/components/form/ACheckbox.vue +3 -15
- package/src/components/form/ANumericInput.vue +3 -13
- package/src/components/utilities/Login.vue +72 -0
- package/src/directives/mask.ts +104 -0
- package/src/histoire.setup.ts +22 -0
- package/src/index.ts +27 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/theme/aform.css +28 -0
- package/src/theme/custom_themes.css +6 -0
- package/src/theme/login.css +132 -0
- package/src/theme/purple_theme.css +9 -0
- package/dist/aform.js +0 -885
- package/dist/aform.js.map +0 -1
- package/dist/aform.umd.cjs +0 -2
- package/dist/aform.umd.cjs.map +0 -1
- package/dist/style.css +0 -1
package/package.json
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/aform",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Tyler Matteson",
|
|
8
|
+
"email": "tyler@agritheory.com"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/agritheory/stonecrop",
|
|
13
|
+
"directory": "aform"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/agritheory/stonecrop/issues"
|
|
17
|
+
},
|
|
6
18
|
"exports": {
|
|
7
19
|
".": {
|
|
8
20
|
"import": "./dist/aform.js",
|
|
@@ -12,45 +24,48 @@
|
|
|
12
24
|
},
|
|
13
25
|
"main": "dist/aform.js",
|
|
14
26
|
"module": "dist/aform.js",
|
|
27
|
+
"umd": "dist/aform.umd.cjs",
|
|
15
28
|
"types": "src/index",
|
|
16
29
|
"files": [
|
|
17
30
|
"dist/*",
|
|
18
|
-
"src
|
|
31
|
+
"src/*"
|
|
19
32
|
],
|
|
20
33
|
"dependencies": {
|
|
21
34
|
"uuid": "^9.0.0",
|
|
22
|
-
"vue": "^3.
|
|
23
|
-
"@stonecrop/themes": "0.2.
|
|
24
|
-
"@stonecrop/utilities": "0.2.
|
|
35
|
+
"vue": "^3.4.23",
|
|
36
|
+
"@stonecrop/themes": "0.2.7",
|
|
37
|
+
"@stonecrop/utilities": "0.2.7"
|
|
25
38
|
},
|
|
26
39
|
"devDependencies": {
|
|
27
|
-
"@histoire/plugin-vue": "^0.
|
|
40
|
+
"@histoire/plugin-vue": "^0.17.17",
|
|
28
41
|
"@types/uuid": "^9.0.0",
|
|
29
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
30
|
-
"@typescript-eslint/parser": "^
|
|
31
|
-
"@vitejs/plugin-vue": "^
|
|
32
|
-
"@vitest/coverage-
|
|
33
|
-
"@vitest/ui": "^
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^7.6.0",
|
|
43
|
+
"@typescript-eslint/parser": "^7.6.0",
|
|
44
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
45
|
+
"@vitest/coverage-v8": "^1.5.0",
|
|
46
|
+
"@vitest/ui": "^1.5.0",
|
|
34
47
|
"@vue/test-utils": "^2.3.2",
|
|
35
48
|
"cypress": "^12.11.0",
|
|
36
49
|
"eslint": "^8.40.0",
|
|
37
50
|
"eslint-config-prettier": "^8.8.0",
|
|
38
51
|
"eslint-plugin-vue": "^9.11.1",
|
|
39
|
-
"histoire": "^0.
|
|
52
|
+
"histoire": "^0.17.17",
|
|
40
53
|
"jsdom": "^22.0.0",
|
|
41
|
-
"typescript": "^5.
|
|
42
|
-
"vite": "^
|
|
43
|
-
"vitest": "^
|
|
54
|
+
"typescript": "^5.4.5",
|
|
55
|
+
"vite": "^5.2.9",
|
|
56
|
+
"vitest": "^1.5.0",
|
|
44
57
|
"vue-router": "^4",
|
|
45
|
-
"@stonecrop/atable": "0.2.
|
|
58
|
+
"@stonecrop/atable": "0.2.7"
|
|
46
59
|
},
|
|
47
60
|
"peerDependencies": {
|
|
48
|
-
"@stonecrop/atable": "0.2.
|
|
61
|
+
"@stonecrop/atable": "0.2.7"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
49
65
|
},
|
|
50
66
|
"engines": {
|
|
51
67
|
"node": ">=20.11.0"
|
|
52
68
|
},
|
|
53
|
-
"umd": "dist/aform.umd.cjs",
|
|
54
69
|
"scripts": {
|
|
55
70
|
"build": "tsc -b && vite build",
|
|
56
71
|
"dev": "vite",
|
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
</template>
|
|
11
11
|
|
|
12
12
|
<script setup lang="ts">
|
|
13
|
-
import {
|
|
13
|
+
import { InputHTMLAttributes } from 'vue'
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
withDefaults(
|
|
16
16
|
defineProps<{
|
|
17
17
|
label?: string
|
|
18
|
-
value?: InputHTMLAttributes['checked']
|
|
19
18
|
required?: boolean
|
|
20
19
|
readOnly?: boolean
|
|
21
20
|
uuid?: string
|
|
@@ -26,18 +25,7 @@ const props = withDefaults(
|
|
|
26
25
|
}
|
|
27
26
|
)
|
|
28
27
|
|
|
29
|
-
const
|
|
30
|
-
(e: 'update:value', value: InputHTMLAttributes['checked']): void
|
|
31
|
-
}>()
|
|
32
|
-
|
|
33
|
-
const checkbox = computed({
|
|
34
|
-
get() {
|
|
35
|
-
return props.value
|
|
36
|
-
},
|
|
37
|
-
set(value) {
|
|
38
|
-
emit('update:value', value)
|
|
39
|
-
},
|
|
40
|
-
})
|
|
28
|
+
const checkbox = defineModel<InputHTMLAttributes['checked']>()
|
|
41
29
|
</script>
|
|
42
30
|
|
|
43
31
|
<style scoped>
|
|
@@ -7,12 +7,9 @@
|
|
|
7
7
|
</template>
|
|
8
8
|
|
|
9
9
|
<script setup lang="ts">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const props = withDefaults(
|
|
10
|
+
withDefaults(
|
|
13
11
|
defineProps<{
|
|
14
12
|
label: string
|
|
15
|
-
modelValue: any
|
|
16
13
|
required?: boolean
|
|
17
14
|
readonly?: boolean
|
|
18
15
|
uuid?: string
|
|
@@ -22,15 +19,8 @@ const props = withDefaults(
|
|
|
22
19
|
validation: () => ({ errorMessage: ' ' }),
|
|
23
20
|
}
|
|
24
21
|
)
|
|
25
|
-
|
|
26
|
-
const inputNumber =
|
|
27
|
-
get: () => {
|
|
28
|
-
return props.modelValue
|
|
29
|
-
},
|
|
30
|
-
set: newValue => {
|
|
31
|
-
emit('update:modelValue', newValue)
|
|
32
|
-
},
|
|
33
|
-
})
|
|
22
|
+
|
|
23
|
+
const inputNumber = defineModel<number>()
|
|
34
24
|
</script>
|
|
35
25
|
|
|
36
26
|
<style scoped>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="login-container">
|
|
3
|
+
<div>
|
|
4
|
+
<div class="account-container">
|
|
5
|
+
<div class="account-header">
|
|
6
|
+
<h1 id="account-title">{{ headerTitle }}</h1>
|
|
7
|
+
<p id="account-subtitle">{{ headerSubtitle }}</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<form @submit="onSubmit">
|
|
11
|
+
<div class="login-form-container">
|
|
12
|
+
<div class="login-form-email login-form-element">
|
|
13
|
+
<label id="login-email" for="email" class="login-label">Email</label>
|
|
14
|
+
<input
|
|
15
|
+
id="email"
|
|
16
|
+
class="login-field"
|
|
17
|
+
name="email"
|
|
18
|
+
placeholder="name@example.com"
|
|
19
|
+
type="email"
|
|
20
|
+
auto-capitalize="none"
|
|
21
|
+
auto-complete="email"
|
|
22
|
+
auto-correct="off"
|
|
23
|
+
:disabled="isLoading" />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="login-form-password login-form-element">
|
|
27
|
+
<label id="login-password" for="password" class="login-label">Password</label>
|
|
28
|
+
<input id="password" class="login-field" name="password" type="password" :disabled="isLoading" />
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<button class="btn" :disabled="isLoading">
|
|
32
|
+
<span v-if="isLoading" class="material-symbols-outlined loading-icon"> progress_activity </span>
|
|
33
|
+
<span id="login-form-button">Login</span>
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</form>
|
|
37
|
+
|
|
38
|
+
<button class="btn">
|
|
39
|
+
<span id="forgot-password-button">Forgot password?</span>
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
import { ref } from 'vue'
|
|
48
|
+
|
|
49
|
+
withDefaults(
|
|
50
|
+
defineProps<{
|
|
51
|
+
headerTitle?: string
|
|
52
|
+
headerSubtitle?: string
|
|
53
|
+
}>(),
|
|
54
|
+
{
|
|
55
|
+
headerTitle: 'Login',
|
|
56
|
+
headerSubtitle: 'Enter your email and password to login',
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const isLoading = ref(false)
|
|
61
|
+
|
|
62
|
+
function onSubmit(event: Event) {
|
|
63
|
+
event.preventDefault()
|
|
64
|
+
isLoading.value = true
|
|
65
|
+
|
|
66
|
+
// TODO: handle submit logic
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<style>
|
|
71
|
+
@import url('../../theme/login.css');
|
|
72
|
+
</style>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { FormSchema } from 'types'
|
|
2
|
+
import { DirectiveBinding } from 'vue'
|
|
3
|
+
|
|
4
|
+
const NAMED_MASKS = {
|
|
5
|
+
date: '##/##/####',
|
|
6
|
+
datetime: '####/##/## ##:##',
|
|
7
|
+
time: '##:##',
|
|
8
|
+
fulltime: '##:##:##',
|
|
9
|
+
phone: '(###) ### - ####',
|
|
10
|
+
card: '#### #### #### ####',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function extractMaskFn(mask: string): ((args: any) => string) | void {
|
|
14
|
+
try {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
16
|
+
return Function(`"use strict";return (${mask})`)()
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error instanceof ReferenceError) {
|
|
19
|
+
// assume mask is a string
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getMask(binding: DirectiveBinding<string>) {
|
|
25
|
+
let mask = binding.value
|
|
26
|
+
|
|
27
|
+
if (mask) {
|
|
28
|
+
const maskFn = extractMaskFn(mask)
|
|
29
|
+
if (maskFn) {
|
|
30
|
+
// TODO: (state) replace with state management;
|
|
31
|
+
// pass the entire form/table data to the function
|
|
32
|
+
const locale = binding.instance['locale']
|
|
33
|
+
mask = maskFn(locale)
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
// TODO: (state) handle using state management
|
|
37
|
+
const schema: FormSchema = binding.instance['schema']
|
|
38
|
+
const fieldType: string | undefined = schema.fieldtype?.toLowerCase()
|
|
39
|
+
if (fieldType && NAMED_MASKS[fieldType]) {
|
|
40
|
+
mask = NAMED_MASKS[fieldType]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return mask
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function unmaskInput(input: string, maskToken?: string) {
|
|
48
|
+
if (!maskToken) {
|
|
49
|
+
maskToken = '#'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let unmaskedInput = input
|
|
53
|
+
const maskChars = [maskToken, '/', '-', '(', ')', ' ']
|
|
54
|
+
|
|
55
|
+
for (const char of maskChars) {
|
|
56
|
+
unmaskedInput = unmaskedInput.replaceAll(char, '')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return unmaskedInput
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function fillMask(input: string, mask: string, maskToken?: string) {
|
|
63
|
+
if (!maskToken) {
|
|
64
|
+
maskToken = '#'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let replacement = mask
|
|
68
|
+
for (const inputChar of input) {
|
|
69
|
+
const replaceIndex = replacement.indexOf(maskToken)
|
|
70
|
+
if (replaceIndex !== -1) {
|
|
71
|
+
const prefix = replacement.substring(0, replaceIndex)
|
|
72
|
+
const suffix = replacement.substring(replaceIndex + 1)
|
|
73
|
+
replacement = prefix + inputChar + suffix
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return replacement.slice(0, mask.length)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function useStringMask(el: HTMLInputElement, binding: DirectiveBinding<string>) {
|
|
81
|
+
const mask = getMask(binding)
|
|
82
|
+
if (!mask) return
|
|
83
|
+
|
|
84
|
+
const maskToken = '#'
|
|
85
|
+
const inputText = el.value
|
|
86
|
+
|
|
87
|
+
// process input value with mask
|
|
88
|
+
const unmaskedInput = unmaskInput(inputText, maskToken)
|
|
89
|
+
if (unmaskedInput) {
|
|
90
|
+
const replacement = fillMask(unmaskedInput, mask, maskToken)
|
|
91
|
+
|
|
92
|
+
// TODO: (state) this is very opinionated;
|
|
93
|
+
// most likely fixed with state management;
|
|
94
|
+
// a better way could be to emit back to instance;
|
|
95
|
+
|
|
96
|
+
if (binding.instance['maskFilled']) {
|
|
97
|
+
binding.instance['maskFilled'] = !replacement.includes(maskToken)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
el.value = replacement
|
|
101
|
+
} else {
|
|
102
|
+
el.value = mask
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineSetupVue3 } from '@histoire/plugin-vue'
|
|
2
|
+
|
|
3
|
+
import ACheckbox from '@/components/form/ACheckbox.vue'
|
|
4
|
+
import AFieldset from '@/components/form/AFieldset.vue'
|
|
5
|
+
import AForm from '@/components/AForm.vue'
|
|
6
|
+
import ANumericInput from '@/components/form/ANumericInput.vue'
|
|
7
|
+
import ATextInput from '@/components/form/ATextInput.vue'
|
|
8
|
+
|
|
9
|
+
import { ATable, ATableHeader, ATableModal } from '@stonecrop/atable'
|
|
10
|
+
import '@stonecrop/atable/styles'
|
|
11
|
+
|
|
12
|
+
export const setupVue3 = defineSetupVue3(({ app }) => {
|
|
13
|
+
// TODO: (typing) add typing for ATable components
|
|
14
|
+
app.component('ACheckbox', ACheckbox)
|
|
15
|
+
app.component('AFieldset', AFieldset)
|
|
16
|
+
app.component('AForm', AForm)
|
|
17
|
+
app.component('ANumericInput', ANumericInput)
|
|
18
|
+
app.component('ATable', ATable)
|
|
19
|
+
app.component('ATableHeader', ATableHeader)
|
|
20
|
+
app.component('ATableModal', ATableModal)
|
|
21
|
+
app.component('ATextInput', ATextInput)
|
|
22
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { App } from 'vue'
|
|
2
|
+
|
|
3
|
+
import ACheckbox from '@/components/form/ACheckbox.vue'
|
|
4
|
+
import AComboBox from '@/components/form/AComboBox.vue'
|
|
5
|
+
import ADate from '@/components/form/ADate.vue'
|
|
6
|
+
import ADropdown from '@/components/form/ADropdown.vue'
|
|
7
|
+
import AFieldset from '@/components/form/AFieldset.vue'
|
|
8
|
+
import AForm from '@/components/AForm.vue'
|
|
9
|
+
import ANumericInput from '@/components/form/ANumericInput.vue'
|
|
10
|
+
import ATextInput from '@/components/form/ATextInput.vue'
|
|
11
|
+
// import { ACurrency } from '@/components/form/ACurrency.vue'
|
|
12
|
+
// import { AQuantity } from '@/components/form/AQuantity.vue'
|
|
13
|
+
|
|
14
|
+
function install(app: App /* options */) {
|
|
15
|
+
app.component('ACheckbox', ACheckbox)
|
|
16
|
+
app.component('ACombobox', AComboBox)
|
|
17
|
+
app.component('ADate', ADate)
|
|
18
|
+
app.component('ADropdown', ADropdown)
|
|
19
|
+
app.component('AFieldset', AFieldset)
|
|
20
|
+
app.component('AForm', AForm)
|
|
21
|
+
app.component('ANumericInput', ANumericInput)
|
|
22
|
+
app.component('ATextInput', ATextInput)
|
|
23
|
+
// app.component('ACurrency', ACurrency)
|
|
24
|
+
// app.component('AQuantity', AQuantity)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { ACheckbox, AComboBox, ADate, ADropdown, AFieldset, AForm, ANumericInput, ATextInput, install }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap');
|
|
2
|
+
@import url('@stonecrop/themes/default/default.css');
|
|
3
|
+
@import url('custom_themes.css');
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
font-family: var(--font-family);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.aform-primary-action {
|
|
10
|
+
font-size: 100%;
|
|
11
|
+
text-align: center;
|
|
12
|
+
min-height: 2em;
|
|
13
|
+
padding: 0.25rem 1rem 0.25rem 1rem;
|
|
14
|
+
border: 1px solid var(--primary-color);
|
|
15
|
+
color: var(--primary-text-color);
|
|
16
|
+
background-color: var(--primary-color);
|
|
17
|
+
outline: 2px solid var(--primary-text-color);
|
|
18
|
+
transition: outline-offset 200ms ease;
|
|
19
|
+
font-size: var(--font-size);
|
|
20
|
+
margin: 0.5ch;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.aform-primary-action:hover,
|
|
24
|
+
.aform-primary-action:active {
|
|
25
|
+
outline: 2px solid var(--primary-text-color);
|
|
26
|
+
outline-offset: -4px;
|
|
27
|
+
transition: outline-offset 200ms ease;
|
|
28
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap');
|
|
2
|
+
@import url('@stonecrop/themes/default/default.css');
|
|
3
|
+
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
/* BTNS */
|
|
7
|
+
--btn-color: white;
|
|
8
|
+
--btn-border: #cccccc;
|
|
9
|
+
--btn-hover: #f2f2f2;
|
|
10
|
+
--btn-label-color: black;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.login-container {
|
|
14
|
+
width: 100%;
|
|
15
|
+
position: relative;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
font-family: var(--font-family);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.account-container {
|
|
24
|
+
width: 100%;
|
|
25
|
+
margin-left: auto;
|
|
26
|
+
margin-top: 0.5rem;
|
|
27
|
+
margin-right: auto;
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.account-header {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
text-align: center;
|
|
37
|
+
margin-top: 0.5rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#account-title {
|
|
41
|
+
font-size: 1.5rem;
|
|
42
|
+
line-height: 2rem;
|
|
43
|
+
font-weight: 600;
|
|
44
|
+
letter-spacing: -0.025em;
|
|
45
|
+
margin: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#account-subtitle {
|
|
49
|
+
font-size: 0.875rem;
|
|
50
|
+
line-height: 1.25rem;
|
|
51
|
+
margin: 1rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.login-form-container {
|
|
55
|
+
display: grid;
|
|
56
|
+
gap: 0.5rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.login-form-email,
|
|
60
|
+
.login-form-password {
|
|
61
|
+
display: grid;
|
|
62
|
+
gap: 0.25rem;
|
|
63
|
+
}
|
|
64
|
+
.login-form-element {
|
|
65
|
+
margin: 0.5rem 0;
|
|
66
|
+
}
|
|
67
|
+
.login-field {
|
|
68
|
+
padding: 0.5rem 0.25rem 0.25rem 0.5rem;
|
|
69
|
+
outline: 1px solid transparent;
|
|
70
|
+
border: 1px solid var(--input-border-color);
|
|
71
|
+
border-radius: 0.25rem;
|
|
72
|
+
|
|
73
|
+
&:focus {
|
|
74
|
+
border: 1px solid black;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
.login-label {
|
|
78
|
+
position: absolute;
|
|
79
|
+
padding: 0;
|
|
80
|
+
background: white;
|
|
81
|
+
white-space: nowrap;
|
|
82
|
+
border-width: 0;
|
|
83
|
+
font-size: 0.7rem;
|
|
84
|
+
margin-left: 0.5rem;
|
|
85
|
+
margin-top: -0.7rem;
|
|
86
|
+
padding: 0.3rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#login-form-button {
|
|
90
|
+
margin-right: 0.5rem;
|
|
91
|
+
height: 1rem;
|
|
92
|
+
width: 1rem;
|
|
93
|
+
}
|
|
94
|
+
.btn {
|
|
95
|
+
background-color: var(--btn-color);
|
|
96
|
+
color: var(--btn-label-color);
|
|
97
|
+
border: 1px solid var(--btn-border);
|
|
98
|
+
margin: 0.5rem 0;
|
|
99
|
+
padding: 0.25rem;
|
|
100
|
+
position: relative;
|
|
101
|
+
|
|
102
|
+
&:hover {
|
|
103
|
+
background-color: var(--btn-hover);
|
|
104
|
+
}
|
|
105
|
+
&:disabled {
|
|
106
|
+
background-color: light-dark(rgba(239, 239, 239, 0.3), rgba(59, 59, 59, 0.3));
|
|
107
|
+
color: light-dark(rgb(84, 84, 84), rgb(170, 170, 170));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
.disabled {
|
|
111
|
+
opacity: 0.5;
|
|
112
|
+
}
|
|
113
|
+
.loading-icon {
|
|
114
|
+
animation: spin 1s linear infinite forwards;
|
|
115
|
+
display: inline-block;
|
|
116
|
+
margin-right: 0.2rem;
|
|
117
|
+
line-height: 0;
|
|
118
|
+
font-size: 1rem;
|
|
119
|
+
position: relative;
|
|
120
|
+
top: 0.2rem;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ANIMATION */
|
|
124
|
+
@keyframes spin {
|
|
125
|
+
from {
|
|
126
|
+
transform: rotate(0deg);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
to {
|
|
130
|
+
transform: rotate(360deg);
|
|
131
|
+
}
|
|
132
|
+
}
|