@brickflow/ui 0.0.52 → 0.0.54
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/dist/module.json +1 -1
- package/dist/module.mjs +42 -26
- package/dist/runtime/brickflow-ui.d.ts +4 -0
- package/dist/runtime/components/Button/icons.demo.vue +1 -1
- package/dist/runtime/components/Button/index.vue +42 -8
- package/dist/runtime/components/Button/size.demo.vue +3 -1
- package/dist/runtime/components/Button/state.demo.vue +3 -2
- package/dist/runtime/components/Button/variants.demo.vue +3 -2
- package/dist/runtime/components/Icon/basic.demo.vue +1 -1
- package/dist/runtime/components/Icon/gallery.demo.vue +1 -1
- package/dist/runtime/components/Icon/index.vue +0 -5
- package/dist/runtime/components/Input/align.demo.vue +25 -0
- package/dist/runtime/components/Input/auto-width.demo.vue +20 -0
- package/dist/runtime/components/Input/index.vue +317 -0
- package/dist/runtime/components/Input/label.demo.vue +20 -0
- package/dist/runtime/components/Input/readonly.demo.vue +26 -0
- package/dist/runtime/components/Input/size.demo.vue +46 -0
- package/dist/runtime/components/Input/type.demo.vue +25 -0
- package/dist/runtime/components/Input/variants.demo.vue +35 -0
- package/dist/runtime/components/InputNumber/basic.demo.vue +37 -0
- package/dist/runtime/components/InputNumber/index.vue +126 -0
- package/dist/runtime/components/TransitionSize/index.vue +121 -0
- package/dist/runtime/config.d.ts +0 -13
- package/dist/runtime/icon-font.d.ts +0 -1
- package/dist/runtime/ui-config.d.ts +10 -0
- package/dist/runtime/ui.css +1 -1
- package/package.json +1 -2
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -265,7 +265,6 @@ const collectUiStylePathsFromCode = (code) => {
|
|
|
265
265
|
}
|
|
266
266
|
return paths.filter((path) => path.length > 0);
|
|
267
267
|
};
|
|
268
|
-
const collectUiStyleConfigPathsFromCode = (code, id) => collectUiStylePathsFromCode(code).map((path) => resolveUiStyleConfigPath(id, path));
|
|
269
268
|
const collectUiConfigValuePaths = (value, prefix) => {
|
|
270
269
|
if (typeof value === "string" || isBrickflowUiStyleReference(value)) {
|
|
271
270
|
return [prefix];
|
|
@@ -345,18 +344,31 @@ const collectUiConfigSchemaEntriesFromCode = (code, id) => {
|
|
|
345
344
|
}
|
|
346
345
|
return entries;
|
|
347
346
|
};
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
347
|
+
const collectUiStylePathsWithConfig = (code, id, config) => {
|
|
348
|
+
const [componentName] = resolveUiStyleConfigPath(id, []);
|
|
349
|
+
const componentConfig = componentName ? config[componentName] : void 0;
|
|
350
|
+
const configReferences = [];
|
|
351
|
+
const addConfigReferences = (value, configPath = []) => {
|
|
352
|
+
if (isBrickflowUiStyleReference(value)) {
|
|
353
|
+
const referencePath = getBrickflowUiStyleReferencePath(value);
|
|
354
|
+
configReferences.push(`UI_STYLE.${referencePath.join(".")}`);
|
|
355
|
+
if (configPath.length >= 2 && referencePath.length >= configPath.length) {
|
|
356
|
+
const prefixLength = referencePath.length - configPath.length + 1;
|
|
357
|
+
const requiredPath = [...referencePath.slice(0, prefixLength), ...configPath.slice(1)];
|
|
358
|
+
configReferences.push(`UI_STYLE.${requiredPath.join(".")}`);
|
|
359
|
+
}
|
|
360
|
+
} else if (typeof value !== "string") {
|
|
361
|
+
for (const [key, childValue] of Object.entries(value)) {
|
|
362
|
+
addConfigReferences(childValue, [...configPath, key]);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
if (componentConfig) {
|
|
367
|
+
addConfigReferences(componentConfig);
|
|
354
368
|
}
|
|
355
|
-
return
|
|
356
|
-
|
|
357
|
-
);
|
|
369
|
+
return collectUiStylePathsFromCode(`${configReferences.join("\n")}
|
|
370
|
+
${code}`);
|
|
358
371
|
};
|
|
359
|
-
const collectUiConfigStyleReferencePaths = (config) => Object.entries(config).flatMap(([key, value]) => collectUiConfigStyleReferencePathsFromValue(value, [key]));
|
|
360
372
|
const renderInterfaceDeclaration = (name, paths, objectType, leafType = "string") => {
|
|
361
373
|
const tree = createUiStylePathTree();
|
|
362
374
|
for (const path of paths) {
|
|
@@ -575,10 +587,7 @@ const toComponentStyleKey = (path) => {
|
|
|
575
587
|
const componentName = basename(dirname(path));
|
|
576
588
|
return `${componentName[0]?.toLowerCase()}${componentName.slice(1)}`;
|
|
577
589
|
};
|
|
578
|
-
const collectComponentStyleValues = (source, componentPath, config) =>
|
|
579
|
-
...collectUiStylePathsFromCode(source),
|
|
580
|
-
...collectUiConfigStyleReferencePaths(config.uiConfig).filter(([component]) => component === toComponentStyleKey(componentPath)).map((path) => path.slice(1))
|
|
581
|
-
].map((path) => path.join(".")).filter(Boolean).filter((path, index, paths) => paths.indexOf(path) === index).sort().map((path) => {
|
|
590
|
+
const collectComponentStyleValues = (source, componentPath, config) => collectUiStylePathsWithConfig(source, componentPath, config.uiConfig).map((path) => path.join(".")).filter(Boolean).filter((path, index, paths) => paths.indexOf(path) === index).sort().map((path) => {
|
|
582
591
|
const segments = path.split(".");
|
|
583
592
|
const componentValue = readObjectPath(config.uiStyles, [toComponentStyleKey(componentPath), ...segments]);
|
|
584
593
|
const globalValue = readObjectPath(config.uiStyles, segments);
|
|
@@ -736,15 +745,14 @@ const module$1 = defineNuxtModule({
|
|
|
736
745
|
}))
|
|
737
746
|
);
|
|
738
747
|
const uiConfig = (await loadUiConfig()).uiConfig;
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const configPaths =
|
|
745
|
-
({
|
|
748
|
+
const stylePaths = fileContents.map(({ content, file }) => ({
|
|
749
|
+
file,
|
|
750
|
+
paths: collectUiStylePathsWithConfig(content, file, uiConfig)
|
|
751
|
+
}));
|
|
752
|
+
const paths = stylePaths.flatMap(({ paths: componentPaths }) => componentPaths);
|
|
753
|
+
const configPaths = stylePaths.flatMap(
|
|
754
|
+
({ file, paths: componentPaths }) => componentPaths.map((path) => resolveUiStyleConfigPath(file, path))
|
|
746
755
|
);
|
|
747
|
-
configPaths.push(...uiConfigStyleReferencePaths);
|
|
748
756
|
const uiConfigPaths = fileContents.flatMap(
|
|
749
757
|
({ content, file }) => collectUiConfigPathsFromCode(content, file, uiConfig)
|
|
750
758
|
);
|
|
@@ -783,15 +791,14 @@ const module$1 = defineNuxtModule({
|
|
|
783
791
|
});
|
|
784
792
|
nuxt.hook("prepare:types", ({ references }) => {
|
|
785
793
|
references.push({ path: resolver.resolve("./runtime/config.d.ts") });
|
|
794
|
+
references.push({ path: resolver.resolve("./runtime/ui-config.d.ts") });
|
|
795
|
+
references.push({ path: resolver.resolve("./runtime/brickflow-ui.d.ts") });
|
|
786
796
|
});
|
|
787
797
|
const iconFontTemplate = addTemplate({
|
|
788
798
|
filename: "brickflow/brickflow-ui-icons.mjs",
|
|
789
799
|
getContents: async () => {
|
|
790
800
|
const iconNames = iconFont ? await getIconFontNames(iconFont.inputDir) : [];
|
|
791
801
|
return [
|
|
792
|
-
"import { uiConfig } from '#brickflow-ui-config'",
|
|
793
|
-
"",
|
|
794
|
-
"export const buttonConfig = uiConfig.button",
|
|
795
802
|
`export const iconNames = ${JSON.stringify(iconNames)}`,
|
|
796
803
|
`export const firstIconName = ${JSON.stringify(iconNames[0] ?? "")}`,
|
|
797
804
|
""
|
|
@@ -814,6 +821,14 @@ const module$1 = defineNuxtModule({
|
|
|
814
821
|
""
|
|
815
822
|
].join("\n")
|
|
816
823
|
});
|
|
824
|
+
const brickflowUiTemplate = addTemplate({
|
|
825
|
+
filename: "brickflow/brickflow-ui.mjs",
|
|
826
|
+
getContents: () => [
|
|
827
|
+
"export { UI_CONFIG, UI_STYLE, uiConfig, uiStyles } from './brickflow-ui-config.mjs'",
|
|
828
|
+
"export { firstIconName, iconNames } from './brickflow-ui-icons.mjs'",
|
|
829
|
+
""
|
|
830
|
+
].join("\n")
|
|
831
|
+
});
|
|
817
832
|
const uiOptionsTemplate = addTemplate({
|
|
818
833
|
filename: "brickflow/brickflow-ui-options.mjs",
|
|
819
834
|
getContents: () => `export const uiThemeEnabled = ${JSON.stringify(options.theme === true)}
|
|
@@ -909,6 +924,7 @@ const module$1 = defineNuxtModule({
|
|
|
909
924
|
].join("\n");
|
|
910
925
|
}
|
|
911
926
|
});
|
|
927
|
+
nuxt.options.alias["#brickflow-ui"] = brickflowUiTemplate.dst;
|
|
912
928
|
nuxt.options.alias["#brickflow-ui-config"] = uiConfigTemplate.dst;
|
|
913
929
|
nuxt.options.alias["#brickflow-ui-catalog"] = uiCatalogTemplate.dst;
|
|
914
930
|
nuxt.options.alias["#brickflow-ui-icons"] = iconFontTemplate.dst;
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
import Icon from '../Icon/index.vue'
|
|
23
23
|
import BaseLink from '../Link/index.vue'
|
|
24
|
+
import TransitionSize from '../TransitionSize/index.vue'
|
|
25
|
+
|
|
24
26
|
const props = withDefaults(
|
|
25
27
|
defineProps<{
|
|
26
28
|
block?: boolean
|
|
@@ -57,6 +59,8 @@
|
|
|
57
59
|
type ButtonSize = Extract<keyof typeof sizeClasses, string>
|
|
58
60
|
type ButtonVariant = Extract<keyof (typeof colorClasses)[ButtonColor], string>
|
|
59
61
|
|
|
62
|
+
const baseRef = shallowRef()
|
|
63
|
+
|
|
60
64
|
const isButton = computed(() => props.to === undefined)
|
|
61
65
|
const component = computed(() => (isButton.value ? 'button' : BaseLink))
|
|
62
66
|
const disabled = computed(() => props.disabled || props.loading)
|
|
@@ -82,6 +86,7 @@
|
|
|
82
86
|
<template>
|
|
83
87
|
<component
|
|
84
88
|
:is="component"
|
|
89
|
+
ref="baseRef"
|
|
85
90
|
v-bind="animationTapClass ? { onPointerupPassive: handleTap } : {}"
|
|
86
91
|
:to="props.to"
|
|
87
92
|
:target="isButton ? undefined : props.target"
|
|
@@ -97,7 +102,23 @@
|
|
|
97
102
|
}"
|
|
98
103
|
@contextmenu.prevent
|
|
99
104
|
>
|
|
105
|
+
<TransitionSize
|
|
106
|
+
v-if="!$slots.leading && !props.icon && !props.trailingIcon"
|
|
107
|
+
dimension="width"
|
|
108
|
+
:flexParent="baseRef?.$el || baseRef"
|
|
109
|
+
flexDirection="start"
|
|
110
|
+
>
|
|
111
|
+
<span v-if="props.loading">
|
|
112
|
+
<Icon
|
|
113
|
+
:name="UI_CONFIG.loadingIconName"
|
|
114
|
+
:class="[UI_STYLE.state.loading, sizeIconClasses[props.size]]"
|
|
115
|
+
aria-hidden="true"
|
|
116
|
+
/>
|
|
117
|
+
</span>
|
|
118
|
+
</TransitionSize>
|
|
119
|
+
|
|
100
120
|
<Transition
|
|
121
|
+
v-if="$slots.leading || props.icon"
|
|
101
122
|
:name="UI_CONFIG.loadingIconTransition"
|
|
102
123
|
mode="out-in"
|
|
103
124
|
>
|
|
@@ -109,7 +130,7 @@
|
|
|
109
130
|
/>
|
|
110
131
|
</span>
|
|
111
132
|
<span
|
|
112
|
-
v-else
|
|
133
|
+
v-else
|
|
113
134
|
:class="UI_STYLE.slot.leading"
|
|
114
135
|
>
|
|
115
136
|
<slot name="leading">
|
|
@@ -129,17 +150,30 @@
|
|
|
129
150
|
<slot />
|
|
130
151
|
</span>
|
|
131
152
|
|
|
132
|
-
<
|
|
153
|
+
<Transition
|
|
133
154
|
v-if="$slots.trailing || props.trailingIcon"
|
|
134
|
-
:
|
|
155
|
+
:name="UI_CONFIG.loadingIconTransition"
|
|
156
|
+
mode="out-in"
|
|
135
157
|
>
|
|
136
|
-
<
|
|
158
|
+
<span v-if="props.loading && !props.icon">
|
|
137
159
|
<Icon
|
|
138
|
-
:name="
|
|
139
|
-
:class="sizeIconClasses[props.size]"
|
|
160
|
+
:name="UI_CONFIG.loadingIconName"
|
|
161
|
+
:class="[UI_STYLE.state.loading, sizeIconClasses[props.size]]"
|
|
140
162
|
aria-hidden="true"
|
|
141
163
|
/>
|
|
142
|
-
</
|
|
143
|
-
|
|
164
|
+
</span>
|
|
165
|
+
<span
|
|
166
|
+
v-else
|
|
167
|
+
:class="UI_STYLE.slot.trailing"
|
|
168
|
+
>
|
|
169
|
+
<slot name="trailing">
|
|
170
|
+
<Icon
|
|
171
|
+
:name="props.trailingIcon!"
|
|
172
|
+
:class="sizeIconClasses[props.size]"
|
|
173
|
+
aria-hidden="true"
|
|
174
|
+
/>
|
|
175
|
+
</slot>
|
|
176
|
+
</span>
|
|
177
|
+
</Transition>
|
|
144
178
|
</component>
|
|
145
179
|
</template>
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<script lang="ts" setup>
|
|
8
|
-
import {
|
|
8
|
+
import { firstIconName, uiConfig } from '#brickflow-ui'
|
|
9
9
|
|
|
10
10
|
import Button from './index.vue'
|
|
11
|
+
|
|
12
|
+
const buttonConfig = uiConfig.button
|
|
11
13
|
</script>
|
|
12
14
|
|
|
13
15
|
<template>
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
<script lang="ts" setup>
|
|
8
8
|
import { ref } from 'vue'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { firstIconName, uiConfig } from '#brickflow-ui'
|
|
11
11
|
|
|
12
12
|
import Button from './index.vue'
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const buttonConfig = uiConfig.button
|
|
15
|
+
const firstColorClasses = buttonConfig.colorClasses[buttonConfig.colorDefault]
|
|
15
16
|
|
|
16
17
|
const stateActive = ref(true)
|
|
17
18
|
</script>
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<script lang="ts" setup>
|
|
8
|
-
import {
|
|
8
|
+
import { uiConfig } from '#brickflow-ui'
|
|
9
9
|
|
|
10
10
|
import Button from './index.vue'
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const buttonConfig = uiConfig.button
|
|
13
|
+
const firstColorClasses = buttonConfig.colorClasses[buttonConfig.colorDefault]
|
|
13
14
|
</script>
|
|
14
15
|
|
|
15
16
|
<template>
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
|
|
4
|
-
defineOptions({
|
|
5
|
-
inheritAttrs: false,
|
|
6
|
-
})
|
|
7
|
-
|
|
8
4
|
const props = withDefaults(
|
|
9
5
|
defineProps<{
|
|
10
6
|
name: string
|
|
@@ -17,7 +13,6 @@
|
|
|
17
13
|
|
|
18
14
|
<template>
|
|
19
15
|
<i
|
|
20
|
-
v-bind="$attrs"
|
|
21
16
|
:class="classes"
|
|
22
17
|
role="img"
|
|
23
18
|
/>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export const uiDemo = {
|
|
3
|
+
title: 'Alignment',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import { capitalize } from 'vue'
|
|
9
|
+
|
|
10
|
+
import Input from './index.vue'
|
|
11
|
+
|
|
12
|
+
const alignments = ['left', 'center', 'right'] as const
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div class="flex max-w-sm flex-col gap-3">
|
|
17
|
+
<Input
|
|
18
|
+
v-for="align in alignments"
|
|
19
|
+
:key="align"
|
|
20
|
+
:align="align"
|
|
21
|
+
:label="`${capitalize(align)} aligned`"
|
|
22
|
+
placeholder="Type here"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export const uiDemo = {
|
|
3
|
+
title: 'Auto Width',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import Input from './index.vue'
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div class="flex flex-col gap-3">
|
|
13
|
+
<Input
|
|
14
|
+
placeholder="Type here"
|
|
15
|
+
label="Auto Width"
|
|
16
|
+
autoWidth
|
|
17
|
+
class="max-w-sm"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
const UI_CONFIG = defineUiConfig<{
|
|
3
|
+
hidePasswordIconName: string
|
|
4
|
+
searchClearIconName: string
|
|
5
|
+
showPasswordIconName: string
|
|
6
|
+
sizeClasses: Record<
|
|
7
|
+
string,
|
|
8
|
+
{
|
|
9
|
+
description: string
|
|
10
|
+
field: string
|
|
11
|
+
fieldWrap: string
|
|
12
|
+
icon: string
|
|
13
|
+
label: string
|
|
14
|
+
}
|
|
15
|
+
>
|
|
16
|
+
sizeDefault: string
|
|
17
|
+
variantClasses: Record<
|
|
18
|
+
string,
|
|
19
|
+
{
|
|
20
|
+
description: string
|
|
21
|
+
field: string
|
|
22
|
+
fieldWrap: string
|
|
23
|
+
icon: string
|
|
24
|
+
label: string
|
|
25
|
+
}
|
|
26
|
+
>
|
|
27
|
+
variantDefault: string
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
export type InputConfig = typeof UI_CONFIG
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import { twMerge } from 'tailwind-merge'
|
|
35
|
+
import { computed, ref, shallowRef, useAttrs, useId, useSlots, watch } from 'vue'
|
|
36
|
+
|
|
37
|
+
import Icon from '../Icon/index.vue'
|
|
38
|
+
|
|
39
|
+
defineOptions({ inheritAttrs: false })
|
|
40
|
+
|
|
41
|
+
const props = withDefaults(
|
|
42
|
+
defineProps<{
|
|
43
|
+
align?: 'center' | 'left' | 'right'
|
|
44
|
+
autoWidth?: boolean
|
|
45
|
+
description?: string
|
|
46
|
+
disabled?: boolean
|
|
47
|
+
icon?: string
|
|
48
|
+
label?: string
|
|
49
|
+
onBlur?: (value: null | string, target: HTMLInputElement) => void
|
|
50
|
+
onFocus?: (value: null | string, target: HTMLInputElement | null) => void
|
|
51
|
+
onIconClick?: () => void
|
|
52
|
+
onTrailingIconClick?: () => void
|
|
53
|
+
onValueChange?: (value: null | string, target: HTMLInputElement) => void
|
|
54
|
+
onValueInput?: (value: null | string, target: HTMLInputElement | null) => void
|
|
55
|
+
placeholder?: string
|
|
56
|
+
readonly?: boolean
|
|
57
|
+
size?: InputSize
|
|
58
|
+
trailingIcon?: string
|
|
59
|
+
type?: 'decimal' | 'email' | 'numeric' | 'password' | 'search' | 'tel' | 'text' | 'url'
|
|
60
|
+
value?: null | string
|
|
61
|
+
variant?: InputVariant
|
|
62
|
+
}>(),
|
|
63
|
+
{
|
|
64
|
+
align: 'left',
|
|
65
|
+
autoWidth: false,
|
|
66
|
+
description: undefined,
|
|
67
|
+
disabled: false,
|
|
68
|
+
icon: undefined,
|
|
69
|
+
label: undefined,
|
|
70
|
+
onBlur: undefined,
|
|
71
|
+
onFocus: undefined,
|
|
72
|
+
onIconClick: undefined,
|
|
73
|
+
onTrailingIconClick: undefined,
|
|
74
|
+
onValueChange: undefined,
|
|
75
|
+
onValueInput: undefined,
|
|
76
|
+
placeholder: '',
|
|
77
|
+
readonly: false,
|
|
78
|
+
size: 'md',
|
|
79
|
+
trailingIcon: undefined,
|
|
80
|
+
type: 'text',
|
|
81
|
+
value: undefined,
|
|
82
|
+
variant: UI_CONFIG.variantDefault,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const model = shallowRef<null | string>('')
|
|
87
|
+
// const model = defineModel<null | number | string>({ default: '' })
|
|
88
|
+
|
|
89
|
+
const attrs = useAttrs()
|
|
90
|
+
const slots = useSlots()
|
|
91
|
+
const generatedId = useId()
|
|
92
|
+
const inputRef = ref<HTMLInputElement | null>(null)
|
|
93
|
+
const widthMeasureRef = ref<HTMLSpanElement | null>(null)
|
|
94
|
+
const measuredWidth = ref(0)
|
|
95
|
+
const hasFocus = ref(false)
|
|
96
|
+
const showPassword = ref(false)
|
|
97
|
+
|
|
98
|
+
const sizeClasses = UI_CONFIG.sizeClasses
|
|
99
|
+
const variantClasses = UI_CONFIG.variantClasses
|
|
100
|
+
type InputSize = Extract<keyof typeof sizeClasses, string>
|
|
101
|
+
type InputVariant = Extract<keyof typeof variantClasses, string>
|
|
102
|
+
const sizeClass = computed(() => sizeClasses[props.size])
|
|
103
|
+
const variantClass = computed(() => variantClasses[props.variant])
|
|
104
|
+
|
|
105
|
+
const fieldId = computed(() => String(attrs.id ?? generatedId))
|
|
106
|
+
const descriptionId = computed(() => `${fieldId.value}-description`)
|
|
107
|
+
const currentValue = computed(() => (props.value === undefined ? model.value : props.value))
|
|
108
|
+
const widthMeasureText = computed(() => currentValue.value || props.placeholder || ' ')
|
|
109
|
+
const isEmptyField = computed(() => {
|
|
110
|
+
return Boolean(currentValue.value)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const hasDescription = computed(() => Boolean(props.description || slots.description || slots.error))
|
|
114
|
+
const inputType = computed(() => {
|
|
115
|
+
return props.type === 'password' && !showPassword.value ? 'password' : 'text'
|
|
116
|
+
})
|
|
117
|
+
const classes = computed(() => [
|
|
118
|
+
UI_STYLE.base,
|
|
119
|
+
props.autoWidth && UI_STYLE.state.autoWidth,
|
|
120
|
+
props.disabled && UI_STYLE.state.disabled,
|
|
121
|
+
])
|
|
122
|
+
const fieldWrapClasses = computed(() => [
|
|
123
|
+
twMerge(UI_STYLE.fieldWrap, sizeClass.value.fieldWrap, variantClass.value.fieldWrap),
|
|
124
|
+
props.readonly && UI_STYLE.state.readonly,
|
|
125
|
+
])
|
|
126
|
+
const fieldClasses = computed(() => [
|
|
127
|
+
twMerge(UI_STYLE.field, sizeClass.value.field, variantClass.value.field),
|
|
128
|
+
props.align === 'center' && UI_STYLE.align.center,
|
|
129
|
+
props.align === 'right' && UI_STYLE.align.right,
|
|
130
|
+
props.autoWidth && UI_STYLE.state.autoWidthField,
|
|
131
|
+
])
|
|
132
|
+
const labelClasses = computed(() => twMerge(UI_STYLE.label, sizeClass.value.label, variantClass.value.label))
|
|
133
|
+
const descriptionClasses = computed(() =>
|
|
134
|
+
twMerge(UI_STYLE.description, sizeClass.value.description, variantClass.value.description),
|
|
135
|
+
)
|
|
136
|
+
const leadingIconClasses = computed(() =>
|
|
137
|
+
twMerge(
|
|
138
|
+
UI_STYLE.slot.leading,
|
|
139
|
+
sizeClass.value.icon,
|
|
140
|
+
variantClass.value.icon,
|
|
141
|
+
props.onIconClick && UI_STYLE.slot.hasAction,
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
const trailingIconClasses = computed(() =>
|
|
145
|
+
twMerge(
|
|
146
|
+
UI_STYLE.slot.trailing,
|
|
147
|
+
sizeClass.value.icon,
|
|
148
|
+
variantClass.value.icon,
|
|
149
|
+
props.onTrailingIconClick && UI_STYLE.slot.hasAction,
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
const fieldStyle = computed(() =>
|
|
153
|
+
props.autoWidth ? { flexShrink: 1, maxWidth: '100%', width: `${measuredWidth.value}px` } : { undefined },
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
watch(
|
|
157
|
+
[widthMeasureText, () => props.autoWidth, () => props.size, () => props.variant, widthMeasureRef],
|
|
158
|
+
() => {
|
|
159
|
+
if (props.autoWidth && widthMeasureRef.value) {
|
|
160
|
+
measuredWidth.value = Math.ceil(widthMeasureRef.value.getBoundingClientRect().width) + 2
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{ flush: 'post', immediate: true },
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
function clear(): void {
|
|
167
|
+
setValue('')
|
|
168
|
+
focus()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function focus(): void {
|
|
172
|
+
inputRef.value?.focus()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function handleBlur(event: FocusEvent): void {
|
|
176
|
+
hasFocus.value = false
|
|
177
|
+
props.onBlur?.(currentValue.value, event.target as HTMLInputElement)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function handleFocus(event: FocusEvent): void {
|
|
181
|
+
hasFocus.value = true
|
|
182
|
+
props.onFocus?.(currentValue.value, event.target as HTMLInputElement)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function onChange(event: Event): void {
|
|
186
|
+
const target = event.target as HTMLInputElement
|
|
187
|
+
props.onValueChange?.(target.value, target)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function onInput(event: Event): void {
|
|
191
|
+
setValue((event.target as HTMLInputElement).value)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function setValue(value: null | string): void {
|
|
195
|
+
model.value = value
|
|
196
|
+
props.onValueInput?.(value, inputRef.value)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
defineExpose({ clear, fieldId, focus, hasFocus, inputRef })
|
|
200
|
+
</script>
|
|
201
|
+
|
|
202
|
+
<template>
|
|
203
|
+
<div :class="[classes, $attrs.class]">
|
|
204
|
+
<label
|
|
205
|
+
v-if="props.label || $slots.label"
|
|
206
|
+
:for="fieldId"
|
|
207
|
+
:class="labelClasses"
|
|
208
|
+
>
|
|
209
|
+
<slot name="label">{{ props.label }}</slot>
|
|
210
|
+
</label>
|
|
211
|
+
|
|
212
|
+
<label
|
|
213
|
+
:for="fieldId"
|
|
214
|
+
:class="fieldWrapClasses"
|
|
215
|
+
:style="props.autoWidth ? { maxWidth: '100%', width: 'fit-content' } : undefined"
|
|
216
|
+
>
|
|
217
|
+
<span
|
|
218
|
+
v-if="props.autoWidth"
|
|
219
|
+
ref="widthMeasureRef"
|
|
220
|
+
aria-hidden="true"
|
|
221
|
+
:class="[fieldClasses, 'pointer-events-none invisible absolute w-max whitespace-pre']"
|
|
222
|
+
>
|
|
223
|
+
{{ widthMeasureText }}
|
|
224
|
+
</span>
|
|
225
|
+
<slot name="leading">
|
|
226
|
+
<component
|
|
227
|
+
:is="props.onIconClick ? 'button' : 'label'"
|
|
228
|
+
v-if="props.icon"
|
|
229
|
+
type="button"
|
|
230
|
+
:for="fieldId"
|
|
231
|
+
:disabled="props.disabled || !props.onIconClick"
|
|
232
|
+
:class="leadingIconClasses"
|
|
233
|
+
@click="props.onIconClick?.()"
|
|
234
|
+
>
|
|
235
|
+
<Icon
|
|
236
|
+
:name="props.icon"
|
|
237
|
+
aria-hidden="true"
|
|
238
|
+
/>
|
|
239
|
+
</component>
|
|
240
|
+
</slot>
|
|
241
|
+
|
|
242
|
+
<input
|
|
243
|
+
v-bind="{ ...$attrs, class: undefined, id: undefined }"
|
|
244
|
+
:id="fieldId"
|
|
245
|
+
ref="inputRef"
|
|
246
|
+
:value="currentValue"
|
|
247
|
+
:type="inputType"
|
|
248
|
+
:inputmode="props.type === 'password' ? undefined : props.type"
|
|
249
|
+
:disabled="props.disabled"
|
|
250
|
+
:readonly="props.readonly"
|
|
251
|
+
:placeholder="props.placeholder"
|
|
252
|
+
:aria-describedby="hasDescription ? descriptionId : undefined"
|
|
253
|
+
:class="fieldClasses"
|
|
254
|
+
:style="fieldStyle"
|
|
255
|
+
@input="onInput"
|
|
256
|
+
@change="onChange"
|
|
257
|
+
@focus="handleFocus"
|
|
258
|
+
@blur="handleBlur"
|
|
259
|
+
/>
|
|
260
|
+
|
|
261
|
+
<button
|
|
262
|
+
v-if="props.type === 'password'"
|
|
263
|
+
type="button"
|
|
264
|
+
:disabled="props.disabled"
|
|
265
|
+
:class="[trailingIconClasses, UI_STYLE.slot.hasAction]"
|
|
266
|
+
:aria-label="showPassword ? 'Hide password' : 'Show password'"
|
|
267
|
+
@click="showPassword = !showPassword"
|
|
268
|
+
>
|
|
269
|
+
<Icon
|
|
270
|
+
:name="showPassword ? UI_CONFIG.hidePasswordIconName : UI_CONFIG.showPasswordIconName"
|
|
271
|
+
aria-hidden="true"
|
|
272
|
+
/>
|
|
273
|
+
</button>
|
|
274
|
+
<button
|
|
275
|
+
v-else-if="props.type === 'search' && isEmptyField"
|
|
276
|
+
type="button"
|
|
277
|
+
:disabled="props.disabled || props.readonly"
|
|
278
|
+
:class="[trailingIconClasses, UI_STYLE.slot.hasAction]"
|
|
279
|
+
aria-label="Clear search"
|
|
280
|
+
@click="clear"
|
|
281
|
+
>
|
|
282
|
+
<Icon
|
|
283
|
+
:name="UI_CONFIG.searchClearIconName"
|
|
284
|
+
aria-hidden="true"
|
|
285
|
+
/>
|
|
286
|
+
</button>
|
|
287
|
+
<slot
|
|
288
|
+
v-else
|
|
289
|
+
name="trailing"
|
|
290
|
+
>
|
|
291
|
+
<component
|
|
292
|
+
:is="props.onTrailingIconClick ? 'button' : 'label'"
|
|
293
|
+
v-if="props.trailingIcon"
|
|
294
|
+
type="button"
|
|
295
|
+
:for="fieldId"
|
|
296
|
+
:disabled="props.disabled || !props.onTrailingIconClick"
|
|
297
|
+
:class="trailingIconClasses"
|
|
298
|
+
@click="props.onTrailingIconClick?.()"
|
|
299
|
+
>
|
|
300
|
+
<Icon
|
|
301
|
+
:name="props.trailingIcon"
|
|
302
|
+
aria-hidden="true"
|
|
303
|
+
/>
|
|
304
|
+
</component>
|
|
305
|
+
</slot>
|
|
306
|
+
</label>
|
|
307
|
+
|
|
308
|
+
<div
|
|
309
|
+
v-if="hasDescription"
|
|
310
|
+
:id="descriptionId"
|
|
311
|
+
:class="descriptionClasses"
|
|
312
|
+
>
|
|
313
|
+
<slot name="error" />
|
|
314
|
+
<slot name="description">{{ props.description }}</slot>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export const uiDemo = {
|
|
3
|
+
title: 'Label & Description, Placeholder',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import Input from './index.vue'
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div class="max-w-sm">
|
|
13
|
+
<Input
|
|
14
|
+
label="Email address"
|
|
15
|
+
description="We will use this address to contact you."
|
|
16
|
+
placeholder="name@example.com"
|
|
17
|
+
type="email"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export const uiDemo = {
|
|
3
|
+
title: 'Read-only & Slot',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import { firstIconName } from '#brickflow-ui'
|
|
9
|
+
|
|
10
|
+
import Input from './index.vue'
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<div class="max-w-sm">
|
|
15
|
+
<Input
|
|
16
|
+
label="Read-only value"
|
|
17
|
+
value="You can select this text"
|
|
18
|
+
:trailingIcon="firstIconName"
|
|
19
|
+
readonly
|
|
20
|
+
>
|
|
21
|
+
<template #trailing>
|
|
22
|
+
<div class="h-full bg-red-900 text-red-300">SLOT TEXT</div>
|
|
23
|
+
</template>
|
|
24
|
+
</Input>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export const uiDemo = {
|
|
3
|
+
title: 'Sizes & Icons',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import { firstIconName, uiConfig } from '#brickflow-ui'
|
|
9
|
+
|
|
10
|
+
import Button from '../Button/index.vue'
|
|
11
|
+
import Input from './index.vue'
|
|
12
|
+
|
|
13
|
+
const inputConfig = uiConfig.input
|
|
14
|
+
|
|
15
|
+
function copy(): void {
|
|
16
|
+
console.log('Click')
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div class="flex flex-col gap-3">
|
|
22
|
+
<div
|
|
23
|
+
v-for="(_, size) in inputConfig.sizeClasses"
|
|
24
|
+
:key="size"
|
|
25
|
+
class="grid gap-x-2 gap-y-1.5"
|
|
26
|
+
style="grid-template-columns: minmax(0, 24rem) max-content; grid-template-rows: repeat(3, auto)"
|
|
27
|
+
>
|
|
28
|
+
<Input
|
|
29
|
+
:size="size"
|
|
30
|
+
:icon="firstIconName"
|
|
31
|
+
:trailingIcon="firstIconName"
|
|
32
|
+
description="Some text description"
|
|
33
|
+
placeholder="Type here"
|
|
34
|
+
:label="`Size: ${size}`"
|
|
35
|
+
class="row-span-3 grid! grid-rows-subgrid"
|
|
36
|
+
@iconClick="copy"
|
|
37
|
+
/>
|
|
38
|
+
<Button
|
|
39
|
+
:size="size"
|
|
40
|
+
class="col-start-2 row-start-2 self-center"
|
|
41
|
+
>
|
|
42
|
+
Button
|
|
43
|
+
</Button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export const uiDemo = {
|
|
3
|
+
title: 'Input Types',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import { capitalize } from 'vue'
|
|
9
|
+
|
|
10
|
+
import Input from './index.vue'
|
|
11
|
+
|
|
12
|
+
const types = ['text', 'email', 'decimal', 'numeric', 'password', 'search', 'tel', 'url'] as const
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div class="flex max-w-sm flex-col gap-3">
|
|
17
|
+
<Input
|
|
18
|
+
v-for="type in types"
|
|
19
|
+
:key="type"
|
|
20
|
+
:type="type"
|
|
21
|
+
:label="capitalize(type)"
|
|
22
|
+
:placeholder="`Enter ${type}`"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export const uiDemo = {
|
|
3
|
+
title: 'Variants & Disabled',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import { uiConfig } from '#brickflow-ui'
|
|
9
|
+
|
|
10
|
+
import Input from './index.vue'
|
|
11
|
+
|
|
12
|
+
const inputConfig = uiConfig.input
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div class="flex max-w-sm flex-col gap-4">
|
|
17
|
+
<div
|
|
18
|
+
v-for="(_, variant) in inputConfig.variantClasses"
|
|
19
|
+
:key="variant"
|
|
20
|
+
class="flex flex-col gap-3"
|
|
21
|
+
>
|
|
22
|
+
<Input
|
|
23
|
+
:variant="variant"
|
|
24
|
+
:label="`Variant ${variant}`"
|
|
25
|
+
placeholder="Type here"
|
|
26
|
+
/>
|
|
27
|
+
<Input
|
|
28
|
+
:variant="variant"
|
|
29
|
+
:label="`Disabled ${variant}`"
|
|
30
|
+
value="Unavailable"
|
|
31
|
+
disabled
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export const uiDemo = {
|
|
3
|
+
title: 'Number & Bounds',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import { ref } from 'vue'
|
|
9
|
+
|
|
10
|
+
import InputNumber from './index.vue'
|
|
11
|
+
|
|
12
|
+
const quantity = ref<null | number>(null)
|
|
13
|
+
const price = ref<null | number>(25)
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="flex max-w-sm flex-col gap-4">
|
|
18
|
+
<InputNumber
|
|
19
|
+
v-model="quantity"
|
|
20
|
+
label="Quantity"
|
|
21
|
+
description="Use ↑ and ↓ to change by one."
|
|
22
|
+
:min="0"
|
|
23
|
+
:max="20"
|
|
24
|
+
block
|
|
25
|
+
/>
|
|
26
|
+
<InputNumber
|
|
27
|
+
v-model="price"
|
|
28
|
+
label="Price"
|
|
29
|
+
description="Changes in steps of five."
|
|
30
|
+
:min="0"
|
|
31
|
+
:max="100"
|
|
32
|
+
:step="5"
|
|
33
|
+
block
|
|
34
|
+
/>
|
|
35
|
+
<p class="text-sm text-gray-400">Quantity: {{ quantity ?? '—' }} · Price: {{ price ?? '—' }}</p>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import Input from '../Input/index.vue'
|
|
5
|
+
|
|
6
|
+
defineOptions({ inheritAttrs: false })
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
max?: number
|
|
11
|
+
min?: number
|
|
12
|
+
onValueChange?: (value: null | number, target: HTMLInputElement) => void
|
|
13
|
+
onValueInput?: (value: null | number, target: HTMLInputElement | null) => void
|
|
14
|
+
step?: number
|
|
15
|
+
value?: null | number
|
|
16
|
+
}>(),
|
|
17
|
+
{
|
|
18
|
+
max: undefined,
|
|
19
|
+
min: undefined,
|
|
20
|
+
onValueChange: undefined,
|
|
21
|
+
onValueInput: undefined,
|
|
22
|
+
step: 1,
|
|
23
|
+
value: undefined,
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const model = defineModel<null | number>({ default: null })
|
|
28
|
+
const rawValue = ref<null | string>(null)
|
|
29
|
+
const currentValue = computed(() => (props.value === undefined ? model.value : props.value))
|
|
30
|
+
const inputValue = computed(
|
|
31
|
+
() => rawValue.value ?? (currentValue.value === null ? '' : String(currentValue.value)),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
function clamp(value: number): number {
|
|
35
|
+
return Math.min(props.max ?? Infinity, Math.max(props.min ?? -Infinity, value))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleBlur(): void {
|
|
39
|
+
rawValue.value = null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function handleKeydown(event: KeyboardEvent): void {
|
|
43
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
event.preventDefault()
|
|
48
|
+
const next = (currentValue.value ?? 0) + (event.key === 'ArrowUp' ? props.step : -props.step)
|
|
49
|
+
const value = clamp(next)
|
|
50
|
+
rawValue.value = String(value)
|
|
51
|
+
model.value = value
|
|
52
|
+
props.onValueInput?.(value, event.target as HTMLInputElement)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleValueChange(value: null | string, target: HTMLInputElement): void {
|
|
56
|
+
const parsed = toNumber(value)
|
|
57
|
+
const bounded = parsed === null ? null : clamp(parsed)
|
|
58
|
+
rawValue.value = null
|
|
59
|
+
model.value = bounded
|
|
60
|
+
props.onValueChange?.(bounded, target)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleValueInput(value: null | string, target: HTMLInputElement | null): void {
|
|
64
|
+
rawValue.value = value ?? ''
|
|
65
|
+
const number = toNumber(value)
|
|
66
|
+
model.value = number
|
|
67
|
+
props.onValueInput?.(number, target)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function toNumber(value: null | string): null | number {
|
|
71
|
+
if (value === null || value.trim() === '') {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const number = Number(value.replace(',', '.'))
|
|
76
|
+
return Number.isFinite(number) ? number : null
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<template>
|
|
81
|
+
<Input
|
|
82
|
+
v-bind="{
|
|
83
|
+
...$attrs,
|
|
84
|
+
onValueInput: handleValueInput,
|
|
85
|
+
onValueChange: handleValueChange,
|
|
86
|
+
onBlur: handleBlur,
|
|
87
|
+
onKeydown: handleKeydown,
|
|
88
|
+
}"
|
|
89
|
+
:value="inputValue"
|
|
90
|
+
type="decimal"
|
|
91
|
+
:min="props.min"
|
|
92
|
+
:max="props.max"
|
|
93
|
+
:step="props.step"
|
|
94
|
+
>
|
|
95
|
+
<template
|
|
96
|
+
v-if="$slots.label"
|
|
97
|
+
#label
|
|
98
|
+
>
|
|
99
|
+
<slot name="label" />
|
|
100
|
+
</template>
|
|
101
|
+
<template
|
|
102
|
+
v-if="$slots.left"
|
|
103
|
+
#left
|
|
104
|
+
>
|
|
105
|
+
<slot name="left" />
|
|
106
|
+
</template>
|
|
107
|
+
<template
|
|
108
|
+
v-if="$slots.right"
|
|
109
|
+
#right
|
|
110
|
+
>
|
|
111
|
+
<slot name="right" />
|
|
112
|
+
</template>
|
|
113
|
+
<template
|
|
114
|
+
v-if="$slots.error"
|
|
115
|
+
#error
|
|
116
|
+
>
|
|
117
|
+
<slot name="error" />
|
|
118
|
+
</template>
|
|
119
|
+
<template
|
|
120
|
+
v-if="$slots.description"
|
|
121
|
+
#description
|
|
122
|
+
>
|
|
123
|
+
<slot name="description" />
|
|
124
|
+
</template>
|
|
125
|
+
</Input>
|
|
126
|
+
</template>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Transition
|
|
3
|
+
:name="props.name"
|
|
4
|
+
:mode="props.mode"
|
|
5
|
+
@beforeEnter="beforeStart"
|
|
6
|
+
@enter="start"
|
|
7
|
+
@afterEnter="afterEnter"
|
|
8
|
+
@beforeLeave="beforeLeave"
|
|
9
|
+
@leave="leave"
|
|
10
|
+
@afterLeave="afterLeave"
|
|
11
|
+
>
|
|
12
|
+
<slot></slot>
|
|
13
|
+
</Transition>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script lang="ts" setup>
|
|
17
|
+
import { computed, nextTick, ref } from 'vue'
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(
|
|
20
|
+
defineProps<{
|
|
21
|
+
dimension?: 'height' | 'width'
|
|
22
|
+
duration?: number
|
|
23
|
+
expandClasses?: ''
|
|
24
|
+
flexDirection?: 'end' | 'start'
|
|
25
|
+
flexParent?: Element
|
|
26
|
+
mode?: 'default' | 'in-out' | 'out-in'
|
|
27
|
+
name?: string
|
|
28
|
+
}>(),
|
|
29
|
+
{
|
|
30
|
+
dimension: 'height',
|
|
31
|
+
duration: 300,
|
|
32
|
+
expandClasses: '',
|
|
33
|
+
flexDirection: 'start',
|
|
34
|
+
flexParent: undefined,
|
|
35
|
+
gap: 0,
|
|
36
|
+
mode: undefined,
|
|
37
|
+
name: undefined,
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const gap = ref(0)
|
|
42
|
+
|
|
43
|
+
const sizeName = computed(() => props.dimension)
|
|
44
|
+
const marginName = computed(() => {
|
|
45
|
+
const xDirection = props.flexDirection === 'start' ? 'marginLeft' : 'marginRight'
|
|
46
|
+
const yDirection = props.flexDirection === 'start' ? 'marginTop' : 'marginBottom'
|
|
47
|
+
return props.dimension === 'height' ? yDirection : xDirection
|
|
48
|
+
})
|
|
49
|
+
const scrollName = computed(() => (props.dimension === 'height' ? 'scrollHeight' : 'scrollWidth'))
|
|
50
|
+
const expandClass = computed(() => {
|
|
51
|
+
return (props.expandClasses || '!transition-all !ease-in-out !overflow-hidden will-change-transform').split(
|
|
52
|
+
' ',
|
|
53
|
+
)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const beforeStart = (event: Element): void => {
|
|
57
|
+
const el = event as HTMLElement
|
|
58
|
+
shrink(el)
|
|
59
|
+
}
|
|
60
|
+
const start = (event: Element): void => {
|
|
61
|
+
const el = event as HTMLElement
|
|
62
|
+
el.dataset.oldOverflow = el.style.overflow
|
|
63
|
+
|
|
64
|
+
// Wait until component mounted to get proper height
|
|
65
|
+
nextTick(() => {
|
|
66
|
+
if (el[scrollName.value] === 0) {
|
|
67
|
+
el.style[sizeName.value] = ''
|
|
68
|
+
} else {
|
|
69
|
+
el.style[sizeName.value] = `${el[scrollName.value]}px`
|
|
70
|
+
el.style.padding = ''
|
|
71
|
+
if (props.flexParent) {
|
|
72
|
+
el.style[marginName.value] = ''
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
el.style.overflow = 'hidden'
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
const afterEnter = (event: Element): void => {
|
|
79
|
+
const el = event as HTMLElement
|
|
80
|
+
release(el)
|
|
81
|
+
}
|
|
82
|
+
const beforeLeave = (event: Element): void => {
|
|
83
|
+
const el = event as HTMLElement
|
|
84
|
+
const computedStyle = getComputedStyle(el)
|
|
85
|
+
el.dataset.oldOverflow = computedStyle.overflow
|
|
86
|
+
|
|
87
|
+
el.style[sizeName.value] = `${el[scrollName.value]}px`
|
|
88
|
+
if (props.flexParent) {
|
|
89
|
+
el.style[marginName.value] = ''
|
|
90
|
+
}
|
|
91
|
+
el.style.overflow = 'hidden'
|
|
92
|
+
}
|
|
93
|
+
const leave = (event: Element): void => {
|
|
94
|
+
const el = event as HTMLElement
|
|
95
|
+
if (el[scrollName.value] !== 0) {
|
|
96
|
+
shrink(el)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const afterLeave = (event: Element): void => {
|
|
100
|
+
const el = event as HTMLElement
|
|
101
|
+
release(el)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function release(el: HTMLElement): void {
|
|
105
|
+
el.classList.add(...expandClass.value)
|
|
106
|
+
el.style.transitionDuration = ''
|
|
107
|
+
el.style[sizeName.value] = ''
|
|
108
|
+
el.style[marginName.value] = ''
|
|
109
|
+
el.style.overflow = String(el.dataset.oldOverflow)
|
|
110
|
+
}
|
|
111
|
+
function shrink(el: HTMLElement): void {
|
|
112
|
+
el.classList.add(...expandClass.value)
|
|
113
|
+
el.style.transitionDuration = `${props.duration}ms`
|
|
114
|
+
el.style[sizeName.value] = '0'
|
|
115
|
+
el.style.padding = '0'
|
|
116
|
+
if (props.flexParent) {
|
|
117
|
+
gap.value = parseInt(window.getComputedStyle(props.flexParent)?.gap) || 0
|
|
118
|
+
el.style[marginName.value] = `-${gap.value}px`
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
</script>
|
package/dist/runtime/config.d.ts
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
export {}
|
|
2
2
|
|
|
3
|
-
declare module '#brickflow-ui-config' {
|
|
4
|
-
import type { BrickflowUiConfig } from './tailwind'
|
|
5
|
-
|
|
6
|
-
export const UI_CONFIG: BrickflowUiConfigType
|
|
7
|
-
export const UI_STYLE: BrickflowUiStyleType
|
|
8
|
-
export const uiConfig: BrickflowUiConfig['uiConfig']
|
|
9
|
-
export const uiStyles: BrickflowUiConfig['uiStyles']
|
|
10
|
-
|
|
11
|
-
const config: BrickflowUiConfig
|
|
12
|
-
|
|
13
|
-
export default config
|
|
14
|
-
}
|
|
15
|
-
|
|
16
3
|
declare global {
|
|
17
4
|
type BrickflowUiConfigFor<TExpected extends BrickflowUiConfigObject> = {
|
|
18
5
|
[TKey in keyof BrickflowUiConfigLiteralPaths]: BrickflowUiConfigLiteralPaths[TKey] extends TExpected
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare module '#brickflow-ui-config' {
|
|
2
|
+
export const UI_CONFIG: BrickflowUiConfigType
|
|
3
|
+
export const UI_STYLE: BrickflowUiStyleType
|
|
4
|
+
export const uiConfig: BrickflowUiConfigLiteralPaths
|
|
5
|
+
export const uiStyles: BrickflowUiStyleType
|
|
6
|
+
|
|
7
|
+
const config: { uiConfig: typeof uiConfig; uiStyles: typeof uiStyles }
|
|
8
|
+
|
|
9
|
+
export default config
|
|
10
|
+
}
|
package/dist/runtime/ui.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@import "./ui-breakpoint.css";@media (width < 1440px){.dkHidden{display:none!important}}@media (width < 1024px),(width >= 1440px){.lpHidden{display:none!important}}@media (width < 768px),(width >= 1024px){.tbHidden{display:none!important}}@media (width < 480px),(width >= 768px){.mbHidden{display:none!important}}@media (width >= 480px){.msHidden{display:none!important}}
|
|
1
|
+
@import "./ui-breakpoint.css";@source "./components";@media (width < 1440px){.dkHidden{display:none!important}}@media (width < 1024px),(width >= 1440px){.lpHidden{display:none!important}}@media (width < 768px),(width >= 1024px){.tbHidden{display:none!important}}@media (width < 480px),(width >= 768px){.mbHidden{display:none!important}}@media (width >= 480px){.msHidden{display:none!important}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brickflow/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.54",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "brickflow UI Nuxt module.",
|
|
6
6
|
"files": [
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"import": "./dist/runtime/tailwind.js"
|
|
18
18
|
},
|
|
19
19
|
"./ui.css": {
|
|
20
|
-
"development": "./src/runtime/ui.css",
|
|
21
20
|
"default": "./dist/runtime/ui.css"
|
|
22
21
|
}
|
|
23
22
|
},
|