diff --git a/apps/docs/app/themes/page.tsx b/apps/docs/app/themes/page.tsx new file mode 100644 index 0000000000..a74abec519 --- /dev/null +++ b/apps/docs/app/themes/page.tsx @@ -0,0 +1,9 @@ +import {ThemeBuilder} from "@/components/themes"; + +export default function ThemesPage() { + return ( +
+ +
+ ); +} diff --git a/apps/docs/components/icons/crop.tsx b/apps/docs/components/icons/crop.tsx new file mode 100644 index 0000000000..2dbfb1af69 --- /dev/null +++ b/apps/docs/components/icons/crop.tsx @@ -0,0 +1,39 @@ +import {IconSvgProps} from "@/types"; + +export const CropMinimalistic = ({size = 24, width, height, ...props}: IconSvgProps) => ( + + + +); + +export const Crop = ({size = 24, width, height, ...props}: IconSvgProps) => ( + + + +); diff --git a/apps/docs/components/icons/filters.tsx b/apps/docs/components/icons/filters.tsx new file mode 100644 index 0000000000..8d50accb3a --- /dev/null +++ b/apps/docs/components/icons/filters.tsx @@ -0,0 +1,19 @@ +import {IconSvgProps} from "@/types"; + +export const Filters = ({size = 24, width, height, ...props}: IconSvgProps) => ( + + + +); diff --git a/apps/docs/components/icons/index.ts b/apps/docs/components/icons/index.ts index 1c541f49b6..550ad37864 100644 --- a/apps/docs/components/icons/index.ts +++ b/apps/docs/components/icons/index.ts @@ -13,3 +13,7 @@ export * from "./two-tone"; export * from "./bold"; export * from "./linear"; export * from "./bug"; +export * from "./mirror-left"; +export * from "./palette-round"; +export * from "./filters"; +export * from "./scaling"; diff --git a/apps/docs/components/icons/mirror-left.tsx b/apps/docs/components/icons/mirror-left.tsx new file mode 100644 index 0000000000..4ba53f8246 --- /dev/null +++ b/apps/docs/components/icons/mirror-left.tsx @@ -0,0 +1,25 @@ +import {IconSvgProps} from "@/types"; + +export const MirrorLeft = ({size = 24, width, height, ...props}: IconSvgProps) => ( + + + + +); diff --git a/apps/docs/components/icons/palette-round.tsx b/apps/docs/components/icons/palette-round.tsx new file mode 100644 index 0000000000..307c5f0a55 --- /dev/null +++ b/apps/docs/components/icons/palette-round.tsx @@ -0,0 +1,19 @@ +import {IconSvgProps} from "@/types"; + +export const PaletteRound = ({size = 24, width, height, ...props}: IconSvgProps) => ( + + + +); diff --git a/apps/docs/components/icons/radial-blur.tsx b/apps/docs/components/icons/radial-blur.tsx new file mode 100644 index 0000000000..626f343c90 --- /dev/null +++ b/apps/docs/components/icons/radial-blur.tsx @@ -0,0 +1,58 @@ +import {IconSvgProps} from "@/types"; + +export const RadialBlur = ({size = 24, width, height, ...props}: IconSvgProps) => ( + + + + + + + + + + + + +); diff --git a/apps/docs/components/icons/scaling.tsx b/apps/docs/components/icons/scaling.tsx new file mode 100644 index 0000000000..ead0764132 --- /dev/null +++ b/apps/docs/components/icons/scaling.tsx @@ -0,0 +1,21 @@ +import {IconSvgProps} from "@/types"; + +export const Scaling = ({size = 24, width, height, ...props}: IconSvgProps) => ( + +); diff --git a/apps/docs/components/icons/text-square.tsx b/apps/docs/components/icons/text-square.tsx new file mode 100644 index 0000000000..e6a9be9590 --- /dev/null +++ b/apps/docs/components/icons/text-square.tsx @@ -0,0 +1,23 @@ +import {IconSvgProps} from "@/types"; + +export const TextSquare = ({size = 24, width, height, ...props}: IconSvgProps) => ( + + + + +); diff --git a/apps/docs/components/navbar.tsx b/apps/docs/components/navbar.tsx index 9be396c459..d56df3df0c 100644 --- a/apps/docs/components/navbar.tsx +++ b/apps/docs/components/navbar.tsx @@ -333,6 +333,17 @@ export const Navbar: FC = ({children, routes, mobileRoutes = [], sl + + handlePressNavbarItem("Themes", "/themes")} + > + Theme + + diff --git a/apps/docs/components/themes/components/color-picker.tsx b/apps/docs/components/themes/components/color-picker.tsx new file mode 100644 index 0000000000..e804ba6a06 --- /dev/null +++ b/apps/docs/components/themes/components/color-picker.tsx @@ -0,0 +1,124 @@ +import {useEffect, useState} from "react"; +import {Button, Popover, PopoverContent, PopoverTrigger} from "@heroui/react"; +import {HexColorInput, HexColorPicker} from "react-colorful"; +import Values from "values.js"; +import {readableColor} from "color2k"; +import {useTheme} from "next-themes"; +import {clsx} from "@heroui/shared-utils"; + +import {ColorPickerType, ThemeType} from "../types"; +import {colorValuesToRgb, getColorWeight} from "../utils/colors"; + +interface ColorPickerProps { + hexColor: string; + type: ColorPickerType; + onChange: (hexColor: string) => void; + onClose: (hexColor: string) => void; +} + +export function ColorPicker({hexColor, type, onChange, onClose}: ColorPickerProps) { + const [selectedColor, setSelectedColor] = useState(hexColor); + + const [isOpen, setIsOpen] = useState(false); + const theme = useTheme().theme as ThemeType; + const selectedColorWeight = getColorWeight(type, theme); + const selectedColorValues = new Values(selectedColor).all(selectedColorWeight); + + function handleChange(updatedHexColor: string) { + onChange(updatedHexColor); + setSelectedColor(updatedHexColor); + } + + /** + * Update the selected color when the popover is opened. + */ + useEffect(() => { + setSelectedColor(hexColor); + }, [hexColor, isOpen]); + + return ( +
+ onClose(selectedColor)} + onOpenChange={setIsOpen} + > + + + + ) : null} +
+
{children}
+ + ); +} diff --git a/apps/docs/components/themes/components/configuration/actions.tsx b/apps/docs/components/themes/components/configuration/actions.tsx new file mode 100644 index 0000000000..d00bcdfe5f --- /dev/null +++ b/apps/docs/components/themes/components/configuration/actions.tsx @@ -0,0 +1,49 @@ +import {Button, Tooltip} from "@heroui/react"; +import {Icon} from "@iconify/react/dist/offline"; +import SunIcon from "@iconify/icons-solar/sun-linear"; +import MoonIcon from "@iconify/icons-solar/moon-linear"; +import UndoLeftIcon from "@iconify/icons-solar/undo-left-linear"; + +import {ThemeType} from "../../types"; + +interface ActionsProps { + theme: ThemeType; + onCopy: () => unknown; + onResetTheme: () => void; + onToggleTheme: () => void; +} + +export function Actions({theme, onCopy, onResetTheme, onToggleTheme}: ActionsProps) { + const isLight = theme === "light"; + + /** + * Handle the copying of the configuration. + */ + function handleCopyConfig() { + navigator.clipboard.writeText(JSON.stringify(onCopy(), null, 2)); + } + + return ( +
+ + + + + + + + + +
+ ); +} diff --git a/apps/docs/components/themes/components/configuration/base-colors.tsx b/apps/docs/components/themes/components/configuration/base-colors.tsx new file mode 100644 index 0000000000..70960d70f1 --- /dev/null +++ b/apps/docs/components/themes/components/configuration/base-colors.tsx @@ -0,0 +1,59 @@ +import {colorsId} from "../../constants"; +import {setCssColor} from "../../css-vars"; +import {useThemeBuilder} from "../../provider"; +import {Config, ThemeType} from "../../types"; +import {ColorPicker} from "../color-picker"; +import {ConfigSection} from "../config-section"; + +import {Filters} from "@/components/icons"; + +interface BrandColorsProps { + config: Config; + syncIcon: React.ReactNode; + syncThemes: boolean; + theme: ThemeType; +} + +export function BaseColors({config, syncThemes, theme}: BrandColorsProps) { + const {setBaseColor} = useThemeBuilder(); + + return ( + } + id={colorsId} + title="Base colors" + toolTip="Primary, Secondary, Success, Warning, Danger colors" + > + setCssColor("primary", hexColor, theme)} + onClose={(hexColor) => setBaseColor({primary: hexColor}, theme, syncThemes)} + /> + setCssColor("secondary", hexColor, theme)} + onClose={(hexColor) => setBaseColor({secondary: hexColor}, theme, syncThemes)} + /> + setCssColor("success", hexColor, theme)} + onClose={(hexColor) => setBaseColor({success: hexColor}, theme, syncThemes)} + /> + setCssColor("warning", hexColor, theme)} + onClose={(hexColor) => setBaseColor({warning: hexColor}, theme, syncThemes)} + /> + setCssColor("danger", hexColor, theme)} + onClose={(hexColor) => setBaseColor({danger: hexColor}, theme, syncThemes)} + /> + + ); +} diff --git a/apps/docs/components/themes/components/configuration/border-widths.tsx b/apps/docs/components/themes/components/configuration/border-widths.tsx new file mode 100644 index 0000000000..3fa10c007a --- /dev/null +++ b/apps/docs/components/themes/components/configuration/border-widths.tsx @@ -0,0 +1,31 @@ +import {useThemeBuilder} from "../../provider"; +import {ConfigSection} from "../config-section"; + +import EditableButton from "./editable-button"; + +import {Crop} from "@/components/icons/crop"; + +const BORDER_WIDTHS = [ + {title: "thin", className: "rounded-tl-md border-t-1 border-l-1"}, + {title: "medium", className: "rounded-tl-md border-t-2 border-l-2"}, + {title: "thick", className: "rounded-tl-md border-t-4 border-l-4"}, +] as const; + +export function BorderWidths() { + const {borderWidthValue, setBorderWidthValue} = useThemeBuilder(); + + return ( + } title="Border width"> + {BORDER_WIDTHS.map(({title, className}) => ( + + ))} + + ); +} diff --git a/apps/docs/components/themes/components/configuration/content-colors.tsx b/apps/docs/components/themes/components/configuration/content-colors.tsx new file mode 100644 index 0000000000..b6eb41c0fd --- /dev/null +++ b/apps/docs/components/themes/components/configuration/content-colors.tsx @@ -0,0 +1,51 @@ +import {baseColorsId} from "../../constants"; +import {setCssContentColor} from "../../css-vars"; +import {useThemeBuilder} from "../../provider"; +import {Config, ThemeType} from "../../types"; +import {ColorPicker} from "../color-picker"; +import {ConfigSection} from "../config-section"; + +import {PaletteRound} from "@/components/icons"; + +interface BaseColorsProps { + config: Config; + theme: ThemeType; +} + +export function ContentColors({config, theme}: BaseColorsProps) { + const {setContentColor} = useThemeBuilder(); + + return ( + } + id={baseColorsId} + title="Content colors" + toolTip="content1, content2, content3, content4 colors" + > + setCssContentColor(1, hexColor)} + onClose={(hexColor) => setContentColor({content1: hexColor}, theme)} + /> + setCssContentColor(2, hexColor)} + onClose={(hexColor) => setContentColor({content2: hexColor}, theme)} + /> + setCssContentColor(3, hexColor)} + onClose={(hexColor) => setContentColor({content3: hexColor}, theme)} + /> + setCssContentColor(4, hexColor)} + onClose={(hexColor) => setContentColor({content4: hexColor}, theme)} + /> + + ); +} diff --git a/apps/docs/components/themes/components/configuration/default-colors.tsx b/apps/docs/components/themes/components/configuration/default-colors.tsx new file mode 100644 index 0000000000..b00c1eafe2 --- /dev/null +++ b/apps/docs/components/themes/components/configuration/default-colors.tsx @@ -0,0 +1,32 @@ +import {defaultColorsId} from "../../constants"; +import {setCssColor} from "../../css-vars"; +import {useThemeBuilder} from "../../provider"; +import {Config, ThemeType} from "../../types"; +import {ColorPicker} from "../color-picker"; +import {ConfigSection} from "../config-section"; + +import {PaletteRound} from "@/components/icons"; + +interface DefaultColorsProp { + config: Config; + theme: ThemeType; +} + +export function DefaultColors({config, theme}: DefaultColorsProp) { + const {setDefaultColor} = useThemeBuilder(); + + return ( + } + id={defaultColorsId} + title="Default Colors" + > + setCssColor("default", hexColor, theme)} + onClose={(hexColor) => setDefaultColor({default: hexColor}, theme, false)} + /> + + ); +} diff --git a/apps/docs/components/themes/components/configuration/disable-opacity.tsx b/apps/docs/components/themes/components/configuration/disable-opacity.tsx new file mode 100644 index 0000000000..82a21d42d6 --- /dev/null +++ b/apps/docs/components/themes/components/configuration/disable-opacity.tsx @@ -0,0 +1,54 @@ +import {setOtherCssParams} from "../../css-vars"; +import {useThemeBuilder} from "../../provider"; +import {Config} from "../../types"; +import {ConfigSection} from "../config-section"; + +import ValueButton from "./value-button"; + +import {RadialBlur} from "@/components/icons/radial-blur"; + +interface DisableOpacityProps { + config: Config; +} + +export function DisableOpacity({config}: DisableOpacityProps) { + const {setOtherParams} = useThemeBuilder(); + + const handleChange = (key: keyof Config["layout"]["otherParams"], value: string) => { + setOtherParams({[key]: value}); + setOtherCssParams(key, value); + }; + + return ( + } title="Disable Opacity"> + { + handleChange("disabledOpacity", value); + }} + value={"0.2"} + /> + { + handleChange("disabledOpacity", value); + }} + value={"0.4"} + /> + { + handleChange("disabledOpacity", value); + }} + value={"0.6"} + /> + { + handleChange("disabledOpacity", value); + }} + value={"0.8"} + /> + + ); +} diff --git a/apps/docs/components/themes/components/configuration/editable-button.tsx b/apps/docs/components/themes/components/configuration/editable-button.tsx new file mode 100644 index 0000000000..96857813ab --- /dev/null +++ b/apps/docs/components/themes/components/configuration/editable-button.tsx @@ -0,0 +1,36 @@ +import {Button} from "@heroui/react"; +import {clsx} from "@heroui/shared-utils"; + +interface EditableButtonProps { + title: any; + className: string; + value: string; + setValue: (value: any) => void; +} + +const EditableButton = ({title, className, value, setValue}: EditableButtonProps) => { + return ( + + ); +}; + +export default EditableButton; diff --git a/apps/docs/components/themes/components/configuration/font-button.tsx b/apps/docs/components/themes/components/configuration/font-button.tsx new file mode 100644 index 0000000000..9f8e2b6c22 --- /dev/null +++ b/apps/docs/components/themes/components/configuration/font-button.tsx @@ -0,0 +1,57 @@ +import {Button} from "@heroui/react"; +import {clsx} from "@heroui/shared-utils"; + +import {FontName, FontType} from "../../types"; + +interface FontButtonProps { + title: FontName; + className: string; + value: string; + setValue: (value: FontType) => void; +} + +interface FontStyle { + fontFamily: string; + letterSpacing?: string; +} + +function getFontStyle(fontName: FontName): FontStyle { + switch (fontName) { + case "inter": + return {fontFamily: "'Inter', sans-serif", letterSpacing: "-0.02em"}; + case "roboto": + return {fontFamily: "'Roboto', sans-serif"}; + case "outfit": + return {fontFamily: "'Outfit', sans-serif", letterSpacing: "0.05em"}; + case "lora": + return {fontFamily: "'Lora', serif"}; + default: + return {fontFamily: "'Inter', sans-serif", letterSpacing: "-0.02em"}; + } +} + +const FontButton = ({title, value, setValue}: FontButtonProps) => { + const style = getFontStyle(title); + + return ( + + ); +}; + +export default FontButton; diff --git a/apps/docs/components/themes/components/configuration/fonts.tsx b/apps/docs/components/themes/components/configuration/fonts.tsx new file mode 100644 index 0000000000..c2fb13d701 --- /dev/null +++ b/apps/docs/components/themes/components/configuration/fonts.tsx @@ -0,0 +1,19 @@ +import {useThemeBuilder} from "../../provider"; +import {ConfigSection} from "../config-section"; + +import FontButton from "./font-button"; + +import {TextSquare} from "@/components/icons/text-square"; + +export function Fonts() { + const {font, setFont} = useThemeBuilder(); + + return ( + } title="Fonts"> + + + + + + ); +} diff --git a/apps/docs/components/themes/components/configuration/index.tsx b/apps/docs/components/themes/components/configuration/index.tsx new file mode 100644 index 0000000000..9ea9330ce9 --- /dev/null +++ b/apps/docs/components/themes/components/configuration/index.tsx @@ -0,0 +1,425 @@ +import {useEffect, useState} from "react"; +import { + Card, + CardBody, + CardHeader, + Divider, + Button, + CardFooter, + Link, + ScrollShadow, + Drawer, + DrawerContent, +} from "@heroui/react"; +import {useTheme} from "next-themes"; +import {useLocalStorage} from "usehooks-ts"; +import {Icon} from "@iconify/react/dist/offline"; +import LinkSquareIcon from "@iconify/icons-solar/link-square-linear"; +import {ArrowLeftIcon, ChevronIcon, CloseIcon} from "@heroui/shared-icons"; +import {clsx} from "@heroui/shared-utils"; + +import {useThemeBuilder} from "../../provider"; +import {Config, Template, ThemeType} from "../../types"; +import {configKey, syncThemesKey, initialConfig} from "../../constants"; +import {SelectTemplate} from "../select-template"; +import {generatePluginConfig} from "../../utils/config"; +import {setAllCssVars} from "../../css-vars"; +import {templates} from "../../templates"; + +import {BaseColors} from "./base-colors"; +import {ContentColors} from "./content-colors"; +import {LayoutColors} from "./layout-colors"; +import {Radiuses} from "./radiuses"; +import {DefaultColors} from "./default-colors"; +import {DisableOpacity} from "./disable-opacity"; +import Swatch from "./swatch"; +import {Fonts} from "./fonts"; +import {Scaling} from "./scaling"; +import {BorderWidths} from "./border-widths"; + +import usePrevious from "@/hooks/use-previous"; +import {Filters, RotateLeftLinearIcon} from "@/components/icons"; +import {ThemeSwitch} from "@/components/theme-switch"; +import {Crop, CropMinimalistic} from "@/components/icons/crop"; +import {RadialBlur} from "@/components/icons/radial-blur"; +import {Scaling as ScalingIcon} from "@/components/icons/scaling"; + +export default function Configuration() { + const [selectedTemplate, setSelectedTemplate] = useState