@1001-digital/components 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 (52) hide show
  1. package/package.json +45 -0
  2. package/src/base/components/Actions.vue +57 -0
  3. package/src/base/components/Alert.vue +90 -0
  4. package/src/base/components/Button.vue +260 -0
  5. package/src/base/components/Card.vue +78 -0
  6. package/src/base/components/CardLink.vue +56 -0
  7. package/src/base/components/Dialog.vue +274 -0
  8. package/src/base/components/Dropdown.vue +167 -0
  9. package/src/base/components/DropdownCheckboxItem.vue +30 -0
  10. package/src/base/components/DropdownGroup.vue +9 -0
  11. package/src/base/components/DropdownItem.vue +23 -0
  12. package/src/base/components/DropdownLabel.vue +9 -0
  13. package/src/base/components/DropdownRadioGroup.vue +15 -0
  14. package/src/base/components/DropdownRadioItem.vue +29 -0
  15. package/src/base/components/DropdownSeparator.vue +7 -0
  16. package/src/base/components/DropdownSub.vue +58 -0
  17. package/src/base/components/Form.vue +27 -0
  18. package/src/base/components/FormCheckbox.vue +92 -0
  19. package/src/base/components/FormGroup.vue +39 -0
  20. package/src/base/components/FormInputGroup.vue +55 -0
  21. package/src/base/components/FormItem.vue +89 -0
  22. package/src/base/components/FormLabel.vue +39 -0
  23. package/src/base/components/FormRadioGroup.vue +118 -0
  24. package/src/base/components/FormSelect.vue +160 -0
  25. package/src/base/components/FormTextarea.vue +38 -0
  26. package/src/base/components/Icon.vue +29 -0
  27. package/src/base/components/Loading.vue +81 -0
  28. package/src/base/components/Popover.vue +182 -0
  29. package/src/base/components/Tag.vue +56 -0
  30. package/src/base/components/Tags.vue +13 -0
  31. package/src/base/components/Toasts.vue +254 -0
  32. package/src/base/components/Tooltip.vue +100 -0
  33. package/src/base/composables/time.ts +82 -0
  34. package/src/base/composables/toast.ts +40 -0
  35. package/src/base/icons.ts +20 -0
  36. package/src/base/link.ts +4 -0
  37. package/src/base/utils/format-number.ts +29 -0
  38. package/src/base/utils/time.ts +20 -0
  39. package/src/evm/components/EvmAccount.vue +28 -0
  40. package/src/evm/components/EvmConnect.vue +254 -0
  41. package/src/evm/components/EvmConnectorQR.vue +116 -0
  42. package/src/evm/components/EvmMetaMaskQR.vue +15 -0
  43. package/src/evm/components/EvmTransactionFlow.vue +327 -0
  44. package/src/evm/components/EvmWalletConnectQR.vue +13 -0
  45. package/src/evm/composables/base.ts +7 -0
  46. package/src/evm/composables/chainId.ts +41 -0
  47. package/src/evm/config.ts +32 -0
  48. package/src/evm/index.ts +25 -0
  49. package/src/evm/utils/addresses.ts +6 -0
  50. package/src/evm/utils/chains.ts +32 -0
  51. package/src/evm/utils/format-eth.ts +15 -0
  52. package/src/index.ts +68 -0
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <div :class="['loader', { stacked }]">
3
+ <span
4
+ v-if="spinner"
5
+ class="spinner"
6
+ aria-hidden="true"
7
+ ></span>
8
+ <span
9
+ v-if="txt"
10
+ class="text"
11
+ >{{ txt }}</span
12
+ >
13
+ </div>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ withDefaults(
18
+ defineProps<{
19
+ txt?: string
20
+ spinner?: boolean
21
+ stacked?: boolean
22
+ }>(),
23
+ {
24
+ txt: 'Loading...',
25
+ spinner: false,
26
+ },
27
+ )
28
+ </script>
29
+
30
+ <style scoped>
31
+ .loader {
32
+ position: relative;
33
+ z-index: var(--z-index-ui);
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ gap: var(--spacer-sm);
38
+
39
+ .spinner {
40
+ width: var(--size-3);
41
+ height: var(--size-3);
42
+ border: 2px solid var(--muted);
43
+ border-top-color: transparent;
44
+ border-radius: 50%;
45
+ animation: spin var(--speed-slow, 1s) linear infinite;
46
+ }
47
+
48
+ .text {
49
+ @mixin ui-font;
50
+ color: var(--muted);
51
+ }
52
+
53
+ &:not(.stacked) {
54
+ .text {
55
+ width: min-content;
56
+ }
57
+ }
58
+
59
+ &.stacked {
60
+ flex-direction: column;
61
+ gap: var(--spacer);
62
+ }
63
+
64
+ &:not(.inline) {
65
+ text-align: center;
66
+ margin: calc(var(--size-6)) 0;
67
+ }
68
+
69
+ &.inline {
70
+ display: inline-flex;
71
+ gap: var(--size-1);
72
+ margin-left: var(--size-1);
73
+ }
74
+ }
75
+
76
+ @keyframes spin {
77
+ to {
78
+ transform: rotate(360deg);
79
+ }
80
+ }
81
+ </style>
@@ -0,0 +1,182 @@
1
+ <template>
2
+ <PopoverRoot
3
+ v-model:open="open"
4
+ :modal="modal"
5
+ >
6
+ <PopoverTrigger as-child>
7
+ <slot name="trigger" />
8
+ </PopoverTrigger>
9
+
10
+ <PopoverPortal>
11
+ <PopoverContent
12
+ class="popover"
13
+ :class="props.class"
14
+ :side="side"
15
+ :align="align"
16
+ :side-offset="sideOffset"
17
+ :align-offset="alignOffset"
18
+ :avoid-collisions="avoidCollisions"
19
+ :collision-padding="collisionPadding"
20
+ @open-auto-focus.prevent="onOpenAutoFocus"
21
+ @interact-outside="onInteractOutside"
22
+ >
23
+ <h1 v-if="title || $slots.title">
24
+ <slot name="title">{{ title }}</slot>
25
+ </h1>
26
+ <PopoverClose
27
+ v-if="closable"
28
+ :as="Button"
29
+ class="popover-close tertiary"
30
+ aria-label="Close"
31
+ >
32
+ <Icon type="close" />
33
+ </PopoverClose>
34
+
35
+ <section>
36
+ <slot />
37
+ </section>
38
+
39
+ <PopoverArrow
40
+ v-if="arrow"
41
+ class="popover-arrow"
42
+ />
43
+ </PopoverContent>
44
+ </PopoverPortal>
45
+ </PopoverRoot>
46
+ </template>
47
+
48
+ <script setup lang="ts">
49
+ import Button from './Button.vue'
50
+ import Icon from './Icon.vue'
51
+ import {
52
+ PopoverArrow,
53
+ PopoverClose,
54
+ PopoverContent,
55
+ PopoverPortal,
56
+ PopoverRoot,
57
+ PopoverTrigger,
58
+ } from 'reka-ui'
59
+
60
+ const props = withDefaults(
61
+ defineProps<{
62
+ class?: string | string[] | Record<string, boolean>
63
+ side?: 'top' | 'right' | 'bottom' | 'left'
64
+ align?: 'start' | 'center' | 'end'
65
+ sideOffset?: number
66
+ alignOffset?: number
67
+ avoidCollisions?: boolean
68
+ collisionPadding?: number
69
+ title?: string
70
+ arrow?: boolean
71
+ closable?: boolean
72
+ dismissable?: boolean
73
+ modal?: boolean
74
+ }>(),
75
+ {
76
+ side: 'bottom',
77
+ align: 'center',
78
+ sideOffset: 4,
79
+ avoidCollisions: true,
80
+ collisionPadding: 8,
81
+ dismissable: true,
82
+ },
83
+ )
84
+
85
+ const open = defineModel<boolean>('open', { required: true })
86
+
87
+ // Focus the popover itself to prevent the close button from gaining focus
88
+ const onOpenAutoFocus = (e: Event) => {
89
+ ;(e.target as HTMLElement)?.focus()
90
+ }
91
+
92
+ const onInteractOutside = (e: Event) => {
93
+ if (!props.dismissable) e.preventDefault()
94
+ }
95
+ </script>
96
+
97
+ <style scoped>
98
+ @layer components {
99
+ :deep(.popover) {
100
+ background: var(--popover-background);
101
+ color: var(--color);
102
+ border: var(--popover-border);
103
+ border-radius: var(--popover-border-radius);
104
+ padding: 0;
105
+ font-family: var(--font-family);
106
+ font-size: var(--ui-font-size);
107
+ z-index: var(--z-index-ui);
108
+ min-inline-size: max(
109
+ var(--popover-min-width, 12rem),
110
+ var(--reka-popover-trigger-width)
111
+ );
112
+ inline-size: min(
113
+ var(--popover-width, 20rem),
114
+ calc(100vw - var(--spacer) * 2)
115
+ );
116
+ max-block-size: var(--reka-popover-content-available-height);
117
+ container-type: inline-size;
118
+ transform-origin: var(--reka-popover-content-transform-origin);
119
+
120
+ display: grid;
121
+ grid-template-rows: auto minmax(0, 1fr) auto;
122
+
123
+ /* Entry/exit animations */
124
+ opacity: 1;
125
+ scale: 1;
126
+ transition:
127
+ opacity var(--speed) ease,
128
+ scale var(--speed) ease;
129
+
130
+ @starting-style {
131
+ opacity: 0;
132
+ scale: 0.95;
133
+ }
134
+
135
+ &[data-state='closed'] {
136
+ opacity: 0;
137
+ scale: 0.95;
138
+ }
139
+
140
+ &:focus {
141
+ outline: none;
142
+ }
143
+
144
+ > h1:first-child {
145
+ display: flex;
146
+ align-items: center;
147
+ block-size: calc(var(--spacer) * 2);
148
+ border-start-start-radius: var(--popover-border-radius);
149
+ border-start-end-radius: var(--popover-border-radius);
150
+ box-shadow: var(--border-shadow);
151
+ padding-inline-start: var(--popover-padding);
152
+ padding-right: calc(var(--spacer) * 3);
153
+ font-family: var(--font-family);
154
+ font-size: var(--ui-font-size);
155
+ text-transform: var(--ui-text-transform);
156
+ margin: 0;
157
+ }
158
+
159
+ > section {
160
+ overflow-y: auto;
161
+ overscroll-behavior: contain;
162
+ padding: var(--popover-padding);
163
+ display: grid;
164
+ gap: var(--spacer);
165
+ }
166
+
167
+ .popover-close {
168
+ position: absolute !important;
169
+ top: 0;
170
+ right: 0;
171
+ box-shadow: var(--border-shadow) !important;
172
+ border-radius: 0 !important;
173
+ border-start-end-radius: var(--popover-border-radius) !important;
174
+ }
175
+
176
+ .popover-arrow {
177
+ fill: var(--popover-arrow-fill);
178
+ stroke: var(--border-color);
179
+ }
180
+ }
181
+ }
182
+ </style>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <span class="tag">
3
+ <span>
4
+ <slot />
5
+ </span>
6
+ <Button
7
+ v-if="dismissable"
8
+ @click="emit('dismiss')"
9
+ >
10
+ <Icon type="close" />
11
+ </Button>
12
+ </span>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import Button from './Button.vue'
17
+ import Icon from './Icon.vue'
18
+
19
+ defineProps<{
20
+ dismissable?: boolean
21
+ }>()
22
+
23
+ const emit = defineEmits<{
24
+ dismiss: []
25
+ }>()
26
+ </script>
27
+
28
+ <style scoped>
29
+ .tag {
30
+ border-radius: var(--tag-border-radius);
31
+ box-shadow: var(--border-shadow);
32
+ font-family: var(--font-family);
33
+ font-size: var(--font-sm);
34
+ font-weight: var(--ui-font-weight);
35
+ text-transform: var(--ui-text-transform);
36
+ letter-spacing: var(--ui-letter-spacing);
37
+ line-height: var(--ui-line-height);
38
+ color: var(--ui-color);
39
+ display: flex;
40
+ align-items: center;
41
+
42
+ > span {
43
+ padding: var(--spacer-sm) var(--spacer);
44
+
45
+ + button {
46
+ border-top-left-radius: 0;
47
+ border-bottom-left-radius: 0;
48
+ padding: var(--spacer-sm);
49
+
50
+ &:is(:hover, :active, :focus, .active) {
51
+ background: var(--gray-z-1);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ </style>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div class="tags">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <style scoped>
8
+ .tags {
9
+ display: flex;
10
+ flex-wrap: wrap;
11
+ gap: var(--spacer-sm);
12
+ }
13
+ </style>
@@ -0,0 +1,254 @@
1
+ <template>
2
+ <ToastProvider
3
+ :duration="duration"
4
+ :swipe-direction="swipeDirection"
5
+ >
6
+ <ToastRoot
7
+ v-for="toast in toasts"
8
+ :key="toast.id"
9
+ :duration="toast.duration"
10
+ force-mount
11
+ class="toast"
12
+ :class="[toast.variant || 'info']"
13
+ @update:open="(open) => !open && onClose(toast.id)"
14
+ >
15
+ <ToastTitle class="toast-title">
16
+ {{ toast.title }}
17
+ </ToastTitle>
18
+ <ToastClose
19
+ class="toast-close tertiary"
20
+ :as="Button"
21
+ aria-label="Close"
22
+ >
23
+ <Icon type="close" />
24
+ </ToastClose>
25
+
26
+ <section v-if="toast.description || toast.action">
27
+ <ToastDescription
28
+ v-if="toast.description"
29
+ class="toast-description"
30
+ >
31
+ {{ toast.description }}
32
+ </ToastDescription>
33
+ <ToastAction
34
+ v-if="toast.action"
35
+ :alt-text="toast.action.label"
36
+ :as="Actions"
37
+ class="left"
38
+ >
39
+ <Button
40
+ class="small"
41
+ @click="toast.action!.onClick()"
42
+ >
43
+ {{ toast.action.label }}
44
+ </Button>
45
+ </ToastAction>
46
+ </section>
47
+ </ToastRoot>
48
+
49
+ <ToastViewport
50
+ class="toast-viewport"
51
+ :class="[position]"
52
+ />
53
+ </ToastProvider>
54
+ </template>
55
+
56
+ <script setup lang="ts">
57
+ import Actions from './Actions.vue'
58
+ import Button from './Button.vue'
59
+ import Icon from './Icon.vue'
60
+ import { useToast } from '../composables/toast'
61
+ import {
62
+ ToastAction,
63
+ ToastClose,
64
+ ToastDescription,
65
+ ToastProvider,
66
+ ToastRoot,
67
+ ToastTitle,
68
+ ToastViewport,
69
+ } from 'reka-ui'
70
+
71
+ withDefaults(
72
+ defineProps<{
73
+ duration?: number
74
+ swipeDirection?: 'right' | 'left' | 'up' | 'down'
75
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
76
+ }>(),
77
+ {
78
+ duration: 5_000,
79
+ swipeDirection: 'right',
80
+ position: 'bottom-right',
81
+ },
82
+ )
83
+
84
+ const { toasts, dismiss } = useToast()
85
+
86
+ const onClose = (id: string) => {
87
+ setTimeout(() => dismiss(id), 300)
88
+ }
89
+ </script>
90
+
91
+ <style scoped>
92
+ @layer components {
93
+ :deep(.toast-viewport) {
94
+ position: fixed;
95
+ z-index: var(--z-index-toast);
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: var(--toast-gap);
99
+ padding: var(--spacer);
100
+ margin: 0;
101
+ list-style: none;
102
+ max-width: 100vw;
103
+ outline: none;
104
+
105
+ &.bottom-right {
106
+ bottom: 0;
107
+ right: 0;
108
+ }
109
+
110
+ &.bottom-left {
111
+ bottom: 0;
112
+ left: 0;
113
+ }
114
+
115
+ &.top-right {
116
+ top: 0;
117
+ right: 0;
118
+ }
119
+
120
+ &.top-left {
121
+ top: 0;
122
+ left: 0;
123
+ }
124
+ }
125
+
126
+ :deep(.toast) {
127
+ position: relative;
128
+ display: grid;
129
+ grid-template-rows: auto minmax(0, 1fr);
130
+ inline-size: var(--toast-width);
131
+ max-inline-size: calc(100vw - var(--spacer) * 2);
132
+ padding: 0;
133
+ border: var(--border);
134
+ border-radius: var(--border-radius);
135
+ overflow: hidden;
136
+ font-family: var(--font-family);
137
+ font-size: var(--ui-font-size);
138
+
139
+ /* Entry animation */
140
+ opacity: 1;
141
+ translate: 0;
142
+ transition:
143
+ opacity var(--speed) ease,
144
+ translate var(--speed) ease;
145
+
146
+ @starting-style {
147
+ opacity: 0;
148
+ translate: 100% 0;
149
+ }
150
+
151
+ /* Exit animation */
152
+ &[data-state='closed'] {
153
+ opacity: 0;
154
+ translate: 25% 0;
155
+ transition:
156
+ opacity var(--speed-fast) ease,
157
+ translate var(--speed) ease;
158
+ }
159
+
160
+ /* Swipe */
161
+ &[data-swipe='move'] {
162
+ translate: var(--reka-toast-swipe-move-x) 0;
163
+ }
164
+
165
+ &[data-swipe='cancel'] {
166
+ translate: 0;
167
+ transition: translate var(--speed) ease;
168
+ }
169
+
170
+ &[data-swipe='end'] {
171
+ translate: var(--reka-toast-swipe-end-x) 0;
172
+ }
173
+
174
+ /* Variants */
175
+ &.info {
176
+ --border-color: var(--toast-info-border-color);
177
+ color: var(--toast-info-color);
178
+ background: var(--toast-info-background);
179
+ border-color: var(--toast-info-border-color);
180
+
181
+ .toast-close {
182
+ --button-tertiary-border-color: var(--toast-info-border-color);
183
+ --button-color-highlight: var(--toast-info-border-color);
184
+ }
185
+ }
186
+
187
+ &.success {
188
+ --border-color: var(--toast-success-border-color);
189
+ color: var(--toast-success-color);
190
+ background: var(--toast-success-background);
191
+ border-color: var(--toast-success-border-color);
192
+
193
+ .toast-close {
194
+ --button-tertiary-border-color: var(--toast-success-border-color);
195
+ --button-color-highlight: var(--toast-success-border-color);
196
+ }
197
+ }
198
+
199
+ &.error {
200
+ --border-color: var(--toast-error-border-color);
201
+ color: var(--toast-error-color);
202
+ background: var(--toast-error-background);
203
+ border-color: var(--toast-error-border-color);
204
+
205
+ .toast-close {
206
+ --button-tertiary-border-color: var(--toast-error-border-color);
207
+ --button-color-highlight: var(--toast-error-border-color);
208
+ }
209
+ }
210
+
211
+ .toast-title {
212
+ display: flex;
213
+ align-items: center;
214
+ block-size: calc(var(--spacer) * 2);
215
+ box-shadow: var(--border-shadow);
216
+ padding-inline-start: var(--ui-padding-inline);
217
+ padding-right: calc(var(--spacer) * 3);
218
+ font-family: var(--font-family);
219
+ font-size: var(--ui-font-size);
220
+ font-weight: normal;
221
+ text-transform: var(--ui-text-transform);
222
+ margin: 0;
223
+ }
224
+
225
+ > section {
226
+ padding: var(--ui-padding-inline);
227
+ display: grid;
228
+ gap: var(--spacer);
229
+ }
230
+
231
+ .toast-description {
232
+ color: var(--muted);
233
+ font-size: var(--font-sm);
234
+ }
235
+
236
+ .toast-close {
237
+ position: absolute !important;
238
+ top: 0;
239
+ right: 0;
240
+ box-shadow: var(--border-shadow) !important;
241
+ border-radius: 0 !important;
242
+ border-start-end-radius: var(--border-radius) !important;
243
+ }
244
+
245
+ .actions {
246
+ width: min-content;
247
+
248
+ > * {
249
+ white-space: nowrap;
250
+ }
251
+ }
252
+ }
253
+ }
254
+ </style>
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <TooltipProvider :delay-duration="delayDuration">
3
+ <TooltipRoot>
4
+ <TooltipTrigger as-child>
5
+ <slot name="trigger" />
6
+ </TooltipTrigger>
7
+
8
+ <TooltipPortal>
9
+ <TooltipContent
10
+ class="tooltip"
11
+ :class="props.class"
12
+ :side="side"
13
+ :align="align"
14
+ :side-offset="sideOffset"
15
+ :align-offset="alignOffset"
16
+ :avoid-collisions="avoidCollisions"
17
+ :collision-padding="collisionPadding"
18
+ >
19
+ <slot />
20
+ <TooltipArrow
21
+ v-if="arrow"
22
+ class="tooltip-arrow"
23
+ />
24
+ </TooltipContent>
25
+ </TooltipPortal>
26
+ </TooltipRoot>
27
+ </TooltipProvider>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import {
32
+ TooltipArrow,
33
+ TooltipContent,
34
+ TooltipPortal,
35
+ TooltipProvider,
36
+ TooltipRoot,
37
+ TooltipTrigger,
38
+ } from 'reka-ui'
39
+
40
+ const props = withDefaults(
41
+ defineProps<{
42
+ class?: string | string[] | Record<string, boolean>
43
+ side?: 'top' | 'right' | 'bottom' | 'left'
44
+ align?: 'start' | 'center' | 'end'
45
+ sideOffset?: number
46
+ alignOffset?: number
47
+ avoidCollisions?: boolean
48
+ collisionPadding?: number
49
+ arrow?: boolean
50
+ delayDuration?: number
51
+ }>(),
52
+ {
53
+ side: 'top',
54
+ align: 'center',
55
+ sideOffset: 4,
56
+ avoidCollisions: true,
57
+ collisionPadding: 8,
58
+ arrow: true,
59
+ delayDuration: 300,
60
+ },
61
+ )
62
+ </script>
63
+
64
+ <style>
65
+ @layer components {
66
+ .tooltip {
67
+ background: var(--tooltip-background);
68
+ color: var(--color);
69
+ border: var(--tooltip-border);
70
+ border-radius: var(--tooltip-border-radius);
71
+ padding: var(--tooltip-padding);
72
+ font-family: var(--font-family);
73
+ font-size: var(--ui-font-size);
74
+ z-index: var(--z-index-ui);
75
+ transform-origin: var(--reka-tooltip-content-transform-origin);
76
+
77
+ /* Entry/exit animations */
78
+ opacity: 1;
79
+ scale: 1;
80
+ transition:
81
+ opacity var(--speed) ease,
82
+ scale var(--speed) ease;
83
+
84
+ @starting-style {
85
+ opacity: 0;
86
+ scale: 0.95;
87
+ }
88
+
89
+ &[data-state='closed'] {
90
+ opacity: 0;
91
+ scale: 0.95;
92
+ }
93
+
94
+ .tooltip-arrow {
95
+ fill: var(--tooltip-arrow-fill);
96
+ stroke: var(--border-color);
97
+ }
98
+ }
99
+ }
100
+ </style>