Refactored AppDesigner
parent
96abb53576
commit
b3f30c3ab7
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<Drawer
|
<Drawer
|
||||||
v-model:visible="$appState.designer.active"
|
v-model:visible="$appState.designer.active"
|
||||||
header="Theme Designer"
|
|
||||||
position="right"
|
position="right"
|
||||||
class="designer !w-screen md:!w-[48rem]"
|
class="designer !w-screen md:!w-[48rem]"
|
||||||
:modal="false"
|
:modal="false"
|
||||||
|
@ -14,142 +13,19 @@
|
||||||
footer: '!p-5'
|
footer: '!p-5'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<Tabs v-model:value="$appState.designer.activeTab" :lazy="deferredTabs">
|
<template #header>
|
||||||
<TabList>
|
<div class="flex items-center gap-2">
|
||||||
<Tab value="0">Base</Tab>
|
<Button v-if="$appState.designer.activeView !== 'dashboard'" variant="text" severity="secondary" rounded type="button" icon="pi pi-chevron-left" @click="openDashboard" />
|
||||||
<Tab value="1">Primitive</Tab>
|
<span class="font-bold text-xl">{{ viewTitle }}</span>
|
||||||
<Tab value="2">Semantic</Tab>
|
</div>
|
||||||
<Tab value="3">Component</Tab>
|
</template>
|
||||||
<Tab value="4">Custom</Tab>
|
|
||||||
</TabList>
|
|
||||||
<TabPanels class="!px-0">
|
|
||||||
<TabPanel value="0">
|
|
||||||
<div class="text-lg font-semibold mb-2">Choose a Theme to Get Started</div>
|
|
||||||
<span class="block text-muted-color leading-6 mb-4">Begin by selecting a built-in theme as a foundation, continue editing your current theme, or import a Figma tokens file.</span>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex flex-col gap-4 border border-surface-200 dark:border-surface-700 rounded-md p-4">
|
|
||||||
<span class="font-semibold">Base Theme</span>
|
|
||||||
<span class="text-muted-color">Variety of built-in themes with distinct characteristics.</span>
|
|
||||||
<SelectButton v-model="$appState.preset" @update:modelValue="onPresetChange" :options="presetOptions" optionLabel="label" optionValue="value" :allowEmpty="false" />
|
|
||||||
</div>
|
|
||||||
<Divider>OR</Divider>
|
|
||||||
<div class="flex flex-col gap-4 border border-surface-200 dark:border-surface-700 rounded-md p-4 items-start">
|
|
||||||
<span class="font-semibold">Load Theme</span>
|
|
||||||
<span class="text-muted-color">Continue editing the theme files stored locally.</span>
|
|
||||||
<Button label="Restore from local storage" class="!px-3 !py-2" severity="secondary" @click="loadFromLocalStorage" />
|
|
||||||
</div>
|
|
||||||
<Divider>OR</Divider>
|
|
||||||
<div class="flex flex-col gap-4 border border-surface-200 dark:border-surface-700 rounded-md p-4">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<span class="font-semibold">Import Figma Tokens </span>
|
|
||||||
<Tag value="PRO" severity="contrast"></Tag>
|
|
||||||
</div>
|
|
||||||
<span class="text-muted-color leading-6">Export the token studio json file and import to the Visual Editor. This feature is currently under development.</span>
|
|
||||||
<FileUpload mode="basic" disabled pt:root:class="!justify-start" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value="1">
|
|
||||||
<div>
|
|
||||||
<form @keydown="onKeyDown" class="flex flex-col gap-3">
|
|
||||||
<DesignBorderRadius />
|
|
||||||
<DesignColors />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value="2">
|
|
||||||
<Accordion :value="['0', '1']" multiple>
|
|
||||||
<AccordionPanel value="0">
|
|
||||||
<AccordionHeader>Common</AccordionHeader>
|
|
||||||
<AccordionContent>
|
|
||||||
<div>
|
|
||||||
<form @keydown="onKeyDown" class="flex flex-col gap-3">
|
|
||||||
<DesignGeneral />
|
|
||||||
<DesignFormField />
|
|
||||||
<DesignList />
|
|
||||||
<DesignNavigation />
|
|
||||||
<DesignOverlay />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionPanel>
|
|
||||||
|
|
||||||
<AccordionPanel value="1">
|
<DesignDashboard v-if="$appState.designer.activeView === 'dashboard'" />
|
||||||
<AccordionHeader>Color Scheme</AccordionHeader>
|
<DesignCreateTheme v-else-if="$appState.designer.activeView === 'create_theme'" />
|
||||||
<AccordionContent>
|
<DesignEditor v-else-if="$appState.designer.activeView === 'editor'" :deferred="deferredTabs" />
|
||||||
<Tabs value="cs-0">
|
|
||||||
<TabList>
|
|
||||||
<Tab value="cs-0">Light</Tab>
|
|
||||||
<Tab value="cs-1">Dark</Tab>
|
|
||||||
</TabList>
|
|
||||||
<TabPanels class="!px-0">
|
|
||||||
<TabPanel value="cs-0">
|
|
||||||
<form @keydown="onKeyDown">
|
|
||||||
<DesignCS :value="$appState.designer.preset.semantic.colorScheme.light" />
|
|
||||||
</form>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value="cs-1">
|
|
||||||
<form @keydown="onKeyDown">
|
|
||||||
<DesignCS :value="$appState.designer.preset.semantic.colorScheme.dark" />
|
|
||||||
</form>
|
|
||||||
</TabPanel>
|
|
||||||
</TabPanels>
|
|
||||||
</Tabs>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionPanel>
|
|
||||||
</Accordion>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value="3">
|
|
||||||
<form @keydown="onKeyDown">
|
|
||||||
<DesignComponent />
|
|
||||||
</form>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value="4">
|
|
||||||
<span class="leading-6 text-muted-color">Extend the theming system with your own design tokens e.g. <span class="font-medium">accent.color</span>. Do not use curly braces in the name field.</span>
|
|
||||||
<ul class="flex flex-col gap-4 list-none p-0 mx-0 my-4">
|
|
||||||
<li v-for="(token, index) of $appState.designer.customTokens" :key="index" class="first:border-t border-b border-surface-200 dark:border-surface-300 py-2">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<label class="flex items-center gap-2 flex-auto">
|
|
||||||
<span class="text-sm">Name</span>
|
|
||||||
<input v-model="token['name']" type="text" class="border border-surface-300 dark:border-surface-600 rounded-lg py-2 px-2 w-full" />
|
|
||||||
</label>
|
|
||||||
<label class="flex items-center gap-2 flex-auto">
|
|
||||||
<span class="text-sm">Value</span>
|
|
||||||
<input v-model="token['value']" type="text" class="border border-surface-300 dark:border-surface-600 rounded-lg py-2 px-2 w-full" />
|
|
||||||
</label>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="removeToken(index)"
|
|
||||||
class="cursor-pointer inline-flex items-center justify-center ms-auto w-8 h-8 rounded-full bg-red-50 hover:bg-red-100 text-red-600 dark:bg-red-400/10 dark:hover:bg-red-400/20 dark:text-red-400 transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-red-600 focus:dark:outline-red-400"
|
|
||||||
>
|
|
||||||
<i class="pi pi-times" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="addToken"
|
|
||||||
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white"
|
|
||||||
>
|
|
||||||
Add New
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="$appState.designer.customTokens.length"
|
|
||||||
type="button"
|
|
||||||
@click="saveTokens"
|
|
||||||
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
</TabPanels>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex justify-between gap-2">
|
<div v-if="$appState.designer.active.view === 'editor'" class="flex justify-between gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="download"
|
@click="download"
|
||||||
|
@ -159,9 +35,9 @@
|
||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="$appState.designer.activeTab !== '0'"
|
|
||||||
type="button"
|
type="button"
|
||||||
@click="apply"
|
@click="apply"
|
||||||
|
icon="pi pi-download"
|
||||||
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white"
|
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white"
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
|
@ -172,172 +48,40 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import EventBus from '@/app/AppEventBus';
|
import { $dt } from '@primevue/themes';
|
||||||
import { $dt, updatePreset, usePreset } from '@primevue/themes';
|
|
||||||
import Aura from '@primevue/themes/aura';
|
|
||||||
import Lara from '@primevue/themes/lara';
|
|
||||||
import Material from '@primevue/themes/material';
|
|
||||||
import Nora from '@primevue/themes/nora';
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
const presets = {
|
|
||||||
Aura,
|
|
||||||
Material,
|
|
||||||
Lara,
|
|
||||||
Nora
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
$preset: computed(() => this.$appState.designer.preset)
|
designerUtils: {
|
||||||
|
refreshACTokens: this.refreshACTokens,
|
||||||
|
saveTheme: this.saveTheme
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
deferredTabs: true,
|
deferredTabs: true
|
||||||
presetOptions: [
|
|
||||||
{ label: 'Aura', value: 'Aura' },
|
|
||||||
{ label: 'Material', value: 'Material' },
|
|
||||||
{ label: 'Lara', value: 'Lara' },
|
|
||||||
{ label: 'Nora', value: 'Nora' }
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
if (!this.$appState.designer.preset) {
|
|
||||||
this.$appState.designer.preset = presets.Aura;
|
|
||||||
this.replaceColorPalette();
|
|
||||||
this.generateACTokens(null, this.$appState.designer.preset);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
apply() {
|
onShow() {
|
||||||
this.saveTheme();
|
this.deferredTabs = false;
|
||||||
updatePreset(this.$appState.designer.preset);
|
},
|
||||||
EventBus.emit('theme-palette-change');
|
onHide() {
|
||||||
|
this.deferredTabs = true;
|
||||||
},
|
},
|
||||||
download() {
|
download() {
|
||||||
const basePreset = this.$appState.preset;
|
// TODO: Fetch from endpoint
|
||||||
const theme = JSON.stringify(this.$appState.designer.preset, null, 4).replace(/"([^"]+)":/g, '$1:');
|
|
||||||
const textContent = `import { createApp } from "vue";
|
|
||||||
import PrimeVue from "primevue/config";
|
|
||||||
import ${basePreset} from "@primevue/themes/${basePreset.toLowerCase()}";
|
|
||||||
import { definePreset } from "@primevue/themes";
|
|
||||||
|
|
||||||
const app = createApp(App);
|
|
||||||
|
|
||||||
const MyPreset = definePreset(${basePreset}, ${theme});
|
|
||||||
|
|
||||||
app.use(PrimeVue, {
|
|
||||||
theme: {
|
|
||||||
preset: MyPreset
|
|
||||||
},
|
|
||||||
});
|
|
||||||
app.mount("#app");
|
|
||||||
`;
|
|
||||||
const blob = new Blob([textContent], { type: 'text/plain' });
|
|
||||||
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
const a = document.createElement('a');
|
|
||||||
|
|
||||||
a.href = url;
|
|
||||||
a.download = 'mytheme.js';
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
},
|
},
|
||||||
onPresetChange(value) {
|
apply() {
|
||||||
this.$appState.preset = value;
|
saveTheme();
|
||||||
const newPreset = presets[value];
|
updatePreset(this.$appState.designer.theme.preset);
|
||||||
|
EventBus.emit('theme-palette-change');
|
||||||
if (value === 'Material') {
|
|
||||||
document.body.classList.add('material');
|
|
||||||
this.$primevue.config.ripple = true;
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('material');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$appState.designer.preset = newPreset;
|
|
||||||
|
|
||||||
this.replaceColorPalette();
|
|
||||||
|
|
||||||
usePreset(this.$appState.designer.preset);
|
|
||||||
},
|
},
|
||||||
saveTheme() {
|
saveTheme() {
|
||||||
const localState = {
|
// TODO: Save to DB or Local Storage
|
||||||
themes: {
|
console.log('theme saved');
|
||||||
defaultTheme: {
|
|
||||||
preset: this.$appState.designer.preset,
|
|
||||||
customTokens: this.$appState.designer.customTokens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
localStorage.setItem(this.$appState.designer.key, JSON.stringify(localState));
|
|
||||||
},
|
|
||||||
loadFromLocalStorage() {
|
|
||||||
const localState = localStorage.getItem(this.$appState.designer.key);
|
|
||||||
|
|
||||||
if (localState) {
|
|
||||||
const parsedLocalState = JSON.parse(localState);
|
|
||||||
|
|
||||||
if (parsedLocalState?.themes?.defaultTheme) {
|
|
||||||
const defaultTheme = parsedLocalState.themes.defaultTheme;
|
|
||||||
|
|
||||||
if (defaultTheme.preset) {
|
|
||||||
this.$appState.designer.preset = defaultTheme.preset;
|
|
||||||
usePreset(this.$appState.designer.preset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultTheme.customTokens) {
|
|
||||||
this.$appState.designer.customTokens = defaultTheme.customTokens;
|
|
||||||
this.refreshACTokens();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$toast.add({ severity: 'success', summary: 'Success', detail: 'Theme loaded to Designer.', life: 3000 });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$toast.add({ severity: 'warn', summary: 'Error', detail: 'No saved theme found.', life: 3000 });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addToken() {
|
|
||||||
this.$appState.designer.customTokens = [...this.$appState.designer.customTokens, ...[{}]];
|
|
||||||
},
|
|
||||||
removeToken(index) {
|
|
||||||
this.$appState.designer.customTokens.splice(index, 1);
|
|
||||||
},
|
|
||||||
saveTokens() {
|
|
||||||
this.$appState.designer.preset.extend = {};
|
|
||||||
this.$appState.designer.customTokens.forEach((token) => {
|
|
||||||
this.$appState.designer.preset.extend[this.transformTokenName(token.name)] = token.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.refreshACTokens();
|
|
||||||
this.saveTheme();
|
|
||||||
this.$toast.add({ severity: 'success', summary: 'Success', detail: 'Tokens saved', life: 3000 });
|
|
||||||
},
|
|
||||||
replaceColorPalette() {
|
|
||||||
this.$appState.designer.preset.semantic.primary = this.$appState.designer.preset.primitive.emerald;
|
|
||||||
this.$appState.designer.preset.semantic.colorScheme.light.surface = { ...{ 0: '#ffffff' }, ...this.$appState.designer.preset.primitive.slate };
|
|
||||||
this.$appState.designer.preset.semantic.colorScheme.dark.surface = { ...{ 0: '#ffffff' }, ...this.$appState.designer.preset.primitive.zinc };
|
|
||||||
},
|
|
||||||
transformTokenName(name) {
|
|
||||||
if (name && name.trim().length) {
|
|
||||||
let tokenNameSections = name.split('.');
|
|
||||||
let transformedName = '';
|
|
||||||
|
|
||||||
tokenNameSections.forEach((section, index) => {
|
|
||||||
transformedName += index === 0 ? section : section.charAt(0).toUpperCase() + section.substring(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
return transformedName;
|
|
||||||
} else {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
camelCaseToDotCase(name) {
|
camelCaseToDotCase(name) {
|
||||||
return name.replace(/([a-z])([A-Z])/g, '$1.$2').toLowerCase();
|
return name.replace(/([a-z])([A-Z])/g, '$1.$2').toLowerCase();
|
||||||
|
@ -359,26 +103,33 @@ app.mount("#app");
|
||||||
const tokenValue = $dt(tokenName).value;
|
const tokenValue = $dt(tokenName).value;
|
||||||
const isColor = tokenName.includes('color') || tokenName.includes('background') || regex.test(tokenName);
|
const isColor = tokenName.includes('color') || tokenName.includes('background') || regex.test(tokenName);
|
||||||
|
|
||||||
this.$appState.designer.acTokens.push({ token: tokenName, label: '{' + tokenName + '}', variable: $dt(tokenName).variable, value: tokenValue, isColor: isColor });
|
this.$appState.designer.theme.acTokens.push({ token: tokenName, label: '{' + tokenName + '}', variable: $dt(tokenName).variable, value: tokenValue, isColor: isColor });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refreshACTokens() {
|
refreshACTokens() {
|
||||||
this.$appState.designer.acTokens = [];
|
this.$appState.designer.theme.acTokens = [];
|
||||||
this.generateACTokens(null, this.$appState.designer.preset);
|
this.generateACTokens(null, this.$appState.designer.theme.preset);
|
||||||
|
console.log('refresh tokens');
|
||||||
},
|
},
|
||||||
onKeyDown(event) {
|
openDashboard() {
|
||||||
if (event.code === 'Enter' || event.code === 'NumpadEnter') {
|
this.$appState.designer.activeView = 'dashboard';
|
||||||
this.apply();
|
}
|
||||||
event.preventDefault();
|
},
|
||||||
|
computed: {
|
||||||
|
viewTitle() {
|
||||||
|
const view = this.$appState.designer.activeView;
|
||||||
|
|
||||||
|
if (view === 'dashboard') {
|
||||||
|
return 'Theme Designer';
|
||||||
|
} else if (view === 'create_theme') {
|
||||||
|
return 'Create Theme';
|
||||||
|
} else if (view === 'editor') {
|
||||||
|
return this.$appState.designer.theme.name;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
onShow() {
|
return null;
|
||||||
this.deferredTabs = false;
|
|
||||||
},
|
|
||||||
onHide() {
|
|
||||||
this.deferredTabs = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
<template>
|
||||||
|
<section class="mb-6">
|
||||||
|
<span class="block text-lg font-semibold mb-2">Theme Name</span>
|
||||||
|
<input v-model="themeName" type="text" autocomplete="off" class="px-3 py-2 rounded-md border border-surface-300 dark:border-surface-700 flex-1" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-6">
|
||||||
|
<div class="text-lg font-semibold mb-2">Foundation</div>
|
||||||
|
<span class="block text-muted-color leading-6 mb-4">Start by choosing a built-in theme as a foundation, or import your own design by uploading a tokens.json file created with the Prime UI Kit and Tokens Studio.</span>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex flex-col gap-4 border border-surface-200 dark:border-surface-700 rounded-md p-4">
|
||||||
|
<span class="font-semibold">Base Theme</span>
|
||||||
|
<span class="text-muted-color">Variety of built-in themes with distinct characteristics.</span>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<SelectButton v-model="basePreset" :options="presetOptions" optionLabel="label" optionValue="value" :allowEmpty="false" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="createThemeFromPreset"
|
||||||
|
icon="pi pi-download"
|
||||||
|
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider>OR</Divider>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 border border-surface-200 dark:border-surface-700 rounded-md p-4">
|
||||||
|
<span class="font-semibold">Import Figma Tokens </span>
|
||||||
|
<span class="text-muted-color leading-6">Export the token.json file from Figma Token Studio and import to the Visual Editor.</span>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<FileUpload mode="basic" pt:root:class="!justify-start" @select="onFileSelect($event)" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="createThemeFromFigma"
|
||||||
|
icon="pi pi-download"
|
||||||
|
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { usePreset } from '@primevue/themes';
|
||||||
|
import Aura from '@primevue/themes/aura';
|
||||||
|
import Lara from '@primevue/themes/lara';
|
||||||
|
import Material from '@primevue/themes/material';
|
||||||
|
import Nora from '@primevue/themes/nora';
|
||||||
|
|
||||||
|
const presets = {
|
||||||
|
Aura,
|
||||||
|
Material,
|
||||||
|
Lara,
|
||||||
|
Nora
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
|
||||||
|
return {
|
||||||
|
designerApiBase: runtimeConfig.public.designerApiBase
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
themeName: null,
|
||||||
|
basePreset: 'Aura',
|
||||||
|
figmaData: null,
|
||||||
|
presetOptions: [
|
||||||
|
{ label: 'Aura', value: 'Aura' },
|
||||||
|
{ label: 'Material', value: 'Material' },
|
||||||
|
{ label: 'Lara', value: 'Lara' },
|
||||||
|
{ label: 'Nora', value: 'Nora' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
replaceColorPalette() {
|
||||||
|
this.$appState.designer.theme.preset.semantic.primary = this.$appState.designer.theme.preset.primitive.emerald;
|
||||||
|
this.$appState.designer.theme.preset.semantic.colorScheme.light.surface = { ...{ 0: '#ffffff' }, ...this.$appState.designer.theme.preset.primitive.slate };
|
||||||
|
this.$appState.designer.theme.preset.semantic.colorScheme.dark.surface = { ...{ 0: '#ffffff' }, ...this.$appState.designer.theme.preset.primitive.zinc };
|
||||||
|
},
|
||||||
|
async createThemeFromPreset() {
|
||||||
|
const newPreset = presets[this.basePreset];
|
||||||
|
|
||||||
|
if (this.basePreset === 'Material') {
|
||||||
|
document.body.classList.add('material');
|
||||||
|
this.$primevue.config.ripple = true;
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('material');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await $fetch(this.designerApiBase + '/theme/create', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
name: this.themeName,
|
||||||
|
preset: newPreset,
|
||||||
|
license_key: this.$appState.designer.licenseKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An error occured', detail: error.message, life: 3000 });
|
||||||
|
} else {
|
||||||
|
this.$appState.designer.theme = {
|
||||||
|
name: this.themeName,
|
||||||
|
preset: newPreset,
|
||||||
|
customTokens: [],
|
||||||
|
acTokens: []
|
||||||
|
};
|
||||||
|
this.replaceColorPalette();
|
||||||
|
usePreset(newPreset);
|
||||||
|
|
||||||
|
this.$appState.designer.activeView = 'editor';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFileSelect(event) {
|
||||||
|
const file = event.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e) => {
|
||||||
|
this.figmaData = e.target.result;
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = (e) => {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An error occured', detail: 'Unable to read file', life: 3000 });
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
},
|
||||||
|
async createThemeFromFigma() {
|
||||||
|
const { data: newPreset, error } = await $fetch(this.designerApiBase + '/theme/figma', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
name: this.themeName,
|
||||||
|
figma_tokens: this.figmaData,
|
||||||
|
license_key: this.$appState.designer.licenseKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An error occured', detail: error.message, life: 3000 });
|
||||||
|
} else {
|
||||||
|
this.$appState.designer.theme = {
|
||||||
|
name: this.themeName,
|
||||||
|
preset: newPreset,
|
||||||
|
customTokens: [],
|
||||||
|
acTokens: []
|
||||||
|
};
|
||||||
|
this.replaceColorPalette();
|
||||||
|
usePreset(newPreset);
|
||||||
|
|
||||||
|
this.$appState.designer.activeView = 'editor';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,236 @@
|
||||||
|
<template>
|
||||||
|
<div class="text-lg font-semibold mb-2">License Key</div>
|
||||||
|
<span class="block text-muted-color leading-6 mb-4"
|
||||||
|
>A license can be purchased from PrimeStore, if you do not have a license key, you are still able to use the Designer in free tier. <NuxtLink to="/designer" class="doc-link">Learn more</NuxtLink> about the Theme Designer.</span
|
||||||
|
>
|
||||||
|
<form @submit.prevent class="flex gap-4">
|
||||||
|
<input v-model="$appState.designer.licenseKey" type="password" autocomplete="off" class="px-3 py-2 rounded-md border border-surface-300 dark:border-surface-700 flex-1" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="activate(false)"
|
||||||
|
icon="pi pi-download"
|
||||||
|
:disabled="!$appState.designer.licenseKey"
|
||||||
|
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white disabled:opacity-60"
|
||||||
|
>
|
||||||
|
Activate
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="text-lg font-semibold mb-2 mt-6">My Themes</div>
|
||||||
|
<span class="block text-muted-color leading-6 mb-4">Continue editing your existing themes or build a new one.</span>
|
||||||
|
<div class="flex flex-wrap gap-6">
|
||||||
|
<button type="button" class="rounded-xl h-36 w-36 bg-transparent border border-gray-200 dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-500 text-black dark:text-white" @click="openNewTheme">
|
||||||
|
<i class="pi pi-plus !text-2xl"></i>
|
||||||
|
</button>
|
||||||
|
<template v-if="loading">
|
||||||
|
<Skeleton class="!rounded-xl !h-36 !w-36">-</Skeleton>
|
||||||
|
<Skeleton class="!rounded-xl !h-36 !w-36">-</Skeleton>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div v-for="theme of $appState.designer.themes" :key="theme.t_key" class="flex flex-col gap-2 relative">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded-xl h-36 w-36 px-4 overflow-hidden text-ellipsis bg-transparent border border-gray-200 dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-500 text-black dark:text-white"
|
||||||
|
@click="loadTheme(theme)"
|
||||||
|
>
|
||||||
|
<span class="text-2xl uppercase">{{ abbrThemeName(theme) }}</span>
|
||||||
|
</button>
|
||||||
|
<div class="flex flex-col items-center gap-2">
|
||||||
|
<div class="group flex items-center gap-2 relative">
|
||||||
|
<input v-model="theme.t_name" type="text" class="w-24 text-sm px-2 py-1" maxlength="100" @blur="renameTheme(theme)" />
|
||||||
|
<i class="hidden group-hover:block pi pi-pencil !text-sm absolute top-50 right-0 text-muted-color"></i>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted-color text-xs">{{ formatTimestamp(theme.last_updated) }}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="toggleMenuOptions($event, theme)"
|
||||||
|
class="bg-surface-100 dark:bg-surface-800 hover:bg-surface-200 dark:hover:bg-surface-700 text-black dark:text-white flex absolute top-2 right-2 w-8 h-8 rounded-full items-center justify-center"
|
||||||
|
>
|
||||||
|
<i class="pi pi-ellipsis-v" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Menu ref="themeMenu" :model="themeOptions" :popup="true" />
|
||||||
|
|
||||||
|
<ConfirmDialog group="designer"></ConfirmDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { usePreset } from '@primevue/themes';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
|
||||||
|
return {
|
||||||
|
designerApiBase: runtimeConfig.public.designerApiBase
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: ['designerUtils'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
currentTheme: null,
|
||||||
|
confirmDialogVisible: false,
|
||||||
|
themeOptions: [
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
icon: 'pi pi-times',
|
||||||
|
command: () => {
|
||||||
|
this.$confirm.require({
|
||||||
|
group: 'designer',
|
||||||
|
message: 'Are you sure you want to delete this theme?',
|
||||||
|
header: 'Confirmation',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
rejectProps: {
|
||||||
|
severity: 'secondary'
|
||||||
|
},
|
||||||
|
accept: () => {
|
||||||
|
this.deleteTheme(this.currentTheme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Duplicate',
|
||||||
|
icon: 'pi pi-copy',
|
||||||
|
command: () => {
|
||||||
|
this.duplicateTheme(this.currentTheme);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ label: 'Download', icon: 'pi pi-download' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.$appState.designer.licenseKey) {
|
||||||
|
const keyValue = localStorage.getItem(this.$appState.designer.localStoreKey);
|
||||||
|
|
||||||
|
if (keyValue) {
|
||||||
|
this.$appState.designer.licenseKey = keyValue;
|
||||||
|
this.activate(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.loadThemes();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async activate(silent) {
|
||||||
|
this.loading = true;
|
||||||
|
const { data, error } = await $fetch(this.designerApiBase + '/license/' + this.$appState.designer.licenseKey);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An Error Occurred', detail: error.message, life: 3000 });
|
||||||
|
} else {
|
||||||
|
if (data) {
|
||||||
|
if (!silent) {
|
||||||
|
this.$toast.add({ severity: 'success', summary: 'Success', detail: 'License is activated.', life: 3000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadThemes();
|
||||||
|
localStorage.setItem(this.$appState.designer.localStoreKey, this.$appState.designer.licenseKey);
|
||||||
|
} else {
|
||||||
|
this.$toast.add({ severity: 'warn', summary: 'Unable to Activate', detail: 'Invalid key', life: 3000 });
|
||||||
|
this.$appState.designer.themes = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
openNewTheme() {
|
||||||
|
this.$appState.designer.activeView = 'create_theme';
|
||||||
|
},
|
||||||
|
async loadThemes() {
|
||||||
|
const { data, error } = await $fetch(this.designerApiBase + '/theme/list/' + this.$appState.designer.licenseKey);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An Error Occurred', detail: error.message, life: 3000 });
|
||||||
|
} else {
|
||||||
|
this.$appState.designer.themes = data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadTheme(theme) {
|
||||||
|
const { data, error } = await $fetch(this.designerApiBase + '/theme/' + theme.t_key);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An Error Occurred', detail: 'Code: ' + error.code, life: 3000 });
|
||||||
|
} else {
|
||||||
|
this.$appState.designer.theme = {
|
||||||
|
name: data.t_name,
|
||||||
|
preset: JSON.parse(data.preset),
|
||||||
|
customTokens: [],
|
||||||
|
acTokens: []
|
||||||
|
};
|
||||||
|
|
||||||
|
usePreset(this.$appState.designer.theme.preset);
|
||||||
|
this.designerUtils.refreshACTokens();
|
||||||
|
this.$appState.designer.activeView = 'editor';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async renameTheme(theme) {
|
||||||
|
const { error } = await $fetch(this.designerApiBase + '/theme/rename/' + theme.t_key, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: {
|
||||||
|
name: theme.t_name,
|
||||||
|
license_key: this.$appState.designer.licenseKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An Error Occurred', detail: error.message, life: 3000 });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteTheme(theme) {
|
||||||
|
const { error } = await $fetch(this.designerApiBase + '/theme/delete/' + theme.t_key, {
|
||||||
|
method: 'DELETE',
|
||||||
|
body: {
|
||||||
|
license_key: this.$appState.designer.licenseKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An Error Occurred', detail: error.message, life: 3000 });
|
||||||
|
} else {
|
||||||
|
this.loadThemes();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async duplicateTheme(theme) {
|
||||||
|
const { error } = await $fetch(this.designerApiBase + '/theme/duplicate/' + theme.t_key, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
license_key: this.$appState.designer.licenseKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.$toast.add({ severity: 'error', summary: 'An Error Occurred', detail: error.message, life: 3000 });
|
||||||
|
} else {
|
||||||
|
this.loadThemes();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatTimestamp(timestamp) {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return date.toLocaleString('en-US', options);
|
||||||
|
},
|
||||||
|
toggleMenuOptions(event, theme) {
|
||||||
|
this.currentTheme = theme;
|
||||||
|
this.$refs.themeMenu.toggle(event);
|
||||||
|
},
|
||||||
|
abbrThemeName(theme) {
|
||||||
|
return theme.t_name ? theme.t_name.substring(0, 2) : 'U';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,96 @@
|
||||||
|
<template>
|
||||||
|
<Tabs v-model:value="$appState.designer.activeTab" :lazy="deferred">
|
||||||
|
<TabList>
|
||||||
|
<Tab value="0">Primitive</Tab>
|
||||||
|
<Tab value="1">Semantic</Tab>
|
||||||
|
<Tab value="2" :disabled="!isComponentRoute">Component</Tab>
|
||||||
|
<Tab value="3">Custom</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanels class="!px-0">
|
||||||
|
<TabPanel value="0">
|
||||||
|
<div>
|
||||||
|
<form @keydown="onKeyDown" class="flex flex-col gap-3">
|
||||||
|
<DesignBorderRadius />
|
||||||
|
<DesignColors />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="1">
|
||||||
|
<Accordion :value="['0', '1']" multiple>
|
||||||
|
<AccordionPanel value="0">
|
||||||
|
<AccordionHeader>Common</AccordionHeader>
|
||||||
|
<AccordionContent>
|
||||||
|
<div>
|
||||||
|
<form @keydown="onKeyDown" class="flex flex-col gap-3">
|
||||||
|
<DesignGeneral />
|
||||||
|
<DesignFormField />
|
||||||
|
<DesignList />
|
||||||
|
<DesignNavigation />
|
||||||
|
<DesignOverlay />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionPanel>
|
||||||
|
|
||||||
|
<AccordionPanel value="1">
|
||||||
|
<AccordionHeader>Color Scheme</AccordionHeader>
|
||||||
|
<AccordionContent>
|
||||||
|
<Tabs value="cs-0">
|
||||||
|
<TabList>
|
||||||
|
<Tab value="cs-0">Light</Tab>
|
||||||
|
<Tab value="cs-1">Dark</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanels class="!px-0">
|
||||||
|
<TabPanel value="cs-0">
|
||||||
|
<form @keydown="onKeyDown">
|
||||||
|
<DesignCS :value="$appState.designer.theme.preset.semantic.colorScheme.light" />
|
||||||
|
</form>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="cs-1">
|
||||||
|
<form @keydown="onKeyDown">
|
||||||
|
<DesignCS :value="$appState.designer.theme.preset.semantic.colorScheme.dark" />
|
||||||
|
</form>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionPanel>
|
||||||
|
</Accordion>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="2">
|
||||||
|
<form @keydown="onKeyDown">
|
||||||
|
<DesignComponent />
|
||||||
|
</form>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="3">
|
||||||
|
<DesignCustomTokens />
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
deferred: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onKeyDown(event) {
|
||||||
|
if (event.code === 'Enter' || event.code === 'NumpadEnter') {
|
||||||
|
this.apply();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isComponentRoute() {
|
||||||
|
const components = this.$appState.designer.theme?.preset?.components;
|
||||||
|
|
||||||
|
return components ? components[this.$route.name] != null : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<label :for="inputId" class="text-sm text-zinc-700 dark:text-white capitalize">{{ label }}</label>
|
<label :for="inputId" class="text-sm text-zinc-700 dark:text-white block capitalize text-ellipsis overflow-hidden w-full whitespace-nowrap" :title="label">{{ label }}</label>
|
||||||
<div :id="id" class="relative">
|
<div :id="id" class="relative">
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
:modelValue="modelValue"
|
:modelValue="modelValue"
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<section class="flex flex-col gap-3">
|
<section v-if="isComponentRoute" class="flex flex-col gap-3">
|
||||||
<div class="text-lg font-semibold capitalize">{{ componentKey }}</div>
|
<div class="text-lg font-semibold capitalize">{{ componentKey }}</div>
|
||||||
<template v-for="(value, name) in tokens" :key="name">
|
<template v-for="(value, name) in tokens" :key="name">
|
||||||
<DesignComponentSection v-if="name !== 'colorScheme'" :componentKey="componentKey" :path="name" />
|
<DesignComponentSection v-if="name !== 'colorScheme'" :componentKey="componentKey" :path="name" />
|
||||||
|
@ -27,19 +27,23 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
inject: ['$preset'],
|
|
||||||
computed: {
|
computed: {
|
||||||
componentKey() {
|
componentKey() {
|
||||||
return this.$route.name;
|
return this.$route.name;
|
||||||
},
|
},
|
||||||
|
isComponentRoute() {
|
||||||
|
const components = this.$appState.designer.theme?.preset?.components;
|
||||||
|
|
||||||
|
return components ? components[this.componentKey] != null : false;
|
||||||
|
},
|
||||||
tokens() {
|
tokens() {
|
||||||
return this.$preset.components[this.componentKey];
|
return this.$appState.designer.theme?.preset?.components[this.componentKey];
|
||||||
},
|
},
|
||||||
lightTokens() {
|
lightTokens() {
|
||||||
return this.$preset.components[this.componentKey].colorScheme.light;
|
return this.$appState.designer.theme?.preset?.components[this.componentKey].colorScheme.light;
|
||||||
},
|
},
|
||||||
darkTokens() {
|
darkTokens() {
|
||||||
return this.$preset.components[this.componentKey].colorScheme.dark;
|
return this.$appState.designer.theme?.preset?.components[this.componentKey].colorScheme.dark;
|
||||||
},
|
},
|
||||||
hasColorScheme() {
|
hasColorScheme() {
|
||||||
return this.tokens.colorScheme != undefined;
|
return this.tokens.colorScheme != undefined;
|
|
@ -24,7 +24,6 @@ export default {
|
||||||
default: null
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: ['$preset'],
|
|
||||||
methods: {
|
methods: {
|
||||||
camelCaseToSpaces(val) {
|
camelCaseToSpaces(val) {
|
||||||
return val.replace(/([a-z])([A-Z])/g, '$1 $2');
|
return val.replace(/([a-z])([A-Z])/g, '$1 $2');
|
||||||
|
@ -67,7 +66,7 @@ export default {
|
||||||
.join(' ');
|
.join(' ');
|
||||||
},
|
},
|
||||||
tokens() {
|
tokens() {
|
||||||
return this.getObjectProperty(this.$preset.components[this.componentKey], this.path);
|
return this.getObjectProperty(this.$appState.designer.theme.preset.components[this.componentKey], this.path);
|
||||||
},
|
},
|
||||||
nestedTokens() {
|
nestedTokens() {
|
||||||
const groups = {};
|
const groups = {};
|
|
@ -0,0 +1,80 @@
|
||||||
|
<template>
|
||||||
|
<span class="leading-6 text-muted-color">Extend the theming system with your own design tokens e.g. <span class="font-medium">accent.color</span>. Do not use curly braces in the name field.</span>
|
||||||
|
<ul class="flex flex-col gap-4 list-none p-0 mx-0 my-4">
|
||||||
|
<li v-for="(token, index) of $appState.designer.customTokens" :key="index" class="first:border-t border-b border-surface-200 dark:border-surface-300 py-2">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<label class="flex items-center gap-2 flex-auto">
|
||||||
|
<span class="text-sm">Name</span>
|
||||||
|
<input v-model="token['name']" type="text" class="border border-surface-300 dark:border-surface-600 rounded-lg py-2 px-2 w-full" />
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 flex-auto">
|
||||||
|
<span class="text-sm">Value</span>
|
||||||
|
<input v-model="token['value']" type="text" class="border border-surface-300 dark:border-surface-600 rounded-lg py-2 px-2 w-full" />
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="removeToken(index)"
|
||||||
|
class="cursor-pointer inline-flex items-center justify-center ms-auto w-8 h-8 rounded-full bg-red-50 hover:bg-red-100 text-red-600 dark:bg-red-400/10 dark:hover:bg-red-400/20 dark:text-red-400 transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-red-600 focus:dark:outline-red-400"
|
||||||
|
>
|
||||||
|
<i class="pi pi-times" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="addToken"
|
||||||
|
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white"
|
||||||
|
>
|
||||||
|
Add New
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="$appState.designer.theme.customTokens.length"
|
||||||
|
type="button"
|
||||||
|
@click="saveTokens"
|
||||||
|
class="px-3 py-2 bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black rounded-md font-medium cursor-pointer transition-colors duration-200 focus:outline focus:outline-offset-2 focus:outline-zinc-950 focus:dark:outline-white"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['designer'],
|
||||||
|
methods: {
|
||||||
|
addToken() {
|
||||||
|
this.$appState.designer.theme.customTokens = [...this.$appState.designer.theme.customTokens, ...[{}]];
|
||||||
|
},
|
||||||
|
removeToken(index) {
|
||||||
|
this.$appState.designer.theme.customTokens.splice(index, 1);
|
||||||
|
},
|
||||||
|
saveTokens() {
|
||||||
|
this.$appState.designer.theme.preset.extend = {};
|
||||||
|
this.$appState.designer.theme.customTokens.forEach((token) => {
|
||||||
|
this.$appState.designer.theme.preset.extend[this.transformTokenName(token.name)] = token.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
designer.refreshACTokens();
|
||||||
|
designer.saveTheme();
|
||||||
|
|
||||||
|
this.$toast.add({ severity: 'success', summary: 'Success', detail: 'Tokens saved', life: 3000 });
|
||||||
|
},
|
||||||
|
transformTokenName(name) {
|
||||||
|
if (name && name.trim().length) {
|
||||||
|
let tokenNameSections = name.split('.');
|
||||||
|
let transformedName = '';
|
||||||
|
|
||||||
|
tokenNameSections.forEach((section, index) => {
|
||||||
|
transformedName += index === 0 ? section : section.charAt(0).toUpperCase() + section.substring(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return transformedName;
|
||||||
|
} else {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<Fieldset legend="Rounded" :toggleable="true">
|
||||||
|
<section class="grid grid-cols-4 gap-2">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.primitive.borderRadius.none" label="None" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.primitive.borderRadius.xs" label="Extra Small" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.primitive.borderRadius.sm" label="Small" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.primitive.borderRadius.md" label="Medium" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.primitive.borderRadius.lg" label="Large" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.primitive.borderRadius.xl" label="Extra Large" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Fieldset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {};
|
||||||
|
</script>
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<Fieldset legend="Colors" :toggleable="true">
|
<Fieldset legend="Colors" :toggleable="true">
|
||||||
<template v-for="(value, key) of $preset.primitive" :key="key">
|
<template v-for="(value, key) of $appState.designer.theme.preset.primitive" :key="key">
|
||||||
<section v-if="key !== 'borderRadius'" class="flex justify-between items-center mb-4">
|
<section v-if="key !== 'borderRadius'" class="flex justify-between items-center mb-4">
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex gap-2 items-center">
|
||||||
<span class="text-sm capitalize block w-20">{{ key }}</span>
|
<span class="text-sm capitalize block w-20">{{ key }}</span>
|
||||||
<input :value="$preset.primitive[key]['500']" @input="onColorChange($event, key)" type="color" />
|
<input :value="$appState.designer.theme.preset.primitive[key]['500']" @input="onColorChange($event, key)" type="color" />
|
||||||
</div>
|
</div>
|
||||||
<DesignColorPalette :value="$preset.primitive[key]" />
|
<DesignColorPalette :value="$appState.designer.theme.preset.primitive[key]" />
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
|
@ -16,10 +16,9 @@
|
||||||
import { palette } from '@primevue/themes';
|
import { palette } from '@primevue/themes';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['$preset'],
|
|
||||||
methods: {
|
methods: {
|
||||||
onColorChange(event, color) {
|
onColorChange(event, color) {
|
||||||
this.$preset.primitive[color] = palette(event.target.value);
|
this.$appState.designer.theme.preset.primitive[color] = palette(event.target.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -0,0 +1,67 @@
|
||||||
|
<template>
|
||||||
|
<Fieldset legend="Form Field" :toggleable="true">
|
||||||
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Base</div>
|
||||||
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.paddingX" label="Padding X" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.paddingY" label="Padding Y" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.borderRadius" label="Border Radius" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.transitionDuration" label="Transition Duration" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Small</div>
|
||||||
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.sm.paddingX" label="Padding X" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.sm.paddingY" label="Padding Y" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.sm.fontSize" label="Font Size" />
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Large</div>
|
||||||
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.lg.paddingX" label="Padding X" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.lg.paddingY" label="Padding Y" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.lg.fontSize" label="Font Size" />
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Focus Ring</div>
|
||||||
|
<section class="grid grid-cols-4 gap-2">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.focusRing.width" label="Width" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.focusRing.style" label="Style" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.focusRing.color" label="Color" type="color" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.formField.focusRing.offset" label="Offset" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Fieldset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {};
|
||||||
|
</script>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<Fieldset legend="General" :toggleable="true">
|
||||||
|
<section class="flex justify-between items-center mb-4">
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<span class="text-sm">Primary</span>
|
||||||
|
<input :value="$appState.designer.theme.preset.semantic.primary['500']" @input="onPrimaryColorChange($event)" type="color" />
|
||||||
|
</div>
|
||||||
|
<DesignColorPalette :value="$appState.designer.theme.preset.semantic.primary" />
|
||||||
|
</section>
|
||||||
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.transitionDuration" label="Transition Duration" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.disabledOpacity" label="Disabled Opacity" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.iconSize" label="Icon Size" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.anchorGutter" label="Anchor Gutter" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.content.borderRadius" label="Border Radius" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.mask.transitionDuration" label="Mask Transition Dur." />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1"></div>
|
||||||
|
<div class="flex flex-col gap-1"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Focus Ring</div>
|
||||||
|
<section class="grid grid-cols-4 gap-2">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.focusRing.width" label="Width" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.focusRing.style" label="Style" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.focusRing.color" label="Color" type="color" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.focusRing.offset" label="Offset" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Fieldset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { palette } from '@primevue/themes';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
onPrimaryColorChange(event) {
|
||||||
|
this.$appState.designer.theme.preset.semantic.primary = palette(event.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -3,13 +3,13 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Container</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Container</div>
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.list.padding" label="Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.list.padding" label="Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.list.gap" label="Gap" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.list.gap" label="Gap" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.list.header.padding" label="Header Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.list.header.padding" label="Header Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -17,27 +17,25 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Option</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Option</div>
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.list.option.padding" label="Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.list.option.padding" label="Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.list.option.borderRadius" label="Border Radius" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.list.option.borderRadius" label="Border Radius" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Option Group</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Option Group</div>
|
||||||
<section class="grid grid-cols-4 gap-2">
|
<section class="grid grid-cols-4 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.list.optionGroup.padding" label="Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.list.optionGroup.padding" label="Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.list.optionGroup.fontWeight" label="Font Weight" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.list.optionGroup.fontWeight" label="Font Weight" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {};
|
||||||
inject: ['$preset']
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
|
@ -3,10 +3,10 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">List</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">List</div>
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.navigation.list.padding" label="Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.navigation.list.padding" label="Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.navigation.list.gap" label="Gap" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.navigation.list.gap" label="Gap" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
|
@ -15,13 +15,13 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Item</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Item</div>
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.navigation.item.padding" label="Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.navigation.item.padding" label="Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.navigation.item.borderRadius" label="Border Radius" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.navigation.item.borderRadius" label="Border Radius" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.navigation.item.gap" label="Gap" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.navigation.item.gap" label="Gap" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -29,10 +29,10 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Submenu Label</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Submenu Label</div>
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.navigation.submenuLabel.padding" label="Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.navigation.submenuLabel.padding" label="Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.navigation.submenuLabel.fontWeight" label="Font Weight" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.navigation.submenuLabel.fontWeight" label="Font Weight" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Submenu Icon</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Submenu Icon</div>
|
||||||
<section class="grid grid-cols-4 gap-2">
|
<section class="grid grid-cols-4 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.navigation.submenuIcon.size" label="Size" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.navigation.submenuIcon.size" label="Size" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
|
@ -51,7 +51,5 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {};
|
||||||
inject: ['$preset']
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
|
@ -3,10 +3,10 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Select</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Select</div>
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.select.borderRadius" label="Border Radius" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.select.borderRadius" label="Border Radius" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.select.shadow" label="Shadow" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.select.shadow" label="Shadow" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
|
@ -15,13 +15,13 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Popover</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Popover</div>
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.popover.borderRadius" label="Border Radius" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.popover.borderRadius" label="Border Radius" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.popover.padding" label="Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.popover.padding" label="Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.popover.shadow" label="Shadow" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.popover.shadow" label="Shadow" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -29,13 +29,13 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Modal</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Modal</div>
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
<section class="grid grid-cols-4 mb-3 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.modal.borderRadius" label="Border Radius" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.modal.borderRadius" label="Border Radius" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.modal.padding" label="Padding" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.modal.padding" label="Padding" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.modal.shadow" label="Shadow" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.modal.shadow" label="Shadow" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Navigation</div>
|
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Navigation</div>
|
||||||
<section class="grid grid-cols-4 gap-2">
|
<section class="grid grid-cols-4 gap-2">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<DesignTokenField v-model="$preset.semantic.overlay.navigation.shadow" label="Shadow" />
|
<DesignTokenField v-model="$appState.designer.theme.preset.semantic.overlay.navigation.shadow" label="Shadow" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
<div class="flex flex-col gap-1"></div>
|
<div class="flex flex-col gap-1"></div>
|
||||||
|
@ -53,7 +53,5 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {};
|
||||||
inject: ['$preset']
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
|
@ -1,30 +0,0 @@
|
||||||
<template>
|
|
||||||
<Fieldset legend="Rounded" :toggleable="true">
|
|
||||||
<section class="grid grid-cols-4 gap-2">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.primitive.borderRadius.none" label="None" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.primitive.borderRadius.xs" label="Extra Small" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.primitive.borderRadius.sm" label="Small" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.primitive.borderRadius.md" label="Medium" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.primitive.borderRadius.lg" label="Large" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.primitive.borderRadius.xl" label="Extra Large" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</Fieldset>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
inject: ['$preset']
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,69 +0,0 @@
|
||||||
<template>
|
|
||||||
<Fieldset legend="Form Field" :toggleable="true">
|
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Base</div>
|
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.paddingX" label="Padding X" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.paddingY" label="Padding Y" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.borderRadius" label="Border Radius" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.transitionDuration" label="Transition Duration" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Small</div>
|
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.sm.paddingX" label="Padding X" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.sm.paddingY" label="Padding Y" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.sm.fontSize" label="Font Size" />
|
|
||||||
</div>
|
|
||||||
<div></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Large</div>
|
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.lg.paddingX" label="Padding X" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.lg.paddingY" label="Padding Y" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.lg.fontSize" label="Font Size" />
|
|
||||||
</div>
|
|
||||||
<div></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Focus Ring</div>
|
|
||||||
<section class="grid grid-cols-4 gap-2">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.focusRing.width" label="Width" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.focusRing.style" label="Style" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.focusRing.color" label="Color" type="color" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.formField.focusRing.offset" label="Offset" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</Fieldset>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
inject: ['$preset']
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,62 +0,0 @@
|
||||||
<template>
|
|
||||||
<Fieldset legend="General" :toggleable="true">
|
|
||||||
<section class="flex justify-between items-center mb-4">
|
|
||||||
<div class="flex gap-2 items-center">
|
|
||||||
<span class="text-sm">Primary</span>
|
|
||||||
<input :value="$preset.semantic.primary['500']" @input="onPrimaryColorChange($event)" type="color" />
|
|
||||||
</div>
|
|
||||||
<DesignColorPalette :value="$preset.semantic.primary" />
|
|
||||||
</section>
|
|
||||||
<section class="grid grid-cols-4 mb-3 gap-2">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.transitionDuration" label="Transition Duration" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.disabledOpacity" label="Disabled Opacity" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.iconSize" label="Icon Size" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.anchorGutter" label="Anchor Gutter" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.content.borderRadius" label="Border Radius" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.mask.transitionDuration" label="Mask Transition Dur." />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1"></div>
|
|
||||||
<div class="flex flex-col gap-1"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="text-sm mb-1 font-semibold text-surface-950 dark:text-surface-0">Focus Ring</div>
|
|
||||||
<section class="grid grid-cols-4 gap-2">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.focusRing.width" label="Width" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.focusRing.style" label="Style" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.focusRing.color" label="Color" type="color" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<DesignTokenField v-model="$preset.semantic.focusRing.offset" label="Offset" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</Fieldset>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { palette } from '@primevue/themes';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inject: ['$preset'],
|
|
||||||
methods: {
|
|
||||||
onPrimaryColorChange(event) {
|
|
||||||
this.$preset.semantic.primary = palette(event.target.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -97,7 +97,8 @@ export default defineNuxtConfig({
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
public: {
|
public: {
|
||||||
contextPath: baseUrl,
|
contextPath: baseUrl,
|
||||||
DEV_ENV: PROCESS_ENV.DEV_ENV
|
DEV_ENV: PROCESS_ENV.DEV_ENV,
|
||||||
|
designerApiBase: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
css: ['primeicons/primeicons.css', '@/assets/styles/flags.css', '@docsearch/css/dist/style.css', '@/assets/styles/tailwind/main.css', '@/assets/styles/layout/layout.scss']
|
css: ['primeicons/primeicons.css', '@/assets/styles/flags.css', '@docsearch/css/dist/style.css', '@/assets/styles/tailwind/main.css', '@/assets/styles/layout/layout.scss']
|
||||||
|
|
|
@ -15,12 +15,18 @@ const $appState = {
|
||||||
announcement: null,
|
announcement: null,
|
||||||
storageKey: 'primevue',
|
storageKey: 'primevue',
|
||||||
designer: {
|
designer: {
|
||||||
key: 'primevue-designer-theme',
|
localStoreKey: 'primevue-designer-licensekey',
|
||||||
|
licenseKey: null,
|
||||||
active: false,
|
active: false,
|
||||||
|
activeView: 'dashboard',
|
||||||
activeTab: '0',
|
activeTab: '0',
|
||||||
preset: null,
|
theme: {
|
||||||
customTokens: [],
|
name: null,
|
||||||
acTokens: []
|
preset: null,
|
||||||
|
customTokens: [],
|
||||||
|
acTokens: []
|
||||||
|
},
|
||||||
|
themes: []
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue