Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement HomePage and MyTheme page #732

Merged
merged 15 commits into from
Sep 27, 2023
11 changes: 9 additions & 2 deletions packages/configuration-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
"@react-aria/i18n": "3.8.1",
"@react-aria/numberfield": "3.7.0",
"@react-stately/numberfield": "3.6.0",
"@vanilla-extract/css": "1.12.0",
"@vanilla-extract/recipes": "0.5.0",
"i18next": "22.5.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-i18next": "12.3.1",
"ts-pattern": "5.0.5"
"react-router-dom": "^6.15.0",
"ts-pattern": "3.3.5"
},
"devDependencies": {
"@react-types/numberfield": "3.5.0",
Expand All @@ -32,11 +35,15 @@
"@types/react-dom": "18.2.7",
"@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0",
"@vanilla-extract/dynamic": "2.0.3",
"@vanilla-extract/sprinkles": "1.6.1",
"@vanilla-extract/vite-plugin": "3.8.2",
"@vitejs/plugin-react": "4.0.4",
"eslint": "8.48.0",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-react-refresh": "0.4.3",
"typescript": "5.1.3",
"vite": "4.4.9"
"vite": "4.4.9",
"vite-plugin-checker": "^0.6.2"
}
}
20 changes: 12 additions & 8 deletions packages/configuration-builder/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Stack } from "@buildo/bento-design-system";
import { Box, Divider } from "@buildo/bento-design-system";
import { Header } from "./Header/Header";
import { ThemeConfigurator } from "./ThemeConfigurator/ThemeConfigurator";
import { RouterProvider } from "react-router-dom";
import { router } from "./router";

function App() {
export function App() {
return (
<Stack space={0}>
<Box display="flex" flexDirection="column" height="full">
<Header />
<ThemeConfigurator />
</Stack>
<Box flexShrink={0}>
<Divider />
</Box>
<Box display="flex" flexGrow={1} flexDirection="column" overflow="hidden">
<RouterProvider router={router} />
</Box>
</Box>
);
}

export default App;
2 changes: 1 addition & 1 deletion packages/configuration-builder/src/ColorEditor/Palette.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box } from "@buildo/bento-design-system";
import { LightnessInterpolation } from "./ColorEditor";
import { useState } from "react";
import { IconEyedropper } from "../Icons/IconEyedropper";
import { IconEyedropper } from "../PhosphorIcons";
import { HSLToHex, HexColor } from "../utils/colorUtils";

type Props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Button, Headline, Inline, Stack, Actions } from "@buildo/bento-design-s
import { useTranslation } from "react-i18next";
import { ColorConfig, ColorEditor } from "../ColorEditor/ColorEditor";
import { HexColor } from "../utils/colorUtils";
import { ThemeConfig } from "../ThemeConfigurator/ThemeConfigurator";
import { defaultColorConfig } from "./defaultColor";
import { ThemeConfig } from "../ConfiguratorStatusContext";

type BrandColors = ThemeConfig["colors"]["brand"];

Expand Down
48 changes: 23 additions & 25 deletions packages/configuration-builder/src/ColorsSection/ColorsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import { useTranslation } from "react-i18next";
import { ConfiguratorSection } from "../ConfiguratorSection/ConfiguratorSection";
import { BrandColors } from "./BrandColors";
import { ThemeConfig } from "../ThemeConfigurator/ThemeConfigurator";
import { useState } from "react";
import { match } from "ts-pattern";
import { InteractiveColor } from "./InteractiveColor";
import { NeutralColor } from "./NeutralColor";
import { SemanticColors } from "./SemanticColors";
import { DataVizColors } from "./DataVizColors";
import { SectionCompleted } from "./SectionCompleted";
import { ThemeConfig, useConfiguratorStatusContext } from "../ConfiguratorStatusContext";

type ColorsConfig = ThemeConfig["colors"];

type Props = {
value: ColorsConfig;
onChange: (value: ColorsConfig) => void;
onComplete: () => void;
};

