@1001-digital/layers.base 0.0.1

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 (55) hide show
  1. package/.editorconfig +12 -0
  2. package/.nuxtrc +1 -0
  3. package/.playground/app.config.ts +5 -0
  4. package/.playground/app.vue +3 -0
  5. package/.playground/nuxt.config.ts +12 -0
  6. package/.playground/pages/index.vue +626 -0
  7. package/AGENTS.md +51 -0
  8. package/README.md +13 -0
  9. package/app/app.vue +3 -0
  10. package/app/assets/styles/base/base.css +52 -0
  11. package/app/assets/styles/base/forms.css +129 -0
  12. package/app/assets/styles/base/reset.css +159 -0
  13. package/app/assets/styles/index.css +28 -0
  14. package/app/assets/styles/utilities/animations.css +77 -0
  15. package/app/assets/styles/utilities/utilities.css +58 -0
  16. package/app/assets/styles/variables/borders.css +18 -0
  17. package/app/assets/styles/variables/colors.css +75 -0
  18. package/app/assets/styles/variables/components/alerts.css +13 -0
  19. package/app/assets/styles/variables/components/buttons.css +18 -0
  20. package/app/assets/styles/variables/components/cards.css +7 -0
  21. package/app/assets/styles/variables/components/dialogs.css +7 -0
  22. package/app/assets/styles/variables/components/forms.css +5 -0
  23. package/app/assets/styles/variables/components/images.css +5 -0
  24. package/app/assets/styles/variables/components/index.css +6 -0
  25. package/app/assets/styles/variables/effects.css +3 -0
  26. package/app/assets/styles/variables/fonts.css +36 -0
  27. package/app/assets/styles/variables/index.css +15 -0
  28. package/app/assets/styles/variables/layout.css +7 -0
  29. package/app/assets/styles/variables/sizes.css +24 -0
  30. package/app/assets/styles/variables/timing.css +5 -0
  31. package/app/assets/styles/variables/ui.css +18 -0
  32. package/app/assets/styles/variables/z-index.css +7 -0
  33. package/app/components/Actions.vue +57 -0
  34. package/app/components/Alert.vue +78 -0
  35. package/app/components/Button.vue +196 -0
  36. package/app/components/Card.vue +62 -0
  37. package/app/components/Dialog.client.vue +217 -0
  38. package/app/components/Form/Form.vue +27 -0
  39. package/app/components/Form/FormCheckbox.vue +88 -0
  40. package/app/components/Form/FormGroup.vue +36 -0
  41. package/app/components/Form/FormInputGroup.vue +55 -0
  42. package/app/components/Form/FormItem.vue +53 -0
  43. package/app/components/Form/FormLabel.vue +39 -0
  44. package/app/components/Form/FormRadioGroup.vue +109 -0
  45. package/app/components/Form/FormSelect.vue +155 -0
  46. package/app/components/HelloWorld.vue +10 -0
  47. package/app/components/Icon.vue +48 -0
  48. package/app/components/Loading.vue +58 -0
  49. package/app/components/Tag.vue +47 -0
  50. package/app/components/Tags.vue +13 -0
  51. package/app.config.ts +14 -0
  52. package/eslint.config.js +3 -0
  53. package/nuxt.config.ts +19 -0
  54. package/package.json +29 -0
  55. package/tsconfig.json +3 -0
