feat: Add an option on the dashboard to allow switching help center layout (#14491)
<img width="633" height="431" alt="Screenshot 2026-05-18 at 12 32 55 PM" src="https://github.com/user-attachments/assets/682d4c5f-4c76-465b-8d2f-92fbc2bb2a40" /> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com>
This commit is contained in:
co-authored by
Muhsin Keloth
Sivin Varghese
iamsivin
parent
497d34c097
commit
6560dbb68d
+290
@@ -0,0 +1,290 @@
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import classicLayoutPreview from './classic-layout-preview.svg?raw';
|
||||
import documentationLayoutPreview from './documentation-layout-preview.svg?raw';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
import RadioCard from 'dashboard/components-next/radioCard/RadioCard.vue';
|
||||
|
||||
const props = defineProps({
|
||||
activePortal: { type: Object, required: true },
|
||||
isFetching: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['updatePortalConfiguration']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const PORTAL_LAYOUTS = {
|
||||
CLASSIC: 'classic',
|
||||
DOCUMENTATION: 'documentation',
|
||||
};
|
||||
|
||||
// `prefix` is the link the help center auto-fills; the DB only stores the handle.
|
||||
const SOCIAL_PLATFORMS = [
|
||||
{
|
||||
key: 'facebook',
|
||||
label: 'Facebook',
|
||||
icon: 'i-ri-facebook-circle-fill',
|
||||
prefix: 'facebook.com/',
|
||||
},
|
||||
{ key: 'x', label: 'X', icon: 'i-ri-twitter-x-fill', prefix: 'x.com/' },
|
||||
{
|
||||
key: 'instagram',
|
||||
label: 'Instagram',
|
||||
icon: 'i-ri-instagram-fill',
|
||||
prefix: 'instagram.com/',
|
||||
},
|
||||
{
|
||||
key: 'linkedin',
|
||||
label: 'LinkedIn',
|
||||
icon: 'i-ri-linkedin-box-fill',
|
||||
prefix: 'linkedin.com/',
|
||||
},
|
||||
{
|
||||
key: 'youtube',
|
||||
label: 'YouTube',
|
||||
icon: 'i-ri-youtube-fill',
|
||||
prefix: 'youtube.com/',
|
||||
},
|
||||
{
|
||||
key: 'tiktok',
|
||||
label: 'TikTok',
|
||||
icon: 'i-ri-tiktok-fill',
|
||||
prefix: 'tiktok.com/',
|
||||
},
|
||||
{
|
||||
key: 'github',
|
||||
label: 'GitHub',
|
||||
icon: 'i-ri-github-fill',
|
||||
prefix: 'github.com/',
|
||||
},
|
||||
{
|
||||
key: 'whatsapp',
|
||||
label: 'WhatsApp',
|
||||
icon: 'i-ri-whatsapp-fill',
|
||||
prefix: 'wa.me/',
|
||||
},
|
||||
];
|
||||
|
||||
const portalConfig = computed(() => props.activePortal?.config || {});
|
||||
|
||||
const state = reactive({
|
||||
layout: PORTAL_LAYOUTS.CLASSIC,
|
||||
socialProfiles: {},
|
||||
});
|
||||
const visiblePlatforms = ref([]);
|
||||
const showAddMenu = ref(false);
|
||||
|
||||
let originalSnapshot = '';
|
||||
|
||||
const platformByKey = key => SOCIAL_PLATFORMS.find(p => p.key === key);
|
||||
|
||||
const trimmedHandle = key => (state.socialProfiles[key] || '').trim();
|
||||
|
||||
const buildSocialProfiles = () =>
|
||||
visiblePlatforms.value.reduce((acc, key) => {
|
||||
const handle = trimmedHandle(key);
|
||||
if (handle) acc[key] = handle;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const snapshot = () =>
|
||||
JSON.stringify({ layout: state.layout, social: buildSocialProfiles() });
|
||||
|
||||
const resetFromPortal = () => {
|
||||
const savedProfiles = portalConfig.value.social_profiles || {};
|
||||
state.layout = portalConfig.value.layout || PORTAL_LAYOUTS.CLASSIC;
|
||||
state.socialProfiles = SOCIAL_PLATFORMS.reduce((acc, { key }) => {
|
||||
acc[key] = savedProfiles[key] || '';
|
||||
return acc;
|
||||
}, {});
|
||||
visiblePlatforms.value = SOCIAL_PLATFORMS.map(p => p.key).filter(key =>
|
||||
(savedProfiles[key] || '').trim()
|
||||
);
|
||||
originalSnapshot = snapshot();
|
||||
};
|
||||
|
||||
watch(() => props.activePortal, resetFromPortal, {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
});
|
||||
|
||||
const hasChanges = computed(() => snapshot() !== originalSnapshot);
|
||||
|
||||
const visiblePlatformDetails = computed(() =>
|
||||
visiblePlatforms.value.map(platformByKey)
|
||||
);
|
||||
|
||||
const addablePlatforms = computed(() =>
|
||||
SOCIAL_PLATFORMS.filter(p => !visiblePlatforms.value.includes(p.key)).map(
|
||||
p => ({ label: p.label, value: p.key, action: p.key, icon: p.icon })
|
||||
)
|
||||
);
|
||||
|
||||
const addPlatform = ({ value }) => {
|
||||
if (!visiblePlatforms.value.includes(value)) {
|
||||
visiblePlatforms.value.push(value);
|
||||
}
|
||||
showAddMenu.value = false;
|
||||
};
|
||||
|
||||
const removePlatform = key => {
|
||||
visiblePlatforms.value = visiblePlatforms.value.filter(k => k !== key);
|
||||
state.socialProfiles[key] = '';
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
emit('updatePortalConfiguration', {
|
||||
id: props.activePortal.id,
|
||||
slug: props.activePortal.slug,
|
||||
config: {
|
||||
layout: state.layout,
|
||||
social_profiles: buildSocialProfiles(),
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col w-full gap-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h6 class="text-base font-medium text-n-slate-12">
|
||||
{{ t('HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.HEADER') }}
|
||||
</h6>
|
||||
<span class="text-sm text-n-slate-11">
|
||||
{{ t('HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.DESCRIPTION') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<section class="flex flex-col gap-3">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-n-slate-11">
|
||||
<RadioCard
|
||||
:id="PORTAL_LAYOUTS.CLASSIC"
|
||||
:is-active="state.layout === PORTAL_LAYOUTS.CLASSIC"
|
||||
:label="
|
||||
t('HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.LAYOUT.CLASSIC.TITLE')
|
||||
"
|
||||
:description="
|
||||
t(
|
||||
'HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.LAYOUT.CLASSIC.DESCRIPTION'
|
||||
)
|
||||
"
|
||||
@select="value => (state.layout = value)"
|
||||
>
|
||||
<div
|
||||
class="w-full mt-2 rounded-md overflow-hidden border border-solid border-n-weak bg-n-slate-2 dark:bg-n-slate-1"
|
||||
>
|
||||
<span v-dompurify-html="classicLayoutPreview" />
|
||||
</div>
|
||||
</RadioCard>
|
||||
|
||||
<RadioCard
|
||||
:id="PORTAL_LAYOUTS.DOCUMENTATION"
|
||||
beta
|
||||
:is-active="state.layout === PORTAL_LAYOUTS.DOCUMENTATION"
|
||||
:label="
|
||||
t('HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.LAYOUT.SIDEBAR.TITLE')
|
||||
"
|
||||
:description="
|
||||
t(
|
||||
'HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.LAYOUT.SIDEBAR.DESCRIPTION'
|
||||
)
|
||||
"
|
||||
@select="value => (state.layout = value)"
|
||||
>
|
||||
<div
|
||||
class="w-full mt-2 rounded-md overflow-hidden border border-solid border-n-weak bg-n-slate-2 dark:bg-n-slate-1"
|
||||
>
|
||||
<span v-dompurify-html="documentationLayoutPreview" />
|
||||
</div>
|
||||
</RadioCard>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="state.layout === PORTAL_LAYOUTS.DOCUMENTATION"
|
||||
class="flex flex-col gap-3"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h6 class="text-sm font-medium text-n-slate-12">
|
||||
{{
|
||||
t('HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.SOCIAL_LINKS.HEADER')
|
||||
}}
|
||||
</h6>
|
||||
<span class="text-sm text-n-slate-11">
|
||||
{{
|
||||
t(
|
||||
'HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.SOCIAL_LINKS.DESCRIPTION'
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="platform in visiblePlatformDetails"
|
||||
:key="platform.key"
|
||||
class="flex items-center h-10 gap-1.5 px-3 rounded-lg outline outline-1 outline-n-weak focus-within:outline-n-brand"
|
||||
>
|
||||
<Icon :icon="platform.icon" class="size-4 shrink-0 text-n-slate-11" />
|
||||
<span class="text-sm shrink-0 text-n-slate-10">{{
|
||||
platform.prefix
|
||||
}}</span>
|
||||
<input
|
||||
v-model="state.socialProfiles[platform.key]"
|
||||
type="text"
|
||||
class="flex-1 min-w-0 text-sm bg-transparent outline-none reset-base text-n-slate-12 placeholder:text-n-slate-10"
|
||||
:placeholder="
|
||||
t(
|
||||
'HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.SOCIAL_LINKS.PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
/>
|
||||
<Button
|
||||
icon="i-lucide-x"
|
||||
color="slate"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:aria-label="
|
||||
t('HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.SOCIAL_LINKS.REMOVE')
|
||||
"
|
||||
@click="removePlatform(platform.key)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="addablePlatforms.length"
|
||||
v-on-clickaway="() => (showAddMenu = false)"
|
||||
class="relative"
|
||||
>
|
||||
<Button
|
||||
:label="
|
||||
t('HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.SOCIAL_LINKS.ADD')
|
||||
"
|
||||
icon="i-lucide-plus"
|
||||
color="slate"
|
||||
variant="faded"
|
||||
size="sm"
|
||||
@click="showAddMenu = !showAddMenu"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showAddMenu"
|
||||
:menu-items="addablePlatforms"
|
||||
class="mt-1 w-52 top-full ltr:left-0 rtl:right-0"
|
||||
@action="addPlatform"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
:label="t('HELP_CENTER.PORTAL_SETTINGS.LAYOUT_CONTENT.SAVE')"
|
||||
:disabled="!hasChanges || isFetching"
|
||||
@click="handleSave"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
+7
@@ -7,6 +7,7 @@ import { useMapGetter } from 'dashboard/composables/store.js';
|
||||
import HelpCenterLayout from 'dashboard/components-next/HelpCenter/HelpCenterLayout.vue';
|
||||
import PortalBaseSettings from 'dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue';
|
||||
import PortalConfigurationSettings from './PortalConfigurationSettings.vue';
|
||||
import PortalLayoutContentSettings from './PortalLayoutContentSettings.vue';
|
||||
import ConfirmDeletePortalDialog from 'dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/ConfirmDeletePortalDialog.vue';
|
||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
@@ -102,6 +103,12 @@ const handleDeletePortal = () => {
|
||||
@send-cname-instructions="handleSendCnameInstructions"
|
||||
/>
|
||||
<div class="w-full h-px bg-n-weak" />
|
||||
<PortalLayoutContentSettings
|
||||
:active-portal="activePortal"
|
||||
:is-fetching="isFetching"
|
||||
@update-portal-configuration="handleUpdatePortalConfiguration"
|
||||
/>
|
||||
<div class="w-full h-px bg-n-weak" />
|
||||
<div class="flex items-end justify-between w-full gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h6 class="text-base font-medium text-n-slate-12">
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120" class="w-full h-auto block" aria-hidden="true">
|
||||
<rect x="0" y="0" width="200" height="14" fill="currentColor" opacity="0.1"/>
|
||||
<rect x="14" y="5" width="20" height="4" rx="1" fill="currentColor" opacity="0.3"/>
|
||||
<rect x="14" y="22" width="64" height="5" rx="1" fill="currentColor" opacity="0.32"/>
|
||||
<rect x="14" y="32" width="128" height="9" rx="2" fill="currentColor" opacity="0.18"/>
|
||||
<rect x="14" y="70" width="82" height="19" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<rect x="104" y="70" width="82" height="19" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<rect x="14" y="93" width="82" height="19" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<rect x="104" y="93" width="82" height="19" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 818 B |
+18
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120" class="w-full h-auto block" aria-hidden="true">
|
||||
<rect x="0" y="0" width="200" height="14" fill="currentColor" opacity="0.1"/>
|
||||
<rect x="6" y="5" width="20" height="4" rx="1" fill="currentColor" opacity="0.3"/>
|
||||
<rect x="0" y="14" width="50" height="106" fill="currentColor" opacity="0.07"/>
|
||||
<rect x="6" y="22" width="38" height="4" rx="1" fill="currentColor" opacity="0.25"/>
|
||||
<rect x="6" y="32" width="30" height="3" rx="1" fill="currentColor" opacity="0.18"/>
|
||||
<rect x="6" y="40" width="35" height="3" rx="1" fill="currentColor" opacity="0.18"/>
|
||||
<rect x="6" y="48" width="28" height="3" rx="1" fill="currentColor" opacity="0.18"/>
|
||||
<rect x="6" y="56" width="32" height="3" rx="1" fill="currentColor" opacity="0.18"/>
|
||||
<rect x="60" y="25" width="80" height="6" rx="1" fill="currentColor" opacity="0.35"/>
|
||||
<rect x="60" y="38" width="120" height="8" rx="2" fill="currentColor" opacity="0.18"/>
|
||||
<rect x="60" y="55" width="37" height="22" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<rect x="101" y="55" width="37" height="22" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<rect x="142" y="55" width="37" height="22" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<rect x="60" y="82" width="37" height="22" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<rect x="101" y="82" width="37" height="22" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<rect x="142" y="82" width="37" height="22" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Label from 'dashboard/components-next/label/Label.vue';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -30,10 +31,16 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
beta: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['select']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const handleChange = () => {
|
||||
if (!props.isActive && !props.disabled) {
|
||||
emit('select', props.id);
|
||||
@@ -42,22 +49,23 @@ const handleChange = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="cursor-pointer rounded-xl outline outline-1 p-4 transition-all duration-200 bg-n-solid-1 py-4 ltr:pl-4 rtl:pr-4 ltr:pr-6 rtl:pl-6"
|
||||
<label
|
||||
:for="id"
|
||||
class="rounded-xl outline outline-1 p-4 transition-all duration-200 bg-n-solid-1 py-4 ltr:pl-4 rtl:pr-4 ltr:pr-6 rtl:pl-6 focus-within:has-[:focus-visible]:ring-2 focus-within:has-[:focus-visible]:ring-n-strong"
|
||||
:class="[
|
||||
disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
|
||||
isActive ? 'outline-n-blue-9' : 'outline-n-weak',
|
||||
!disabled && !isActive ? 'hover:outline-n-strong' : '',
|
||||
]"
|
||||
@click="handleChange"
|
||||
>
|
||||
<div class="flex flex-col gap-2 items-start">
|
||||
<div class="flex items-center justify-between w-full gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-heading-3 text-n-slate-12">
|
||||
<h3 class="text-heading-2 text-n-slate-12">
|
||||
{{ label }}
|
||||
</h3>
|
||||
<Label v-if="disabled" :label="disabledLabel" color="amber" compact />
|
||||
<Label v-if="beta" :label="t('GENERAL.BETA')" color="blue" compact />
|
||||
</div>
|
||||
<input
|
||||
:id="`${id}`"
|
||||
@@ -66,7 +74,7 @@ const handleChange = () => {
|
||||
:name="id"
|
||||
:disabled="disabled"
|
||||
type="radio"
|
||||
class="h-4 w-4 border-n-slate-6 text-n-brand focus:ring-n-brand focus:ring-offset-0 flex-shrink-0"
|
||||
class="shadow cursor-pointer grid place-items-center border-2 border-n-strong appearance-none rounded-full w-5 h-5 checked:bg-n-brand before:content-[''] before:bg-n-brand before:border-4 before:rounded-full before:border-n-strong checked:before:w-[18px] checked:before:h-[18px] checked:border checked:border-n-brand"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
@@ -75,5 +83,5 @@ const handleChange = () => {
|
||||
</p>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
@@ -866,6 +866,28 @@
|
||||
},
|
||||
"EDIT_CONFIGURATION": "Edit configuration"
|
||||
},
|
||||
"LAYOUT_CONTENT": {
|
||||
"HEADER": "Appearance",
|
||||
"DESCRIPTION": "Pick the layout that fits how your visitors read.",
|
||||
"LAYOUT": {
|
||||
"CLASSIC": {
|
||||
"TITLE": "Classic",
|
||||
"DESCRIPTION": "A welcoming home page with search and featured topics."
|
||||
},
|
||||
"SIDEBAR": {
|
||||
"TITLE": "Documentation",
|
||||
"DESCRIPTION": "Side-by-side navigation that keeps every guide a click away."
|
||||
}
|
||||
},
|
||||
"SOCIAL_LINKS": {
|
||||
"HEADER": "Social links",
|
||||
"DESCRIPTION": "Add the handle for each network and your help center builds the full link. Shown in the documentation layout footer.",
|
||||
"PLACEHOLDER": "handle",
|
||||
"ADD": "Add social link",
|
||||
"REMOVE": "Remove"
|
||||
},
|
||||
"SAVE": "Save changes"
|
||||
},
|
||||
"API": {
|
||||
"CREATE_PORTAL": {
|
||||
"SUCCESS_MESSAGE": "Portal created successfully",
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ defineEmits(['update']);
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col sm:flex-row md:flex-col xl:flex-row items-start gap-4 mt-3 min-w-0"
|
||||
class="flex flex-col sm:flex-row flex-col items-start gap-4 mt-3 min-w-0"
|
||||
>
|
||||
<RadioCard
|
||||
id="disabled"
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
lightImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
darkImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="flex flex-col gap-4 w-full h-fit p-4 rounded-md border border-n-weak dark:border-n-weak"
|
||||
:class="{
|
||||
'border-n-brand ': active,
|
||||
}"
|
||||
>
|
||||
<div class="flex flex-col gap-2 items-center w-full rounded-t-[5px]">
|
||||
<div class="grid grid-cols-[1fr_auto] items-center w-full gap-1">
|
||||
<div class="overflow-hidden text-heading-2 text-n-slate-12 text-start">
|
||||
<span class="block truncate">{{ title }}</span>
|
||||
</div>
|
||||
<input
|
||||
:checked="active"
|
||||
type="radio"
|
||||
:name="`hotkey-${title}`"
|
||||
class="shadow cursor-pointer grid place-items-center border-2 border-n-strong appearance-none rounded-full w-5 h-5 checked:bg-n-brand before:content-[''] before:bg-n-brand before:border-4 before:rounded-full before:border-n-strong checked:before:w-[18px] checked:before:h-[18px] checked:border checked:border-n-brand"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-n-slate-11 line-clamp-2 text-body-para text-start">
|
||||
{{ description }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<img
|
||||
:src="lightImage"
|
||||
:alt="`Light themed image for ${title}`"
|
||||
class="block object-cover w-full dark:hidden"
|
||||
/>
|
||||
<img
|
||||
:src="darkImage"
|
||||
:alt="`Dark themed image for ${title}`"
|
||||
class="hidden object-cover w-full dark:block"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
@@ -13,7 +13,6 @@ import UserBasicDetails from './UserBasicDetails.vue';
|
||||
import MessageSignature from './MessageSignature.vue';
|
||||
import FontSize from './FontSize.vue';
|
||||
import UserLanguageSelect from './UserLanguageSelect.vue';
|
||||
import HotKeyCard from './HotKeyCard.vue';
|
||||
import ChangePassword from './ChangePassword.vue';
|
||||
import NotificationPreferences from './NotificationPreferences.vue';
|
||||
import AudioNotifications from './AudioNotifications.vue';
|
||||
@@ -22,6 +21,7 @@ import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
||||
import AccessToken from './AccessToken.vue';
|
||||
import MfaSettingsCard from './MfaSettingsCard.vue';
|
||||
import Policy from 'dashboard/components/policy.vue';
|
||||
import RadioCard from 'dashboard/components-next/radioCard/RadioCard.vue';
|
||||
import {
|
||||
ROLES,
|
||||
CONVERSATION_PERMISSIONS,
|
||||
@@ -36,7 +36,7 @@ export default {
|
||||
UserProfilePicture,
|
||||
Policy,
|
||||
UserBasicDetails,
|
||||
HotKeyCard,
|
||||
RadioCard,
|
||||
ChangePassword,
|
||||
NotificationPreferences,
|
||||
AudioNotifications,
|
||||
@@ -268,26 +268,27 @@ export default {
|
||||
<div
|
||||
class="flex flex-col justify-between w-full gap-5 sm:gap-4 sm:flex-row"
|
||||
>
|
||||
<button
|
||||
<RadioCard
|
||||
v-for="hotKey in hotKeys"
|
||||
:id="hotKey.key"
|
||||
:key="hotKey.key"
|
||||
class="px-0 reset-base w-full sm:flex-1 rounded-xl outline-1 outline"
|
||||
:class="
|
||||
isEditorHotKeyEnabled(hotKey.key)
|
||||
? 'outline-n-brand/30'
|
||||
: 'outline-n-weak'
|
||||
"
|
||||
:label="hotKey.title"
|
||||
:description="hotKey.description"
|
||||
:is-active="isEditorHotKeyEnabled(hotKey.key)"
|
||||
class="sm:flex-1"
|
||||
@select="toggleHotKey"
|
||||
>
|
||||
<HotKeyCard
|
||||
:key="hotKey.title"
|
||||
:title="hotKey.title"
|
||||
:description="hotKey.description"
|
||||
:light-image="hotKey.lightImage"
|
||||
:dark-image="hotKey.darkImage"
|
||||
:active="isEditorHotKeyEnabled(hotKey.key)"
|
||||
@click="toggleHotKey(hotKey.key)"
|
||||
<img
|
||||
:src="hotKey.lightImage"
|
||||
:alt="`Light themed image for ${hotKey.title}`"
|
||||
class="block object-cover w-full dark:hidden"
|
||||
/>
|
||||
</button>
|
||||
<img
|
||||
:src="hotKey.darkImage"
|
||||
:alt="`Dark themed image for ${hotKey.title}`"
|
||||
class="hidden object-cover w-full dark:block"
|
||||
/>
|
||||
</RadioCard>
|
||||
</div>
|
||||
</SectionLayout>
|
||||
<SectionLayout
|
||||
|
||||
Reference in New Issue
Block a user