primevue-mirror/apps/showcase/components/layout/designer/editor/DesignTokenField.vue

232 lines
8.2 KiB
Vue
Raw Normal View History

2024-11-12 09:18:07 +00:00
<template>
<div class="group">
<div class="flex justify-between justify-items-center">
2025-01-03 16:56:06 +00:00
<label :for="inputId" class="text-xs text-zinc-700 dark:text-white/70 block capitalize text-ellipsis overflow-hidden w-full whitespace-nowrap mb-px" :title="label">{{ label }}</label>
2025-01-04 10:31:50 +00:00
<button v-if="switchable" type="button" @click="transfer">
<i class="pi pi-sort-alt !text-xs text-zinc-500 dark:text-white/50 hidden group-hover:block animate-fadein" title="Transfer between color scheme and common"></i>
</button>
</div>
2024-11-13 16:09:13 +00:00
<div :id="id" class="relative">
<AutoComplete
:modelValue="modelValue"
2024-11-13 16:17:03 +00:00
@input="onInput"
:inputId="inputId"
:suggestions="items"
@complete="search"
unstyled
optionLabel="label"
:showEmptyMessage="false"
:pt="{
pcInputText: {
root: [
'border text-zinc-950 dark:text-white rounded-lg py-2 px-2 w-full text-xs',
{ 'pr-6': type === 'color', 'border-red-500 dark:border-red-400 bg-red-50 dark:bg-red-500/30': invalid, 'border-surface-300 dark:border-surface-600': !invalid }
]
},
overlay: 'border border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-950 shadow-2 rounded-md',
listContainer: 'max-h-40 overflow-auto',
2024-11-14 08:18:07 +00:00
list: 'm-0 py-2 px-2 list-none',
2024-11-14 08:58:02 +00:00
loader: 'hidden',
2024-11-14 08:18:07 +00:00
option: 'cursor-pointer py-1 text-sm text-surface-700 dark:text-white/80 data-[p-focus=true]:bg-surface-100 data-[p-focus=true]:dark:bg-surface-800 rounded-md'
}"
@option-select="onOptionSelect"
>
<template #option="slotProps">
2024-11-14 08:18:07 +00:00
<div v-tooltip.left="slotProps.option.value" class="flex items-center justify-between gap-4 px-2">
<span>{{ slotProps.option.token }}</span>
<div v-if="slotProps.option.isColor" class="border border-surface-200 dark:border-surface-700 w-4 h-4 rounded-full" :style="{ backgroundColor: slotProps.option.variable }"></div>
<div v-else class="text-xs max-w-16 text-ellipsis whitespace-nowrap overflow-hidden">
{{ slotProps.option.value }}
</div>
</div>
</template>
</AutoComplete>
<div v-if="type === 'color'" class="absolute right-[4px] top-1/2 -mt-2 w-4 h-4 rounded-md border border-surface-300 dark:border-surface-600" :style="{ backgroundColor: previewColor }"></div>
2024-11-13 16:09:13 +00:00
</div>
2024-11-12 09:18:07 +00:00
</div>
</template>
<script>
2024-11-13 16:09:13 +00:00
import { UniqueComponentId } from '@primevue/core/utils';
2024-11-12 09:18:07 +00:00
import { $dt } from '@primevue/themes';
export default {
emits: ['update:modelValue'],
props: {
label: {
type: String,
default: undefined
},
type: {
type: String,
default: undefined
},
modelValue: {
type: null,
default: undefined
},
componentKey: {
type: null,
default: null
},
path: {
type: String,
default: undefined
2025-01-04 10:31:50 +00:00
},
switchable: {
type: Boolean,
default: false
2024-11-12 09:18:07 +00:00
}
},
2024-11-12 13:44:52 +00:00
data() {
return {
2024-11-13 16:09:13 +00:00
id: null,
items: null
2024-11-12 13:44:52 +00:00
};
},
2024-11-14 09:57:46 +00:00
created() {
2024-11-13 16:09:13 +00:00
this.id = 'dt_field_' + UniqueComponentId();
},
2024-11-12 09:18:07 +00:00
methods: {
onOptionSelect(event) {
2024-11-14 09:57:46 +00:00
this.$emit('update:modelValue', event.value.label);
event.originalEvent.stopPropagation();
},
2024-11-12 09:18:07 +00:00
onInput(event) {
this.$emit('update:modelValue', event.target.value);
},
search(event) {
const query = event.query;
2024-11-12 13:44:52 +00:00
if (query.startsWith('{')) {
2024-11-22 10:02:06 +00:00
this.items = this.$appState.designer.acTokens.filter((t) => t.label.startsWith(query));
2024-11-12 13:44:52 +00:00
} else {
2024-11-14 08:58:02 +00:00
this.items = [];
2024-11-12 13:44:52 +00:00
}
},
getPathFromColorScheme(colorScheme) {
const lightPrefix = 'light.';
const darkPrefix = 'dark.';
if (colorScheme.startsWith(lightPrefix)) {
return colorScheme.slice(lightPrefix.length);
} else if (colorScheme.startsWith(darkPrefix)) {
return colorScheme.slice(darkPrefix.length);
}
return colorScheme;
},
transfer(event) {
let tokens = this.$appState.designer.theme.preset.components[this.componentKey];
const colorSchemePrefix = 'colorScheme.';
if (this.path.startsWith(colorSchemePrefix)) {
let tokenPath = this.getPathFromColorScheme(this.path.slice(colorSchemePrefix.length));
2025-01-13 07:29:27 +00:00
this.set(tokens, tokenPath, this.modelValue);
this.unset(tokens, 'colorScheme.light.' + tokenPath);
this.unset(tokens, 'colorScheme.dark.' + tokenPath);
} else {
2025-01-13 07:29:27 +00:00
this.set(tokens, 'colorScheme.light.' + this.path, this.modelValue);
this.set(tokens, 'colorScheme.dark.' + this.path, this.modelValue);
this.unset(tokens, this.path);
}
this.removeEmptyProps(tokens);
event.preventDefault();
},
removeEmptyProps(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (typeof value === 'object' && value !== null) {
this.removeEmptyProps(value);
}
if (typeof value === 'object' && value !== null && Object.keys(value).length === 0) {
delete obj[key];
}
}
}
return obj;
2025-01-13 07:29:27 +00:00
},
set(obj, path, value) {
if (Object(obj) !== obj) return obj;
const pathArray = Array.isArray(path) ? path : path.toString().match(/[^.[\]]+/g) || [];
pathArray.reduce((acc, key, i) => {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
return acc;
}
if (i === pathArray.length - 1) {
acc[key] = value;
return value;
}
acc[key] = Object(acc[key]) === acc[key] ? acc[key] : {};
return acc[key];
}, obj);
return obj;
},
unset(obj, path) {
if (Object(obj) !== obj) return false;
const pathArray = Array.isArray(path) ? path : path.toString().match(/[^.[\]]+/g) || [];
if (pathArray.length === 0) return false;
if (pathArray.includes('__proto__') || pathArray.includes('constructor') || pathArray.includes('prototype')) {
return false;
}
let current = obj;
const length = pathArray.length;
for (let i = 0; i < length - 1; i++) {
const key = pathArray[i];
if (current[key] == null) {
return false;
}
current = current[key];
}
const lastKey = pathArray[length - 1];
if (!(lastKey in current)) {
return false;
}
delete current[lastKey];
return true;
2024-11-12 09:18:07 +00:00
}
},
computed: {
previewColor() {
2024-11-14 09:57:46 +00:00
const tokenValue = typeof this.modelValue === 'object' ? this.modelValue.label : this.modelValue;
return tokenValue && tokenValue.trim().length && tokenValue.startsWith('{') && tokenValue.endsWith('}') ? $dt(tokenValue).variable : tokenValue;
2024-11-13 16:09:13 +00:00
},
inputId() {
return this.id + '_input';
},
invalid() {
return this.modelValue == null || this.modelValue.trim().length === 0 || this.modelValue.startsWith(this.componentKey);
2024-11-12 09:18:07 +00:00
}
}
};
</script>