Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7adcda8
fix: make settings dialog side bar not lazy
jvxz May 4, 2026
80b057e
feat: add tab ref to useSettingsDialog
jvxz May 4, 2026
2a3544a
feat: add matrix event type schema
jvxz May 4, 2026
ddab06f
feat: add initial settings schema
jvxz May 4, 2026
45f562e
feat: enhance settings dialog sidebar
jvxz May 4, 2026
eb98e8d
fix: fix matrix event type schema
jvxz May 4, 2026
9cb7aa5
feat: enhance settings schema types + add defaults
jvxz May 4, 2026
a0b5329
feat: add settings client plugin
jvxz May 4, 2026
9d6c2e2
feat: add useSettings convenience composable
jvxz May 4, 2026
cd7cd22
feat: enhance settings dialog components
jvxz May 4, 2026
fd82de8
Merge branch 'main' into feat/app-settings
jvxz May 4, 2026
e4e5056
Merge branch 'main' into feat/app-settings
jvxz May 4, 2026
d0e8ba0
Merge branch 'main' into feat/app-settings
jvxz May 6, 2026
d7aa6bc
refactor: edit objectValues util
jvxz May 6, 2026
a70e5a7
feat: enhance form primitive component
jvxz May 6, 2026
63442d6
refactor: edit settings structure
jvxz May 6, 2026
d1b995c
refactor: edit settings content layout component
jvxz May 6, 2026
8595975
fix: adjust variable names
jvxz May 6, 2026
0dcf0c9
feat: add settings form components
jvxz May 6, 2026
8b7d04a
feat: add settings content components
jvxz May 6, 2026
0b27531
feat: make dialog title the correct tab
jvxz May 6, 2026
9749f7f
feat: add search functionality
jvxz May 6, 2026
aab1e62
fix: remove duplicate search dialog reset
jvxz May 6, 2026
ebd507f
refactor: remove unnecessary useLocalStorage setting
jvxz May 6, 2026
6044aa0
fix: fix silly mistake
jvxz May 6, 2026
94afea7
refactor: remove unused type
jvxz May 6, 2026
463a14f
chore(lint): apply lint fixes
autofix-ci[bot] May 6, 2026
8321cef
fix: reset search query on dialog unmount
jvxz May 6, 2026
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
57 changes: 36 additions & 21 deletions app/components/form/primitive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,45 @@ const errorMessage = computed(() => {

<template>
<Primitive v-bind="$props" :class="cn('space-y-1.5', $props.ui?.container)">
<div class="flex size-fit w-full items-center justify-between">
<ULabel
:for="id"
:class="cn('text-sm font-medium gap-1', $props.ui?.label)"
>
{{ label }}
<span v-if="required" class="text-danger">*</span>
</ULabel>
<LazyUSpinner
v-if="isLoading"
class="shrink-0 size-4.5"
:data-testid="`loading-spinner-${label}`"
/>
<p
v-else-if="errorMessage"
:title="errorMessage"
:class="cn('text-xs text-danger text-end max-w-2/3 truncate', $props.ui?.error)"
>
{{ errorMessage }}
</p>
</div>
<template v-if="!$slots.label && !$slots.error">
<div class="flex size-fit w-full items-center justify-between">
<ULabel
:for="id"
:class="cn('text-sm font-medium gap-1', $props.ui?.label)"
>
{{ label }}
<span v-if="required" class="text-danger">*</span>
</ULabel>
<LazyUSpinner
v-if="isLoading"
class="shrink-0 size-4.5"
:data-testid="`loading-spinner-${label}`"
/>
<p
v-else-if="errorMessage"
:title="errorMessage"
:class="cn('text-xs text-danger text-end max-w-2/3 truncate', $props.ui?.error)"
>
{{ errorMessage }}
</p>
</div>
</template>

<slot
v-if="$slots.label"
:id
name="label"
/>
<slot
v-if="$slots.error"
name="error"
:message="errorMessage"
/>

<Slot :id :data-error="errorMessage ? '' : undefined">
<slot />
</Slot>

<div v-if="$slots.footer" :class="cn('text-xs text-muted-foreground text-end', $props.ui?.footer)">
<slot name="footer" />
</div>
Expand Down
11 changes: 11 additions & 0 deletions app/components/settings/content-layout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script lang="ts" setup>
import type { PrimitiveProps } from 'reka-ui'

defineProps<PrimitiveProps>()
</script>

<template>
<Primitive v-bind="$props" :class="cn('mt-12 flex flex-col p-3.5 mx-auto max-w-screen-md', $attrs.class)">
<slot />
</Primitive>
</template>
19 changes: 19 additions & 0 deletions app/components/settings/content.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts" setup>
const props = defineProps<{
category: SettingsCategory
}>()

const Content = defineAsyncComponent(async () => {
try {
const c = await import(`~/components/settings/content/${props.category}.vue`)
return c
}
catch {
return h('div', { innerHTML: `Please create ~/components/settings/content/${props.category}.vue` })
Comment thread
jvxz marked this conversation as resolved.
Outdated
}
})
</script>

<template>
<Content />
</template>
14 changes: 14 additions & 0 deletions app/components/settings/content/accessibility.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script lang="ts" setup>
const settings = useSettings()
</script>

<template>
<SettingsContentLayout>
<SettingsFormSwitch
v-model:model-value="settings.accessibility.uiAnimations"
:label="SETTINGS_ITEM_METADATA.accessibility.uiAnimations.title"
:description="SETTINGS_ITEM_METADATA.accessibility.uiAnimations.description"
>
</SettingsFormSwitch>
</SettingsContentLayout>
</template>
15 changes: 15 additions & 0 deletions app/components/settings/content/appearance.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts" setup>
const settings = useSettings()
</script>

<template>
<SettingsContentLayout>
<SettingsFormAutocomplete
v-model:model-value="settings.appearance.font"
:label="SETTINGS_ITEM_METADATA.appearance.font.title"
:description="SETTINGS_ITEM_METADATA.appearance.font.description"
:options="SETTINGS_ITEM_METADATA.appearance.font.options"
:default-option="DEFAULT_SETTINGS.appearance.font"
/>
</SettingsContentLayout>
</template>
58 changes: 38 additions & 20 deletions app/components/settings/dialog.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script lang="ts" setup>
const { open } = useSettingsDialog()
const { open, tab } = useSettingsDialog()
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated

onUnmounted(() => tab.value = SETTINGS_DEFAULT_TAB)
Comment thread
jvxz marked this conversation as resolved.
Outdated
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
</script>

<template>
Expand All @@ -16,26 +18,42 @@ const { open } = useSettingsDialog()
)"
@close-auto-focus.prevent
>
<div class="flex">
<LazySettingsDialogSidebar />
<header class="pe-2.5 ps-4 border-b flex flex-1 h-header-height items-center">
<DialogTitle class="font-medium">
My Account
</DialogTitle>
<div class="flex-1" />
<DialogClose
:class="cn(
interactiveStyles.base,
interactiveStyles.variant.ghost,
interactiveStyles.size.icon,
'inline-flex size-8 items-center justify-center opacity-70',
)"
<TabsRoot
v-model:model-value="tab"
activation-mode="manual"
orientation="vertical"
class="flex"
>
<SettingsDialogSidebar />

