refactor: update welcome modal and color picker (#1466)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a new "full" size option to dialogs, allowing dialogs to fill
the entire screen.
* Introduced a prop to control the visibility of the close button on
dialogs.

* **Refactor**
* Updated the Welcome Modal to use the new Dialog component with
improved layout and slot usage.
* Reworked dialog content structure for better flexibility and styling.

* **Tests**
* Updated tests to reflect the switch from the Modal component to the
Dialog component, including new mocks and assertions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mdatelle <mike@datelle.net>
Co-authored-by: Eli Bosley <ekbosley@gmail.com>
This commit is contained in:
Michael Datelle
2025-07-08 15:24:29 -04:00
committed by GitHub
parent 5a3e7a91eb
commit 0379845618
5 changed files with 94 additions and 70 deletions

View File

@@ -14,9 +14,9 @@ import WelcomeModal from '~/components/Activation/WelcomeModal.ce.vue';
const mockT = (key: string, args?: unknown[]) => (args ? `${key} ${JSON.stringify(args)}` : key);
const mockComponents = {
Modal: {
Dialog: {
template: `
<div data-testid="modal" v-if="open" role="dialog" aria-modal="true">
<div data-testid="modal" v-if="modelValue" role="dialog" aria-modal="true">
<div data-testid="modal-header"><slot name="header" /></div>
<div data-testid="modal-body"><slot /></div>
<div data-testid="modal-footer"><slot name="footer" /></div>
@@ -24,20 +24,13 @@ const mockComponents = {
</div>
`,
props: [
't',
'open',
'showCloseX',
'modelValue',
'title',
'titleInMain',
'description',
'overlayColor',
'overlayOpacity',
'maxWidth',
'modalVerticalCenter',
'disableShadow',
'disableOverlayClose',
'showFooter',
'size',
],
emits: ['close'],
emits: ['update:modelValue'],
},
ActivationPartnerLogo: {
template: '<div data-testid="partner-logo"></div>',
@@ -80,6 +73,33 @@ vi.mock('~/store/theme', () => ({
useThemeStore: () => mockThemeStore,
}));
vi.mock('@unraid/ui', () => ({
BrandButton: {
template:
'<button data-testid="brand-button" :disabled="disabled" @click="$emit(\'click\')">{{ text }}</button>',
props: ['text', 'disabled'],
emits: ['click'],
},
Dialog: {
template: `
<div data-testid="modal" v-if="modelValue" role="dialog" aria-modal="true">
<div data-testid="modal-header"><slot name="header" /></div>
<div data-testid="modal-body"><slot /></div>
<div data-testid="modal-footer"><slot name="footer" /></div>
<div data-testid="modal-subfooter"><slot name="subFooter" /></div>
</div>
`,
props: [
'modelValue',
'title',
'description',
'showFooter',
'size',
],
emits: ['update:modelValue'],
},
}));
describe('Activation/WelcomeModal.ce.vue', () => {
let mockSetProperty: ReturnType<typeof vi.fn>;
let mockQuerySelector: ReturnType<typeof vi.fn>;
@@ -171,13 +191,13 @@ describe('Activation/WelcomeModal.ce.vue', () => {
expect(wrapper.find('[data-testid="modal"]').exists()).toBe(false);
});
it('disables the Create a password button when loading', () => {
it('does not disable the Create a password button when loading', () => {
mockActivationCodeDataStore.loading.value = true;
const wrapper = mountComponent();
const button = wrapper.find('[data-testid="brand-button"]');
expect(button.attributes('disabled')).toBe('');
expect(button.attributes('disabled')).toBe(undefined);
});
it('renders activation steps with correct active step', () => {
@@ -245,26 +265,20 @@ describe('Activation/WelcomeModal.ce.vue', () => {
});
describe('Modal properties', () => {
it('passes correct props to Modal component', () => {
it('passes correct props to Dialog component', () => {
const wrapper = mountComponent();
const modal = wrapper.findComponent(mockComponents.Modal);
const dialog = wrapper.find('[data-testid="modal"]');
expect(modal.props()).toMatchObject({
showCloseX: false,
overlayColor: 'bg-background',
overlayOpacity: 'bg-opacity-100',
maxWidth: 'max-w-800px',
disableShadow: true,
modalVerticalCenter: false,
disableOverlayClose: true,
});
expect(dialog.exists()).toBe(true);
// The Dialog component is rendered correctly
expect(wrapper.html()).toContain('data-testid="modal"');
});
it('renders modal with correct accessibility attributes', () => {
const wrapper = mountComponent();
const modal = wrapper.findComponent(mockComponents.Modal);
const dialog = wrapper.find('[data-testid="modal"]');
expect(modal.attributes()).toMatchObject({
expect(dialog.attributes()).toMatchObject({
role: 'dialog',
'aria-modal': 'true',
});

View File

@@ -3,17 +3,16 @@ import { computed, ref, watchEffect } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { BrandButton } from '@unraid/ui';
import { BrandButton, Dialog } from '@unraid/ui';
import ActivationPartnerLogo from '~/components/Activation/ActivationPartnerLogo.vue';
import ActivationSteps from '~/components/Activation/ActivationSteps.vue';
import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData';
import Modal from '~/components/Modal.vue';
import { useThemeStore } from '~/store/theme';
const { t } = useI18n();
const { partnerInfo, loading } = storeToRefs(useActivationCodeDataStore());
const { partnerInfo } = storeToRefs(useActivationCodeDataStore());
const { setTheme } = useThemeStore();
@@ -31,12 +30,6 @@ const title = computed<string>(() =>
: t('Welcome to Unraid!')
);
const description = computed<string>(() =>
t(
`First, you'll create your device's login credentials, then you'll activate your Unraid license—your device's operating system (OS).`
)
);
const showModal = ref<boolean>(true);
const dropdownHide = () => {
showModal.value = false;
@@ -65,37 +58,34 @@ watchEffect(() => {
<template>
<div id="modals" ref="modals" class="relative z-[99999]">
<Modal
v-if="showModal"
:t="t"
:open="showModal"
:show-close-x="false"
:title="title"
:title-in-main="partnerInfo?.hasPartnerLogo"
:description="description"
overlay-color="bg-background"
overlay-opacity="bg-opacity-100"
max-width="max-w-800px"
:disable-shadow="true"
:modal-vertical-center="false"
:disable-overlay-close="true"
<Dialog
v-model="showModal"
:show-footer="false"
:show-close-button="false"
size="full"
class="bg-background"
@close="dropdownHide"
>
<template v-if="partnerInfo?.hasPartnerLogo" #header>
<ActivationPartnerLogo />
</template>
<template #footer>
<div class="w-full flex gap-8px justify-center mx-auto">
<BrandButton :text="t('Create a password')" :disabled="loading" @click="dropdownHide" />
<div class="flex flex-col items-center justify-start mt-8">
<div v-if="partnerInfo?.hasPartnerLogo">
<ActivationPartnerLogo />
</div>
</template>
<template #subFooter>
<ActivationSteps :active-step="1" class="mt-6" />
</template>
</Modal>
<h1 class="text-center text-20px sm:text-24px font-semibold mt-4">{{ title }}</h1>
<div class="sm:max-w-lg mx-auto mt-2 text-center">
<p class="text-18px sm:text-20px opacity-75">
{{ t(`First, you'll create your device's login credentials, then you'll activate your Unraid license—your device's operating system (OS).`) }}
</p>
</div>
<div class="flex flex-col justify-start p-6 w-2/4">
<div class="mx-auto mt-6 mb-8">
<BrandButton :text="t('Create a password')" @click="dropdownHide" />
</div>
<ActivationSteps :active-step="1" class="mt-6" />
</div>
</div>
</Dialog>
</div>
</template>