export function ColorsSection(props: Props) {
export function ColorsSection() {
const steps = [
"brand" as const,
"interactive" as const,
Expand All @@ -27,6 +19,13 @@ export function ColorsSection(props: Props) {
"dataVisualization" as const,
];
const { t } = useTranslation();
const { theme, setTheme, completeSection } = useConfiguratorStatusContext();

const colors = theme.colors;

const onChange = (newValue: ThemeConfig["colors"]) => {
setTheme({ ...theme, colors: newValue });
};

const [completed, setCompleted] = useState(false);
const [currentStep, setCurrentStep] = useState<(typeof steps)[0]>("brand");
Expand All @@ -42,59 +41,58 @@ export function ColorsSection(props: Props) {
return match(completed)
.with(true, () => (
<ConfiguratorSection title={t("ColorsSection.title")} endStep>
<SectionCompleted goToMyTheme={() => {}} goToTypography={() => {}} />
<SectionCompleted />
</ConfiguratorSection>
))
.with(false, () => (
<ConfiguratorSection
key={currentStep} // refresh component to restore scroll position at every step change
title={t("ColorsSection.title")}
steps={steps.map((step) => ({ label: t(`ColorsSection.Step.${step}`) }))}
currentStep={currentStepIndex}
>
{match(currentStep)
.with("brand", () => (
<BrandColors
value={props.value.brand}
onChange={(value) => props.onChange({ ...props.value, brand: value })}
value={colors.brand}
onChange={(value) => onChange({ ...colors, brand: value })}
onCancel={() => {}}
onNext={onNext}
/>
))
.with("interactive", () => (
<InteractiveColor
value={props.value.interactive}
onChange={(value) => props.onChange({ ...props.value, interactive: value })}
brandColors={props.value.brand}
value={theme.colors.interactive}
onChange={(value) => onChange({ ...colors, interactive: value })}
brandColors={colors.brand}
onBack={onBack}
onNext={onNext}
/>
))
.with("neutral", () => (
<NeutralColor
value={props.value.neutral}
onChange={(neutral) => props.onChange({ ...props.value, neutral })}
value={colors.neutral}
onChange={(neutral) => onChange({ ...colors, neutral })}
onBack={onBack}
onNext={onNext}
/>
))
.with("semantic", () => (
<SemanticColors
value={props.value.semantic}
onChange={(semantic) => props.onChange({ ...props.value, semantic })}
value={colors.semantic}
onChange={(semantic) => onChange({ ...colors, semantic })}
onBack={onBack}
onNext={onNext}
/>
))
.with("dataVisualization", () => (
<DataVizColors
value={props.value.dataVisualization}
onChange={(dataVisualization) =>
props.onChange({ ...props.value, dataVisualization })
}
value={colors.dataVisualization}
onChange={(dataVisualization) => onChange({ ...colors, dataVisualization })}
onBack={onBack}
onNext={() => {
setCompleted(true);
props.onComplete();
completeSection("colors");
}}
/>
))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import { ThemeConfig } from "../ThemeConfigurator/ThemeConfigurator";
import { Actions, Headline, Stack } from "@buildo/bento-design-system";
import { ColorEditor } from "../ColorEditor/ColorEditor";
import { ThemeConfig } from "../ConfiguratorStatusContext";

type DataVizColors = ThemeConfig["colors"]["dataVisualization"];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
Stack,
unsafeLocalizedString,
} from "@buildo/bento-design-system";
import { ThemeConfig } from "../ThemeConfigurator/ThemeConfigurator";
import { ColorEditor } from "../ColorEditor/ColorEditor";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { ThemeConfig } from "../ConfiguratorStatusContext";

type InteractiveColor = ThemeConfig["colors"]["interactive"];
type BrandColors = ThemeConfig["colors"]["brand"];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useTranslation } from "react-i18next";
import { ThemeConfig } from "../ThemeConfigurator/ThemeConfigurator";
import { Actions, Headline, Stack } from "@buildo/bento-design-system";
import { ColorEditor } from "../ColorEditor/ColorEditor";
import { ColorPresets } from "./ColorPresets";
import { HexColor } from "../utils/colorUtils";
import { defaultColorConfig } from "./defaultColor";
import { ThemeConfig } from "../ConfiguratorStatusContext";

type NeutralColor = ThemeConfig["colors"]["neutral"];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import { Button, Feedback, Inline, Stack } from "@buildo/bento-design-system";
import { ButtonLink, Feedback, Inline, Stack } from "@buildo/bento-design-system";
import { useTranslation } from "react-i18next";
import { IconConfetti } from "../Icons/IconConfetti";
import { IconConfetti } from "../PhosphorIcons";

type Props = {
goToMyTheme: () => void;
goToTypography: () => void;
};

export function SectionCompleted(props: Props) {
export function SectionCompleted() {
const { t } = useTranslation();
return (
<Stack space={40} align="center">
<Feedback size="large" title={t("ColorsSection.completed")} icon={IconConfetti} />
<Inline space={16}>
<Button
<ButtonLink
size="large"
kind="solid"
hierarchy="secondary"
label={t("ColorsSection.returnToMyTheme")}
onPress={props.goToMyTheme}
href="/theme"
/>
<Button
<ButtonLink
size="large"
kind="solid"
hierarchy="primary"
label={t("ColorsSection.goToTypography")}
onPress={props.goToTypography}
href="/theme/typography"
/>
</Inline>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useTranslation } from "react-i18next";
import { ThemeConfig } from "../ThemeConfigurator/ThemeConfigurator";
import { Actions, Headline, Stack } from "@buildo/bento-design-system";
import { ColorEditor } from "../ColorEditor/ColorEditor";
import { ColorPresets } from "./ColorPresets";
import { HexColor } from "../utils/colorUtils";
import { defaultColorConfig } from "./defaultColor";
import { ThemeConfig } from "../ConfiguratorStatusContext";

type SemanticColors = ThemeConfig["colors"]["semantic"];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ type Props = {
export function ConfiguratorSection(props: Props) {
const { t } = useTranslation();
return (
<Box padding={40} paddingTop={24}>
<Box padding={40} paddingTop={24} flexGrow={1} overflowY="auto">
<Stack space={40}>
<Stack space={24}>
<Breadcrumb
items={[{ label: t("ConfigurationSection.myTheme"), href: "" }, { label: props.title }]}
items={[
{ label: t("ConfigurationSection.myTheme"), href: "/theme" },
{ label: props.title },
]}
/>
<Headline size="medium">{props.title}</Headline>
{!props.endStep && <Stepper steps={props.steps} currentStep={props.currentStep} />}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useState } from "react";
import { ColorConfig } from "../ColorEditor/ColorEditor";
import { ColorsSection } from "../ColorsSection/ColorsSection";
import { HexColor } from "../utils/colorUtils";
import { defaultTokens } from "@buildo/bento-design-system";
import { defaultColorConfig } from "../ColorsSection/defaultColor";
import { createContext, useContext, useState } from "react";
import { defaultColorConfig } from "./ColorsSection/defaultColor";
import { Children, defaultTokens } from "@buildo/bento-design-system";
import { HexColor } from "./utils/colorUtils";
import { ColorConfig } from "./ColorEditor/ColorEditor";

type BrandColors =
| [ColorConfig]
Expand Down Expand Up @@ -36,8 +35,19 @@ export type ThemeConfig = {
};
};

export function ThemeConfigurator() {
const [themeConfig, setThemeConfig] = useState<ThemeConfig>({
export type ThemeSection = "colors";

type ConfiguratorStatus = {
theme: ThemeConfig;
sections: { [key in ThemeSection]: boolean };
completeSection: (section: ThemeSection) => void;
setTheme: (newTheme: ThemeConfig) => void;
};

export const ConfiguratorStatusContext = createContext<ConfiguratorStatus | null>(null);

export function ConfiguratorStatusProvider(props: { children: Children }) {
const [theme, setTheme] = useState<ThemeConfig>({
colors: {
brand: [defaultColorConfig(defaultTokens.brandColor.brandPrimary as HexColor)],
interactive: defaultColorConfig(
Expand Down Expand Up @@ -67,11 +77,28 @@ export function ThemeConfigurator() {
},
});

const [sections, setSections] = useState<ConfiguratorStatus["sections"]>({
colors: false,
});

return (
<ColorsSection
value={themeConfig.colors}
onChange={(colors) => setThemeConfig({ ...themeConfig, colors })}
onComplete={() => {}}
/>
<ConfiguratorStatusContext.Provider
value={{
theme,
setTheme: (newTheme) => setTheme(newTheme),
sections,
completeSection: (section) => setSections({ ...sections, [section]: true }),
}}
>
{props.children}
</ConfiguratorStatusContext.Provider>
);
}

export function useConfiguratorStatusContext() {
const value = useContext(ConfiguratorStatusContext);
if (!value) {
throw new Error("Missing ConfiguratorStatusContext.Provider");
}
return value;
}
Loading