<div class="flex flex-1 flex-col">
<header class="pe-2.5 ps-4 border-b flex shrink-0 h-header-height items-center justify-between">
<DialogTitle class="font-medium">
My Account
</DialogTitle>

<DialogClose
:class="cn(
interactiveStyles.base,
interactiveStyles.variant.ghost,
interactiveStyles.size.icon,
'inline-flex size-8 items-center justify-center opacity-70',
)"
>
<Icon name="tabler:x" class="size-5" />
<VisuallyHidden>Close</VisuallyHidden>
</DialogClose>
</header>

<TabsContent
v-for="setting in SETTINGS_CATEGORY_METADATA"
:key="setting.key"
:value="setting.key"
>
<Icon name="tabler:x" class="size-5" />
<span class="sr-only">Close</span>
</DialogClose>
</header>
</div>
<SettingsContent :category="setting.key" />
</TabsContent>
</div>
</TabsRoot>
</DialogContent>
</DialogPortal>
</UDialogRoot>
Expand Down
31 changes: 29 additions & 2 deletions app/components/settings/dialog/sidebar.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<script lang="ts" setup>
const { self } = useSelf()
const { tab } = useSettingsDialog()

onUnmounted(() => tab.value = SETTINGS_DEFAULT_TAB)
</script>

<template>
<div class="p-4 rounded-l-lg bg-background shrink-0 w-64">
<UButton variant="ghost" class="group text-foreground font-normal p-3 gap-3.5 h-[4.25rem] w-full items-center justify-between">
<div class="p-4 rounded-l-lg bg-background flex shrink-0 flex-col gap-4 w-64">
<UButton variant="ghost" class="group text-foreground font-normal shrink-0 gap-3.5 h-14 w-full items-center justify-center">
<MatrixAvatar
:user="self?.userId"
:size="48"
Expand All @@ -18,5 +21,29 @@ const { self } = useSelf()
</span>
</div>
</UButton>
<div class="grid place-items-center">
<UInput placeholder="Search" />
</div>

