feat(onboarding): polish setup experience
This commit is contained in:
1 parent
66ad01e7ce
commit
c25c1dbac7
34 files changed
+1005
-1172
No files matched your search
@@ -0,0 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { ColorInput, useMantineTheme } from "@mantine/core";
|
||||
import type { ColorInputProps } from "@mantine/core";
|
||||
|
||||
export const BoardColorInput = (props: ColorInputProps) => {
|
||||
const theme = useMantineTheme();
|
||||
|
||||
return <ColorInput {...props} format="hex" swatches={Object.values(theme.colors).map((color) => color[6])} />;
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Combobox, Group, InputBase, Text, useCombobox } from "@mantine/core";
|
||||
import { IconCheck, IconDeviceDesktop, IconMoon, IconSun } from "@tabler/icons-react";
|
||||
|
||||
import type { ColorScheme } from "@homarr/definitions";
|
||||
import { colorSchemes } from "@homarr/definitions";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
const schemeIcons: Record<ColorScheme, React.ElementType> = {
|
||||
auto: IconDeviceDesktop,
|
||||
light: IconSun,
|
||||
dark: IconMoon,
|
||||
};
|
||||
|
||||
interface ColorSchemeComboboxProps {
|
||||
label?: string;
|
||||
value: ColorScheme;
|
||||
onChange: (value: ColorScheme) => void;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
export const ColorSchemeCombobox = ({ label, value, onChange, width }: ColorSchemeComboboxProps) => {
|
||||
const tOptions = useScopedI18n("common.colorScheme.options");
|
||||
const combobox = useCombobox({ onDropdownClose: () => combobox.resetSelectedOption() });
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
store={combobox}
|
||||
onOptionSubmit={(nextValue) => {
|
||||
onChange(nextValue as ColorScheme);
|
||||
combobox.closeDropdown();
|
||||
}}
|
||||
>
|
||||
<Combobox.Target>
|
||||
<InputBase
|
||||
component="button"
|
||||
type="button"
|
||||
pointer
|
||||
label={label}
|
||||
rightSection={<Combobox.Chevron />}
|
||||
rightSectionPointerEvents="none"
|
||||
onClick={() => combobox.toggleDropdown()}
|
||||
variant="filled"
|
||||
w={width}
|
||||
>
|
||||
<OptionItem scheme={value} currentScheme={value} tOptions={tOptions} />
|
||||
</InputBase>
|
||||
</Combobox.Target>
|
||||
<Combobox.Dropdown>
|
||||
<Combobox.Options>
|
||||
{colorSchemes.map((scheme) => (
|
||||
<Combobox.Option value={scheme} key={scheme}>
|
||||
<OptionItem scheme={scheme} currentScheme={value} tOptions={tOptions} showCheck />
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox.Dropdown>
|
||||
</Combobox>
|
||||
);
|
||||
};
|
||||
|
||||
const OptionItem = ({
|
||||
scheme,
|
||||
currentScheme,
|
||||
tOptions,
|
||||
showCheck,
|
||||
}: {
|
||||
scheme: ColorScheme;
|
||||
currentScheme: ColorScheme;
|
||||
tOptions: (key: ColorScheme) => string;
|
||||
showCheck?: boolean;
|
||||
}) => {
|
||||
const Icon = schemeIcons[scheme];
|
||||
|
||||
return (
|
||||
<Group wrap="nowrap" justify="space-between">
|
||||
<Group wrap="nowrap">
|
||||
<Icon size={16} stroke={1.5} />
|
||||
<Text>{tOptions(scheme)}</Text>
|
||||
</Group>
|
||||
{showCheck && scheme === currentScheme && <IconCheck color="currentColor" size={16} />}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
@@ -15,6 +15,9 @@ export { BetaBadge } from "./beta-badge";
|
||||
export { MaskedImage } from "./masked-image";
|
||||
export { MaskedOrNormalImage } from "./masked-or-normal-image";
|
||||
export { LanguageIcon } from "./language-icon";
|
||||
export { LanguageCombobox } from "./language-combobox";
|
||||
export { ColorSchemeCombobox } from "./color-scheme-combobox";
|
||||
export { BoardColorInput } from "./board-color-input";
|
||||
export { Link } from "./link";
|
||||
export {
|
||||
SelectGridLayout,
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Combobox, Group, InputBase, Loader, ScrollArea, Text, useCombobox } from "@mantine/core";
|
||||
import { IconCheck } from "@tabler/icons-react";
|
||||
|
||||
import type { SupportedLanguage } from "@homarr/translation";
|
||||
import { localeConfigurations, supportedLanguages } from "@homarr/translation";
|
||||
|
||||
import { LanguageIcon } from "./language-icon";
|
||||
|
||||
interface LanguageComboboxProps {
|
||||
label?: string;
|
||||
value: SupportedLanguage;
|
||||
onChange: (value: SupportedLanguage) => void;
|
||||
isPending?: boolean;
|
||||
width?: string;
|
||||
withinPortal?: boolean;
|
||||
}
|
||||
|
||||
export const LanguageCombobox = ({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
isPending,
|
||||
width,
|
||||
withinPortal = false,
|
||||
}: LanguageComboboxProps) => {
|
||||
const combobox = useCombobox({ onDropdownClose: () => combobox.resetSelectedOption() });
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
store={combobox}
|
||||
withinPortal={withinPortal}
|
||||
onOptionSubmit={(nextValue) => {
|
||||
onChange(nextValue as SupportedLanguage);
|
||||
combobox.closeDropdown();
|
||||
}}
|
||||
>
|
||||
<Combobox.Target>
|
||||
<InputBase
|
||||
component="button"
|
||||
type="button"
|
||||
pointer
|
||||
label={label}
|
||||
leftSection={isPending ? <Loader size={16} /> : null}
|
||||
rightSection={<Combobox.Chevron />}
|
||||
rightSectionPointerEvents="none"
|
||||
onClick={() => combobox.toggleDropdown()}
|
||||
variant="filled"
|
||||
w={width}
|
||||
>
|
||||
<OptionItem currentLocale={value} localeKey={value} />
|
||||
</InputBase>
|
||||
</Combobox.Target>
|
||||
<Combobox.Dropdown>
|
||||
<ScrollArea h={300}>
|
||||
<Combobox.Options>
|
||||
{supportedLanguages.map((locale) => (
|
||||
<Combobox.Option value={locale} key={locale}>
|
||||
<OptionItem currentLocale={value} localeKey={locale} showCheck />
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</ScrollArea>
|
||||
</Combobox.Dropdown>
|
||||
</Combobox>
|
||||
);
|
||||
};
|
||||
|
||||
const OptionItem = ({
|
||||
currentLocale,
|
||||
localeKey,
|
||||
showCheck,
|
||||
}: {
|
||||
currentLocale: SupportedLanguage;
|
||||
localeKey: SupportedLanguage;
|
||||
showCheck?: boolean;
|
||||
}) => (
|
||||
<Group wrap="nowrap" justify="space-between">
|
||||
<Group wrap="nowrap">
|
||||
<LanguageIcon icon={localeConfigurations[localeKey].icon} />
|
||||
<Group wrap="nowrap" gap="xs">
|
||||
<Text>{localeConfigurations[localeKey].name}</Text>
|
||||
<Text size="xs" c="dimmed" inherit>
|
||||
({localeConfigurations[localeKey].translatedName})
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
{showCheck && localeKey === currentLocale && <IconCheck color="currentColor" size={16} />}
|
||||
</Group>
|
||||
);
|
||||
Reference in new issue
Block a user