Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,55 @@ export default defineAppConfig({
primary: 'amber',
neutral: 'slate',
},
fonts: {
sans: 'var(--font-sans, ui-sans-serif)',
serif: 'var(--font-serif, ui-serif)',
mono: 'var(--font-mono, ui-monospace)',
},
icons: {
arrowDown: 'lucide:arrow-down',
arrowLeft: 'lucide:arrow-left',
arrowRight: 'lucide:arrow-right',
arrowUp: 'lucide:arrow-up',
caution: 'lucide:circle-alert',
check: 'lucide:check',
chevronDoubleLeft: 'lucide:chevrons-left',
chevronDoubleRight: 'lucide:chevrons-right',
chevronDown: 'lucide:chevron-down',
chevronLeft: 'lucide:chevron-left',
chevronRight: 'lucide:chevron-right',
chevronUp: 'lucide:chevron-up',
close: 'lucide:x',
copy: 'lucide:copy',
copyCheck: 'lucide:copy-check',
dark: 'lucide:moon',
drag: 'lucide:grip-vertical',
ellipsis: 'lucide:ellipsis',
error: 'lucide:circle-x',
external: 'lucide:arrow-up-right',
eye: 'lucide:eye',
eyeOff: 'lucide:eye-off',
file: 'lucide:file',
folder: 'lucide:folder',
folderOpen: 'lucide:folder-open',
hash: 'lucide:hash',
info: 'lucide:info',
light: 'lucide:sun',
loading: 'lucide:loader-circle',
menu: 'lucide:menu',
minus: 'lucide:minus',
panelClose: 'lucide:panel-left-close',
panelOpen: 'lucide:panel-left-open',
plus: 'lucide:plus',
reload: 'lucide:rotate-ccw',
search: 'lucide:search',
stop: 'lucide:square',
success: 'lucide:circle-check',
system: 'lucide:monitor',
tip: 'lucide:lightbulb',
upload: 'lucide:upload',
warning: 'lucide:triangle-alert',
},
button: {
slots: {
base: 'hover:cursor-pointer',
Expand Down
8 changes: 8 additions & 0 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as locales from '@nuxt/ui/locale';

import { Analytics } from '@vercel/analytics/nuxt';
import { useThemeStore } from './store/theme';
import { useIconsStore } from './store/icons';

const { locale } = useI18n();

Expand All @@ -15,6 +16,8 @@ useThemeCss();
// Keep theme store's editMode in sync with global color mode
const themeStore = useThemeStore();
const colorMode = useColorMode();
const iconsStore = useIconsStore();
const appConfig = useAppConfig();

useHead({
htmlAttrs: {
Expand All @@ -27,6 +30,11 @@ if (import.meta.client) {
watch(() => colorMode.value, (val) => {
themeStore.setEditMode(val === 'dark' ? 'dark' : 'light');
}, { immediate: true });

watchEffect(() => {
appConfig.ui ||= {};
appConfig.ui.icons = { ...iconsStore.icons };

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nullish coalescing operator '||=' creates the object if it doesn't exist. However, the subsequent assignment creates a new object reference every time, which could break object equality checks elsewhere. Consider checking if the icons have actually changed before reassigning.

Suggested change
watchEffect(() => {
appConfig.ui ||= {};
appConfig.ui.icons = { ...iconsStore.icons };
// Only update icons if they have actually changed (shallow compare)
function shallowEqual(objA: Record<string, any>, objB: Record<string, any>): boolean {
if (objA === objB) return true;
const aKeys = Object.keys(objA || {});
const bKeys = Object.keys(objB || {});
if (aKeys.length !== bKeys.length) return false;
for (const key of aKeys) {
if (objA[key] !== objB[key]) return false;
}
return true;
}
watchEffect(() => {
appConfig.ui ||= {};
if (!shallowEqual(appConfig.ui.icons, iconsStore.icons)) {
appConfig.ui.icons = { ...iconsStore.icons };
}

Copilot uses AI. Check for mistakes.
});
Comment thread
ebanDev marked this conversation as resolved.
Outdated
}
</script>

Expand Down
36 changes: 35 additions & 1 deletion app/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

@theme static {
--font-sans: 'Public Sans', sans-serif;
--font-serif: 'Georgia', Cambria, 'Times New Roman', Times, serif;
--font-mono: 'SFMono-Regular', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--font-weight-thin: 100;
--font-weight-extralight: 200;
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-weight-extrabold: 800;
--font-weight-black: 900;

--color-green-50: #effdf5;
--color-green-100: #d9fbe8;
Expand All @@ -16,4 +27,27 @@
--color-green-800: #016538;
--color-green-900: #0a5331;
--color-green-950: #052e16;
}
}

:root {
font-family: var(--font-sans, 'Public Sans', sans-serif);
font-weight: var(--font-weight-normal, 400);
}

code,
pre,
kbd,
samp {
font-family: var(--font-mono, 'SFMono-Regular', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The font 'SFMono-Regular' is duplicated in the font stack. It appears both at the beginning and after 'ui-monospace'. This duplication exists in multiple places and should be corrected.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

}

.font-thin { font-weight: var(--font-weight-thin, 100); }
.font-extralight { font-weight: var(--font-weight-extralight, 200); }
.font-light { font-weight: var(--font-weight-light, 300); }
.font-normal { font-weight: var(--font-weight-normal, 400); }
.font-medium { font-weight: var(--font-weight-medium, 500); }
.font-semibold { font-weight: var(--font-weight-semibold, 600); }
.font-bold { font-weight: var(--font-weight-bold, 700); }
.font-extrabold { font-weight: var(--font-weight-extrabold, 800); }
.font-black { font-weight: var(--font-weight-black, 900); }
.font-heavy { font-weight: var(--font-weight-black, 900); }
Comment thread
ebanDev marked this conversation as resolved.
Outdated
7 changes: 5 additions & 2 deletions app/components/AIGenerateThemeModal.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { useAppIcons } from '@/composables/useAppIcons';

const props = defineProps<{ model?: string }>();
const emit = defineEmits<{ (e: 'close'): void; (e: 'imported'): void }>();

Expand All @@ -8,6 +10,7 @@ const error = ref<string | null>(null);
const { t } = useI18n();
const prompt = ref(t('ai.defaultPrompt'));
const apiKey = ref('');
const { icon: appIcon } = useAppIcons();

const { generateThemeFromPrompt } = useAIDesigner();
const { importTheme } = useThemeImport();
Expand Down Expand Up @@ -82,7 +85,7 @@ defineExpose({ show, hide });
{{ $t('ai.title') }}
</h3>
<UButton
icon="i-lucide-x"
:icon="appIcon('close')"
color="neutral"
variant="ghost"
@click="hide"
Expand Down Expand Up @@ -135,7 +138,7 @@ defineExpose({ show, hide });
</UButton>
<UButton
:loading="loading"
icon="i-lucide-sparkles"
:icon="appIcon('sparkles')"
@click="onGenerate"
>
{{ $t('ai.btnGenerate') }}
Expand Down
38 changes: 20 additions & 18 deletions app/components/AppHeader.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import AIGenerateThemeModal from './AIGenerateThemeModal.vue';
import { useSavedThemesStore } from '../store/savedThemes';
import { useAppIcons } from '@/composables/useAppIcons';

const colorMode = useColorMode();
// Composable for Theme Config open state (non-persistent)
Expand All @@ -27,6 +28,7 @@ const savedThemesStore = useSavedThemesStore();
const savedNames = computed(() => savedThemesStore.listNames);

const { t, availableLocales, setLocale } = useI18n();
const { icon: appIcon } = useAppIcons();

// Locale switch items: set the locale directly to ensure immediate update
const localeItems = computed(() => (
Expand All @@ -40,12 +42,12 @@ const localeItems = computed(() => (
// Dropdown items with onSelect handlers and keys
const actionItems = computed(() => ([
[
{ label: t('actions.save'), icon: 'i-lucide-bookmark', key: 'save', onSelect: () => { isSaveModalOpen.value = true; } },
{ label: t('actions.load'), icon: 'i-lucide-folder-open', key: 'load', onSelect: () => { isLoadModalOpen.value = true; } },
{ label: t('actions.export'), icon: 'i-lucide-download', key: 'export', onSelect: () => { exportTheme(); } },
{ label: t('actions.save'), icon: appIcon('bookmark'), key: 'save', onSelect: () => { isSaveModalOpen.value = true; } },
{ label: t('actions.load'), icon: appIcon('folderOpen'), key: 'load', onSelect: () => { isLoadModalOpen.value = true; } },
{ label: t('actions.export'), icon: appIcon('download'), key: 'export', onSelect: () => { exportTheme(); } },
],
[
{ label: t('locale.language'), icon: 'i-lucide-globe', disabled: true },
{ label: t('locale.language'), icon: appIcon('globe'), disabled: true },
...localeItems.value,
],
]));
Expand Down Expand Up @@ -120,7 +122,7 @@ const isNavigationOpen = ref(false);
<div class="flex items-center" />
<div class="flex items-center">
<UButton
icon="i-lucide-menu"
:icon="appIcon('menu')"
color="neutral"
variant="soft"
class="md:hidden"
Expand All @@ -136,7 +138,7 @@ const isNavigationOpen = ref(false);
{{ $t('app.name') }}
</h3>
<UButton
icon="i-lucide-x"
:icon="appIcon('close')"
color="neutral"
variant="ghost"
size="sm"
Expand All @@ -152,23 +154,23 @@ const isNavigationOpen = ref(false);
<div class="flex items-center gap-4 ml-auto relative z-40">
<!-- Generate Theme with AI Button -->
<UButton
icon="i-lucide-sparkles"
:icon="appIcon('sparkles')"
color="primary"
variant="outline"
:title="$t('header.generateWithAI')"
@click="openAIModal"
/>
<!-- Theme Configuration Button -->
<UButton
icon="i-lucide-brush"
:icon="appIcon('navCss')"
color="primary"
variant="solid"
:title="$t('header.themeConfig')"
@click="openThemeConfig()"
/>
<!-- Dark Mode Toggle -->
<UButton
:icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
:icon="colorMode.value === 'dark' ? appIcon('sun') : appIcon('moon')"
color="neutral"
variant="outline"
:title="$t('header.toggleDark')"
Expand All @@ -191,7 +193,7 @@ const isNavigationOpen = ref(false);
>
<UButton
class="z-50"
icon="i-lucide-ellipsis-vertical"
:icon="appIcon('ellipsisVertical')"
color="neutral"
variant="soft"
:title="$t('actions.actions')"
Expand All @@ -213,7 +215,7 @@ const isNavigationOpen = ref(false);
{{ $t('header.themeConfig') }}
</h3>
<UButton
icon="i-lucide-x"
:icon="appIcon('close')"
color="neutral"
variant="ghost"
size="sm"
Expand Down Expand Up @@ -245,7 +247,7 @@ const isNavigationOpen = ref(false);
<UButton
color="neutral"
variant="ghost"
icon="i-lucide-x"
:icon="appIcon('close')"
@click="closeExportModal"
/>
</div>
Expand All @@ -261,7 +263,7 @@ const isNavigationOpen = ref(false);
class="block text-sm font-medium"
>{{ $t('header.appConfig') }}</label>
<UButton
icon="i-lucide-clipboard"
:icon="appIcon('clipboard')"
color="primary"
variant="soft"
size="sm"
Expand Down Expand Up @@ -296,7 +298,7 @@ const isNavigationOpen = ref(false);
class="block text-sm font-medium"
>{{ $t('header.mainCss') }}</label>
<UButton
icon="i-lucide-clipboard"
:icon="appIcon('clipboard')"
color="primary"
variant="soft"
size="sm"
Expand Down Expand Up @@ -350,7 +352,7 @@ const isNavigationOpen = ref(false);
<UButton
color="neutral"
variant="ghost"
icon="i-lucide-x"
:icon="appIcon('close')"
@click="isSaveModalOpen = false"
/>
</div>
Expand Down Expand Up @@ -398,7 +400,7 @@ const isNavigationOpen = ref(false);
<UButton
color="neutral"
variant="ghost"
icon="i-lucide-x"
:icon="appIcon('close')"
@click="isLoadModalOpen = false"
/>
</div>
Expand Down Expand Up @@ -429,7 +431,7 @@ const isNavigationOpen = ref(false);
size="sm"
color="primary"
variant="soft"
icon="i-lucide-download"
:icon="appIcon('download')"
@click="handleLoadTheme(name)"
>
{{ $t('actions.load') }}
Expand All @@ -438,7 +440,7 @@ const isNavigationOpen = ref(false);
size="sm"
color="error"
variant="ghost"
icon="i-lucide-trash"
:icon="appIcon('trash')"
@click="handleDeleteTheme(name)"
/>
</div>
Expand Down
16 changes: 9 additions & 7 deletions app/components/AppNavigation.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui';
import { useAppIcons } from '@/composables/useAppIcons';
// Composable controlling Theme Config slideover open state
const { open: openThemeConfig } = useThemeConfig();

const { availableComponents } = useComponentPreviewConfig();
const localePath = useLocalePath();
const { t } = useI18n();
const { icon: appIcon } = useAppIcons();
defineProps<{
orientation: 'horizontal' | 'vertical';
}>();
Expand All @@ -14,28 +16,28 @@ const items = computed<NavigationMenuItem[][]>(() => [
[
{
label: t('nav.home'),
icon: 'i-lucide-home',
icon: appIcon('navHome'),
to: localePath('/'),
},
{
label: t('nav.cssVariables'),
icon: 'i-lucide-brush',
icon: appIcon('navCss'),
to:'#',
onSelect: () => openThemeConfig(),
},
{
label: t('nav.customColors'),
icon: 'i-lucide-palette',
icon: appIcon('navColors'),
to: localePath('/colors'),
},
{
label: t('nav.preview'),
icon: 'i-lucide-eye',
icon: appIcon('navPreview'),
to: localePath('/preview'),
},
{
label: t('nav.components'),
icon: 'i-lucide-square-code',
icon: appIcon('navComponents'),
children: availableComponents.map(component => ({
label: component.label,
to: localePath(`/components/${component.value}`),
Expand All @@ -47,13 +49,13 @@ const items = computed<NavigationMenuItem[][]>(() => [
},
{
label: 'GitHub',
icon: 'i-lucide-github',
icon: appIcon('navGithub'),
target: '_blank',
to: 'https://github.com/Rareer/nuxt-ui-theme-builder',
},
{
label: 'LinkedIn',
icon: 'i-lucide-linkedin',
icon: appIcon('navLinkedIn'),
target: '_blank',
to: 'https://www.linkedin.com/in/toni-kritz-5528a5184/',
},
Expand Down
Loading