<TabsList class="tab-list flex flex-col gap-1 isolate">
<TabsTrigger
v-for="entry in SETTINGS_CATEGORY_METADATA"
:key="entry.key"
:value="entry.key"
as-child
@pointerdown.prevent
@click="tab = entry.key"
>
<UButton
variant="ghost"
class="justify-start data-[active]:text-foreground active:bg-card-lighter/50 data-[active]:bg-card-lighter/50 hover:bg-card-lighter/50 data-[active]:anchor-name-item"
>
<Icon :name="entry.icon" class="size-4" />
{{ upperFirst(entry.title) }}
</UButton>
</TabsTrigger>

<div class="rounded bg-card-lighter pointer-events-none duration-100 absolute position-anchor-item anchor-inset ease-snappy -z-1" />
</TabsList>
</div>
</template>
35 changes: 35 additions & 0 deletions app/components/settings/form/autocomplete.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script lang="ts" setup generic="T extends string[] | undefined">
import type { SettingsFormPrimitiveProps } from './types'

type ValueType = T extends string[] ? T[number] : string

defineProps<SettingsFormPrimitiveProps & {
options: T
defaultOption?: ValueType
}>()

const modelValue = defineModel<ValueType>()
</script>

<template>
<SettingsFormPrimitive v-bind="$props">
<USelectRoot v-model:model-value="modelValue">
<USelectTrigger class="min-w-48">
<USelectValue>
{{ modelValue }}
</USelectValue>
</USelectTrigger>
<USelectContent>
<USelectItem
v-for="option in options"
:key="option"
:value="option"
>
<USelectItemText>
{{ option }}
</USelectItemText>
</USelectItem>
</USelectContent>
</USelectRoot>
</SettingsFormPrimitive>
</template>
21 changes: 21 additions & 0 deletions app/components/settings/form/primitive.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts" setup>
import type { FormPrimitiveProps } from '~/components/form/primitive.vue'

defineProps<FormPrimitiveProps & { description?: string }>()
</script>

<template>
<FormPrimitive v-bind="$props" class="flex gap-2 items-center justify-between space-y-0">
<template #label="{ id }">
<div class="flex flex-col gap-1">
<ULabel :for="id" class="text-base font-medium m-0">
{{ label }}
</ULabel>
<p v-if="description" class="text-sm text-muted-foreground">
{{ description }}
</p>
</div>
</template>
<slot />
</FormPrimitive>
</template>
23 changes: 23 additions & 0 deletions app/components/settings/form/switch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts" setup>
import type { SettingsFormPrimitiveProps } from './types'

defineProps<SettingsFormPrimitiveProps>()

const modelValue = defineModel<boolean>()
</script>

<template>
<FormPrimitive v-bind="$props" class="flex gap-2 items-center justify-between space-y-0">
<template #label="{ id }">
<div class="flex flex-col gap-1">
<ULabel :for="id" class="text-base font-medium m-0">
{{ label }}
</ULabel>
<p v-if="description" class="text-sm text-muted-foreground">
{{ description }}
</p>
</div>
</template>
<USwitch v-model="modelValue" />
</FormPrimitive>
</template>
5 changes: 5 additions & 0 deletions app/components/settings/form/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { FormPrimitiveProps } from '~/components/form/primitive.vue'

export type SettingsFormPrimitiveProps = FormPrimitiveProps & {
description?: string
}
2 changes: 2 additions & 0 deletions app/composables/use-settings-dialog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export const useSettingsDialog = createGlobalState(() => {
const open = shallowRef(false)
const tab = shallowRef<SettingsCategory>(SETTINGS_DEFAULT_TAB)

return {
open,
tab,
}
})
1 change: 1 addition & 0 deletions app/composables/use-settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const useSettings = () => useNuxtApp().$settings
4 changes: 4 additions & 0 deletions app/constants/matrix/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { EventType } from 'matrix-js-sdk'
import * as v from 'valibot'

export const EventTypeSchema = v.picklist(objectValues(EventType))
Loading
Loading