@@ -0,0 +1,217 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <component ref="dialog" :is="tag" :class="classes" @cancel.stop.prevent="open = false">
4
+ <button v-if="xClose" class="close unstyled" :title="`Close ${title || 'Dialog'}`" @touchdown="open = false"
5
+ @click="open = false">
6
+ <Icon type="close" />
7
+ </button>
8
+
9
+ <h1 v-if="title">{{ title }}</h1>
10
+
11
+ <slot />
12
+ </component>
13
+
14
+ <div v-if="compat" class="overlay" @click="() => onClickOutside()"></div>
15
+ </Teleport>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ const dialog = ref<HTMLDialogElement | null>(null)
20
+ const props = withDefaults(defineProps<{
21
+ title?: string
22
+ class?: string | string[] | Record<string, boolean>
23
+ xClose?: boolean
24
+ clickOutside?: boolean
25
+ compat?: boolean
26
+ }>(), {
27
+ xClose: true,
28
+ clickOutside: true,
29
+ })
30
+ const emit = defineEmits<{
31
+ closed: []
32
+ }>()
33
+ const open = defineModel<boolean>('open', { required: true })
34
+ const debouncedOpen = ref(open.value)
35
+ const tag = computed(() => (props.compat ? 'article' : 'dialog'))
36
+ const classes = computed(() => {
37
+ let obj: Record<string, boolean> = {
38
+ dialog: true,
39
+ compat: !!props.compat,
40
+ }
41
+
42
+ // Apply passed classes
43
+ if (typeof props.class === 'string') {
44
+ obj[props.class] = true
45
+ } else if (Array.isArray(props.class)) {
46
+ props.class.forEach((c) => {
47
+ obj[c] = true
48
+ })
49
+ } else if (typeof props.class === 'object') {
50
+ obj = { ...obj, ...props.class }
51
+ }
52
+
53
+ // Apply open state class
54
+ if (props.compat && debouncedOpen.value) {
55
+ obj.open = true
56
+ }
57
+
58
+ return obj
59
+ })
60
+
61
+ const show = () => {
62
+ if (props.compat) {
63
+ debouncedOpen.value = true
64
+ } else {
65
+ dialog.value?.showModal()
66
+ }
67
+ }
68
+
69
+ const hide = () => {
70
+ if (props.compat) {
71
+ debouncedOpen.value = false
72
+ } else {
73
+ dialog.value?.close()
74
+ }
75
+ emit('closed')
76
+ }
77
+
78
+ const onClickOutside = () => {
79
+ if (props.clickOutside) {
80
+ open.value = false
81
+ }
82
+ }
83
+
84
+ // Keep track of the open/hide state
85
+ watchEffect(() => (open.value ? show() : hide()))
86
+ </script>
87
+
88
+ <style>
89
+ .dialog {
90
+ padding: var(--spacer);
91
+ padding-block-start: calc(var(--spacer) * 3);
92
+ max-inline-size: min(var(--dialog-width, 32rem), calc(100vw - var(--spacer) * 2));
93
+ inline-size: 100%;
94
+ background: var(--background);
95
+ color: var(--color);
96
+ border: var(--border);
97
+ border-radius: var(--border-radius);
98
+ overscroll-behavior: contain;
99
+ block-size: 0;
100
+ min-block-size: min-content;
101
+ max-block-size: 100dvh;
102
+ container-type: inline-size;
103
+ display: grid;
104
+ gap: var(--spacer);
105
+
106
+ /* Entry/exit animations */
107
+ opacity: 1;
108
+ transform: scale(1);
109
+ transition:
110
+ opacity var(--speed) ease,
111
+ transform var(--speed) ease,
112
+ overlay var(--speed) ease allow-discrete,
113
+ display var(--speed) ease allow-discrete;
114
+
115
+ @starting-style {
116
+ opacity: 0;
117
+ transform: scale(0.95);
118
+ }
119
+
120
+ /* Exit animation */
121
+ &:not([open]):not(:popover-open) {
122
+ opacity: 0;
123
+ transform: scale(0.95);
124
+ }
125
+
126
+ &::backdrop {
127
+ background-color: var(--backdrop-background-color);
128
+ backdrop-filter: var(--blur);
129
+ transition:
130
+ background-color var(--speed) ease,
131
+ backdrop-filter var(--speed) ease,
132
+ overlay var(--speed) ease allow-discrete,
133
+ display var(--speed) ease allow-discrete;
134
+
135
+ @starting-style {
136
+ background-color: transparent;
137
+ }
138
+ }
139
+
140
+ @media (--md) {
141
+ max-block-size: calc(100dvh - var(--spacer) * 2);
142
+ }
143
+
144
+ &.compat {
145
+ position: fixed;
146
+ transform: translate(-50%, -50%);
147
+
148
+ &.open {
149
+ inset-block-start: 50%;
150
+ inset-inline-start: 50%;
151
+ z-index: var(--z-index-dialog);
152
+
153
+ +.overlay {
154
+ position: fixed;
155
+ inset: 0;
156
+ z-index: var(--z-index-overlay);
157
+ background: var(--backdrop-background-color);
158
+ }
159
+ }
160
+ }
161
+
162
+ &:focus {
163
+ outline: none;
164
+ }
165
+
166
+ >.close {
167
+ position: absolute;
168
+ inset-block-start: 0;
169
+ inset-inline-end: 0;
170
+ block-size: calc(var(--spacer) * 2);
171
+ inline-size: calc(var(--spacer) * 2);
172
+ border-inline-start: var(--border);
173
+ border-block-end: var(--border);
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ padding: 0;
178
+ z-index: 10;
179
+ background: var(--background);
180
+
181
+ &:is(:hover, :active, :focus, .active) {
182
+ outline: none;
183
+ }
184
+ }
185
+
186
+ >h1:first-of-type {
187
+ inline-size: 100%;
188
+ border-block-end: var(--border);
189
+ block-size: calc(var(--spacer) * 2);
190
+ position: absolute;
191
+ inset-block-start: 0;
192
+ inset-inline-start: 0;
193
+ padding: 0 0 0 var(--spacer);
194
+ display: flex;
195
+ align-items: center;
196
+ margin: 0;
197
+ font-family: var(--ui-font-family);
198
+ font-size: var(--ui-font-size);
199
+ text-transform: var(--ui-text-transform);
200
+ background: var(--background);
201
+ }
202
+
203
+ >.actions {
204
+ margin-block-start: var(--spacer);
205
+ display: flex;
206
+ gap: var(--spacer);
207
+ justify-content: flex-end;
208
+ }
209
+ }
210
+
211
+ html:has(dialog[open]),
212
+ body:has(dialog[open]),
213
+ html:has(.dialog.open),
214
+ body:has(.dialog.open) {
215
+ overflow: hidden;
216
+ }
217
+ </style>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <form class="form">
3
+ <slot />
4
+ </form>
5
+ </template>
6
+
7
+ <style scoped>
8
+ .form {
9
+ display: grid;
10
+ gap: var(--spacer);
11
+
12
+ :deep(> header),
13
+ :deep(> footer) {
14
+ display: grid;
15
+ gap: var(--spacer-sm);
16
+
17
+ h1,
18
+ p {
19
+ text-align: start;
20
+ }
21
+
22
+ h1 {
23
+ font-size: var(--font-xl);
24
+ }
25
+ }
26
+ }
27
+ </style>
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <label class="form-checkbox">
3
+ <CheckboxRoot v-model="model" :disabled="disabled" :name="name" :value="value" class="form-checkbox-button">
4
+ <CheckboxIndicator class="form-checkbox-indicator">
5
+ <Icon type="check" />
6
+ </CheckboxIndicator>
7
+ </CheckboxRoot>
8
+ <span v-if="$slots.default"><slot /></span>
9
+ </label>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import {
14
+ CheckboxIndicator,
15
+ CheckboxRoot,
16
+ } from 'reka-ui'
17
+
18
+ const model = defineModel<boolean | 'indeterminate'>()
19
+
20
+ defineProps({
21
+ disabled: {
22
+ type: Boolean,
23
+ default: false,
24
+ },
25
+ name: {
26
+ type: String,
27
+ default: undefined,
28
+ },
29
+ value: {
30
+ type: String,
31
+ default: 'on',
32
+ },
33
+ })
34
+ </script>
35
+
36
+ <style scoped>
37
+ .form-checkbox {
38
+ display: flex;
39
+ align-items: center;
40
+ gap: var(--size-2);
41
+ cursor: pointer;
42
+ user-select: none;
43
+
44
+ &:hover .form-checkbox-button {
45
+ border-color: var(--primary);
46
+ }
47
+ }
48
+
49
+ .form-checkbox-button {
50
+ all: unset;
51
+ inline-size: var(--size-4);
52
+ block-size: var(--size-4);
53
+ border-radius: calc(var(--border-radius) / 2);
54
+ border: 2px solid var(--muted);
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ transition: all var(--speed);
59
+ flex-shrink: 0;
60
+
61
+ &[data-state='checked'],
62
+ &[data-state='indeterminate'] {
63
+ border-color: var(--primary);
64
+ background: var(--primary);
65
+ }
66
+
67
+ &[data-disabled] {
68
+ opacity: 0.5;
69
+ cursor: not-allowed;
70
+ }
71
+
72
+ &:focus-visible {
73
+ outline: 2px solid var(--primary);
74
+ outline-offset: 2px;
75
+ }
76
+ }
77
+
78
+ .form-checkbox-indicator {
79
+ color: var(--background);
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+
84
+ .icon {
85
+ font-size: var(--font-xs);
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <div class="form-group" :class="{ radio }">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ defineProps<{
9
+ radio?: boolean
10
+ }>()
11
+ </script>
12
+
13
+ <style scoped>
14
+ .form-group {
15
+ display: grid;
16
+ gap: var(--spacer-sm);
17
+
18
+ &.radio {
19
+ display: flex;
20
+ gap: var(--spacer-sm);
21
+
22
+ :deep(input) {
23
+ inline-size: min-content;
24
+
25
+ &:not(:first-of-type) {
26
+ margin-inline-start: var(--spacer-sm);
27
+ }
28
+ }
29
+
30
+ :deep(label),
31
+ :deep(input) {
32
+ display: inline;
33
+ }
34
+ }
35
+ }
36
+ </style>
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <div class="input-group">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <style scoped>
8
+ .input-group {
9
+ position: relative;
10
+ display: flex;
11
+ align-items: center;
12
+
13
+ :deep(input + .icon) {
14
+ position: absolute;
15
+ inset-inline-end: var(--ui-padding-inline);
16
+ inline-size: var(--size-4);
17
+ color: var(--muted);
18
+ z-index: 2;
19
+ }
20
+
21
+ :deep(input:has(+ .icon)) {
22
+ padding-inline-end: calc(var(--ui-padding-inline) + var(--spacer-sm));
23
+
24
+ &:is(:hover, :active, :focus, .active) + .icon {
25
+ color: var(--gray-z-6);
26
+ }
27
+ }
28
+
29
+ :deep(input),
30
+ :deep(button),
31
+ :deep(.button),
32
+ :deep(a) {
33
+ z-index: 1;
34
+
35
+ &:has(+ input),
36
+ &:has(+ button),
37
+ &:has(+ a) {
38
+ border-start-end-radius: 0 !important;
39
+ border-end-end-radius: 0 !important;
40
+ }
41
+
42
+ + input,
43
+ + button,
44
+ + a {
45
+ margin-inline-start: calc(-1 * var(--border-width));
46
+ border-start-start-radius: 0 !important;
47
+ border-end-start-radius: 0 !important;
48
+ }
49
+
50
+ &:is(:hover, :active, :focus, .active) {
51
+ z-index: 2;
52
+ }
53
+ }
54
+ }
55
+ </style>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <div class="form-item">
3
+ <span v-if="$slots.prefix" class="prefix">
4
+ <slot name="prefix" />
5
+ </span>
6
+ <slot />
7
+ <span v-if="$slots.suffix" class="suffix">
8
+ <slot name="suffix" />
9
+ </span>
10
+ </div>
11
+ </template>
12
+
13
+ <style scoped>
14
+ .form-item {
15
+ border: var(--border);
16
+ border-radius: var(--border-radius);
17
+ display: flex;
18
+ align-items: center;
19
+ background: var(--background);
20
+ inline-size: 100%;
21
+ max-inline-size: -webkit-fill-available;
22
+
23
+ :deep(input),
24
+ :deep(textarea),
25
+ :deep(select) {
26
+ border: none;
27
+ }
28
+
29
+ &:has(input:hover),
30
+ &:has(input:focus) {
31
+ background: var(--button-background-highlight);
32
+ border-color: var(--button-border-color-highlight);
33
+ }
34
+
35
+ .prefix,
36
+ .suffix {
37
+ padding: 0 var(--ui-padding-inline);
38
+ color: var(--muted);
39
+ block-size: 100%;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ }
44
+
45
+ .prefix {
46
+ border-inline-end: var(--button-border);
47
+ }
48
+
49
+ .suffix {
50
+ border-inline-start: var(--button-border);
51
+ }
52
+ }
53
+ </style>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <label class="form-label">
3
+ <span v-if="label">{{ label }}</span>
4
+ <slot />
5
+ </label>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ defineProps<{
10
+ label?: string
11
+ }>()
12
+ </script>
13
+
14
+ <style scoped>
15
+ .form-label {
16
+ font-family: var(--ui-font-family);
17
+ font-size: var(--ui-font-size);
18
+ font-weight: var(--ui-font-weight);
19
+ text-transform: var(--ui-text-transform);
20
+ letter-spacing: var(--ui-letter-spacing);
21
+ line-height: var(--ui-line-height);
22
+ color: var(--ui-color);
23
+ transition: all var(--speed);
24
+ display: grid;
25
+ gap: var(--size-2);
26
+
27
+ &:hover {
28
+ color: var(--color);
29
+ }
30
+
31
+ :deep(a) {
32
+ text-decoration: underline;
33
+ }
34
+
35
+ > span:first-child {
36
+ display: block;
37
+ }
38
+ }
39
+ </style>
@@ -0,0 +1,109 @@
1
+ <template>
2
+ <RadioGroupRoot v-model="model" :disabled="disabled" :name="name" :orientation="orientation" class="form-radio-group">
3
+ <label v-for="option in options" :key="option[valueKey]" class="form-radio-item">
4
+ <RadioGroupItem :value="option[valueKey]" class="form-radio-button">
5
+ <RadioGroupIndicator class="form-radio-indicator" />
6
+ </RadioGroupItem>
7
+ <span>{{ option[labelKey] }}</span>
8
+ </label>
9
+ </RadioGroupRoot>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import {
14
+ RadioGroupIndicator,
15
+ RadioGroupItem,
16
+ RadioGroupRoot,
17
+ } from 'reka-ui'
18
+
19
+ const model = defineModel<string>()
20
+
21
+ defineProps({
22
+ options: {
23
+ type: Array as () => Record<string, any>[],
24
+ default: () => [],
25
+ },
26
+ disabled: {
27
+ type: Boolean,
28
+ default: false,
29
+ },
30
+ orientation: {
31
+ type: String as () => 'horizontal' | 'vertical',
32
+ default: 'horizontal',
33
+ },
34
+ valueKey: {
35
+ type: String,
36
+ default: 'value',
37
+ },
38
+ labelKey: {
39
+ type: String,
40
+ default: 'label',
41
+ },
42
+ name: {
43
+ type: String,
44
+ default: undefined,
45
+ },
46
+ })
47
+ </script>
48
+
49
+ <style scoped>
50
+ .form-radio-group {
51
+ display: flex;
52
+ gap: var(--spacer);
53
+
54
+ &[data-orientation='vertical'] {
55
+ flex-direction: column;
56
+ gap: var(--spacer-sm);
57
+ }
58
+
59
+ &[data-disabled] {
60
+ opacity: 0.5;
61
+ cursor: not-allowed;
62
+ }
63
+ }
64
+
65
+ .form-radio-item {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: var(--size-2);
69
+ cursor: pointer;
70
+ user-select: none;
71
+
72
+ &:hover .form-radio-button {
73
+ border-color: var(--primary);
74
+ }
75
+ }
76
+
77
+ .form-radio-button {
78
+ all: unset;
79
+ inline-size: var(--size-4);
80
+ block-size: var(--size-4);
81
+ border-radius: 50%;
82
+ border: 2px solid var(--muted);
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ transition: border-color var(--speed);
87
+ flex-shrink: 0;
88
+
89
+ &[data-state='checked'] {
90
+ border-color: var(--primary);
91
+ }
92
+
93
+ &[data-disabled] {
94
+ cursor: not-allowed;
95
+ }
96
+
97
+ &:focus-visible {
98
+ outline: 2px solid var(--primary);
99
+ outline-offset: 2px;
100
+ }
101
+ }
102
+
103
+ .form-radio-indicator {
104
+ inline-size: var(--size-2);
105
+ block-size: var(--size-2);
106
+ border-radius: 50%;
107
+ background: var(--primary);
108
+ }
109
+ </style>