docs(themes): adding theme generator (#4626)

* chore: adding xylish's contributions + modifying styles

* chore: nextui to heroui

* chore: colors in theme generator

* chore: radiuses, disable-opacity

* chore: fixing the configuration box styles

* chore: adding the showcase elemtents

* chore: modifying styles

* chore: adding the fonts

* chore: adding the scaling

* chore: removing the calendar

* feat: adding the border-width

* chore: modifying style for mobile

* chore: modifying the styles

* chore: removing the NextUI references + small bug fix

* chore: adding coderabits reviews

* fix: borderWidth not getting applied on breadcrumbs and input

* chore: rebasing

* chore: modifying the styles

* chore: updating the styles for the smaller devices

* chore: refactoring

* chore: improvements

* chore: making the fonts workable

* chore: making the fonts workable

* chore: modifying the swatch according to the theme

* chore: adding the default selected template

* chore: modifying mobile styles

* chore: fixing the popover

* chore: nit

* fix: fixing the select styles

* chore: modifying the mobile styles

* chore: modifying the styles

* fix: adding junior's suggestions

* fix: fixing the breadcrumb

* fix: adding junior's suggestions
This commit is contained in:
Maharshi Alpesh 2025-02-15 01:46:22 +05:30 committed by GitHub
parent 3f7f85b437
commit e99ddc45b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 4303 additions and 4 deletions

View File

@ -0,0 +1,9 @@
import {ThemeBuilder} from "@/components/themes";
export default function ThemesPage() {
return (
<div className="flex flex-col md:flex-row gap-6 w-full p-6 py-3 md:pr-[45vw] lg:pr-[30vw] justify-start mt-12 scrollbar-hide">
<ThemeBuilder />
</div>
);
}

View File

@ -0,0 +1,39 @@
import {IconSvgProps} from "@/types";
export const CropMinimalistic = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
aria-label="Minimalistic Crop"
focusable="false"
height={size || height}
viewBox="0 0 20 20"
width={size || width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
clipRule="evenodd"
d="M4.16699 1.04169C4.51217 1.04169 4.79199 1.32151 4.79199 1.66669V9.16669C4.79199 10.7557 4.79332 11.8846 4.90846 12.741C5.02118 13.5794 5.23257 14.0624 5.58524 14.4151C5.93792 14.7678 6.42096 14.9792 7.25937 15.0919C8.11575 15.207 9.24464 15.2084 10.8337 15.2084H18.3337C18.6788 15.2084 18.9587 15.4882 18.9587 15.8334C18.9587 16.1785 18.6788 16.4584 18.3337 16.4584H16.4587V18.3334C16.4587 18.6785 16.1788 18.9584 15.8337 18.9584C15.4885 18.9584 15.2087 18.6785 15.2087 18.3334V16.4584H10.7867C9.25518 16.4584 8.04215 16.4584 7.09281 16.3307C6.11579 16.1994 5.325 15.9226 4.70136 15.299C4.07773 14.6754 3.80096 13.8846 3.6696 12.9075C3.54197 11.9582 3.54198 10.7452 3.54199 9.2137L3.54199 4.79169H1.66699C1.32181 4.79169 1.04199 4.51187 1.04199 4.16669C1.04199 3.82151 1.32181 3.54169 1.66699 3.54169H3.54199V1.66669C3.54199 1.32151 3.82181 1.04169 4.16699 1.04169ZM12.7413 4.90815C11.8849 4.79301 10.756 4.79169 9.16699 4.79169H6.66699C6.32181 4.79169 6.04199 4.51187 6.04199 4.16669C6.04199 3.82151 6.32181 3.54169 6.66699 3.54169L9.21401 3.54169C10.7455 3.54167 11.9585 3.54166 12.9078 3.6693C13.8849 3.80066 14.6757 4.07742 15.2993 4.70106C15.9229 5.32469 16.1997 6.11548 16.331 7.0925C16.4587 8.04185 16.4587 9.25488 16.4587 10.7863V13.3334C16.4587 13.6785 16.1788 13.9584 15.8337 13.9584C15.4885 13.9584 15.2087 13.6785 15.2087 13.3334V10.8334C15.2087 9.24434 15.2073 8.11545 15.0922 7.25906C14.9795 6.42065 14.7681 5.93761 14.4154 5.58494C14.0627 5.23226 13.5797 5.02087 12.7413 4.90815Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
</svg>
);
export const Crop = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
aria-label="Crop"
focusable="false"
height={size || height}
viewBox="0 0 20 20"
width={size || width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
clipRule="evenodd"
d="M4.16699 1.04163C4.51217 1.04163 4.79199 1.32145 4.79199 1.66663V9.16663C4.79199 10.7556 4.79332 11.8845 4.90846 12.7409C5.02118 13.5793 5.23257 14.0624 5.58524 14.415C5.93792 14.7677 6.42096 14.9791 7.25937 15.0918C8.11575 15.207 9.24464 15.2083 10.8337 15.2083H18.3337C18.6788 15.2083 18.9587 15.4881 18.9587 15.8333C18.9587 16.1785 18.6788 16.4583 18.3337 16.4583H16.4587V18.3333C16.4587 18.6785 16.1788 18.9583 15.8337 18.9583C15.4885 18.9583 15.2087 18.6785 15.2087 18.3333V16.4583H10.7867C9.25518 16.4583 8.04215 16.4583 7.09281 16.3307C6.11579 16.1993 5.325 15.9226 4.70136 15.2989C4.07773 14.6753 3.80096 13.8845 3.6696 12.9075C3.54197 11.9581 3.54198 10.7451 3.54199 9.21364L3.54199 4.79163H1.66699C1.32181 4.79163 1.04199 4.5118 1.04199 4.16663C1.04199 3.82145 1.32181 3.54163 1.66699 3.54163H3.54199V1.66663C3.54199 1.32145 3.82181 1.04163 4.16699 1.04163ZM12.7413 4.90809C11.8849 4.79295 10.756 4.79163 9.16699 4.79163H6.66699C6.32181 4.79163 6.04199 4.5118 6.04199 4.16663C6.04199 3.82145 6.32181 3.54163 6.66699 3.54163L9.21401 3.54163C10.7455 3.54161 11.9585 3.5416 12.9078 3.66924C13.8849 3.80059 14.6757 4.07736 15.2993 4.701C15.9229 5.32463 16.1997 6.11542 16.331 7.09244C16.4587 8.04179 16.4587 9.25482 16.4587 10.7863V13.3333C16.4587 13.6785 16.1788 13.9583 15.8337 13.9583C15.4885 13.9583 15.2087 13.6785 15.2087 13.3333V10.8333C15.2087 9.24428 15.2073 8.11539 15.0922 7.259C14.9795 6.42059 14.7681 5.93755 14.4154 5.58488C14.0627 5.2322 13.5797 5.02081 12.7413 4.90809ZM9.54252 6.45829H10.4581C11.0122 6.45826 11.4896 6.45823 11.8719 6.50963C12.2816 6.56471 12.6743 6.68893 12.9928 7.00747C13.3114 7.32601 13.4356 7.71873 13.4907 8.12842C13.5421 8.51071 13.542 8.9881 13.542 9.54215V10.4578C13.542 11.0118 13.5421 11.4892 13.4907 11.8715C13.4356 12.2812 13.3114 12.6739 12.9928 12.9925C12.6743 13.311 12.2816 13.4352 11.8719 13.4903C11.4896 13.5417 11.0122 13.5417 10.4581 13.5416H9.54252C8.98846 13.5417 8.51108 13.5417 8.12879 13.4903C7.71909 13.4352 7.32637 13.311 7.00783 12.9925C6.68929 12.6739 6.56508 12.2812 6.51 11.8715C6.4586 11.4892 6.45863 11.0118 6.45866 10.4578V9.54215C6.45863 8.9881 6.4586 8.51071 6.51 8.12842C6.56508 7.71873 6.68929 7.32601 7.00783 7.00747C7.32637 6.68893 7.71909 6.56471 8.12879 6.50963C8.51108 6.45823 8.98846 6.45826 9.54252 6.45829ZM8.29535 7.74848C8.02426 7.78493 7.93929 7.84377 7.89172 7.89135C7.84414 7.93893 7.7853 8.0239 7.74885 8.29498C7.70999 8.58405 7.70866 8.97637 7.70866 9.58329V10.4166C7.70866 11.0236 7.70999 11.4159 7.74885 11.7049C7.7853 11.976 7.84414 12.061 7.89172 12.1086C7.93929 12.1561 8.02426 12.215 8.29535 12.2514C8.58442 12.2903 8.97674 12.2916 9.58366 12.2916H10.417C11.0239 12.2916 11.4162 12.2903 11.7053 12.2514C11.9764 12.215 12.0614 12.1561 12.1089 12.1086C12.1565 12.061 12.2154 11.976 12.2518 11.7049C12.2907 11.4159 12.292 11.0236 12.292 10.4166V9.58329C12.292 8.97637 12.2907 8.58405 12.2518 8.29498C12.2154 8.0239 12.1565 7.93893 12.1089 7.89135C12.0614 7.84377 11.9764 7.78493 11.7053 7.74848C11.4162 7.70962 11.0239 7.70829 10.417 7.70829H9.58366C8.97673 7.70829 8.58441 7.70962 8.29535 7.74848Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
</svg>
);

View File

@ -0,0 +1,19 @@
import {IconSvgProps} from "@/types";
export const Filters = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
focusable="false"
height={size || height}
viewBox="0 0 20 20"
width={size || width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
clipRule="evenodd"
d="M10.0003 2.29166C7.58408 2.29166 5.62533 4.25041 5.62533 6.66666C5.62533 9.0829 7.58408 11.0417 10.0003 11.0417C12.4166 11.0417 14.3753 9.0829 14.3753 6.66666C14.3753 4.25041 12.4166 2.29166 10.0003 2.29166ZM4.37533 6.66666C4.37533 3.56005 6.89372 1.04166 10.0003 1.04166C13.1069 1.04166 15.6253 3.56005 15.6253 6.66666C15.6253 7.16742 15.5599 7.65289 15.4371 8.11501C17.5014 8.94794 18.9587 10.9698 18.9587 13.3333C18.9587 16.4399 16.4403 18.9583 13.3337 18.9583C12.0865 18.9583 10.9331 18.5518 10.0004 17.8647C9.06748 18.5521 7.91468 18.9583 6.66699 18.9583C3.56039 18.9583 1.04199 16.4399 1.04199 13.3333C1.04199 10.9698 2.49922 8.94794 4.56355 8.11501C4.44076 7.65289 4.37533 7.16742 4.37533 6.66666ZM5.01777 9.27963C3.41873 9.93082 2.29199 11.5011 2.29199 13.3333C2.29199 15.7496 4.25075 17.7083 6.66699 17.7083C9.08324 17.7083 11.042 15.7496 11.042 13.3333C11.042 12.9481 10.9923 12.5751 10.8993 12.2202C10.6066 12.2672 10.3063 12.2917 10.0003 12.2917C7.83695 12.2917 5.95882 11.0704 5.01777 9.27963ZM12.1037 11.8852C12.2266 12.3477 12.292 12.8333 12.292 13.3333C12.292 14.7315 11.7818 16.0106 10.9375 16.9945C11.6258 17.4461 12.4487 17.7083 13.3337 17.7083C15.7499 17.7083 17.7087 15.7496 17.7087 13.3333C17.7087 11.5011 16.5819 9.93082 14.9829 9.27963C14.3653 10.4548 13.3442 11.3848 12.1037 11.8852Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
</svg>
);

View File

@ -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";

View File

@ -0,0 +1,25 @@
import {IconSvgProps} from "@/types";
export const MirrorLeft = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
focusable="false"
height={size || height}
viewBox="0 0 20 20"
width={size || width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
clipRule="evenodd"
d="M11.1174 2.50001C11.1174 2.15483 11.3972 1.87501 11.7424 1.87501H12.5018C12.8633 1.87501 13.2005 1.87501 13.5128 1.8777C13.858 1.88068 14.1354 2.1629 14.1324 2.50807C14.1294 2.85323 13.8472 3.13063 13.502 3.12765C13.1956 3.12501 12.8635 3.12501 12.5 3.12501H11.7424C11.3972 3.12501 11.1174 2.84518 11.1174 2.50001ZM15.3093 2.54551C15.4118 2.21588 15.762 2.03171 16.0917 2.13416C16.5211 2.26764 16.8934 2.47397 17.2097 2.7903C17.526 3.10662 17.7324 3.47886 17.8659 3.90836C17.9683 4.23798 17.7841 4.58824 17.4545 4.69069C17.1249 4.79313 16.7746 4.60896 16.6722 4.27933C16.589 4.01168 16.477 3.8254 16.3258 3.67418C16.1746 3.52296 15.9883 3.41102 15.7207 3.32784C15.391 3.22539 15.2069 2.87513 15.3093 2.54551ZM17.4919 5.86761C17.8371 5.86463 18.1193 6.14203 18.1223 6.48719C18.125 6.79951 18.125 7.13663 18.125 7.49809V8.63637C18.125 8.98155 17.8452 9.26137 17.5 9.26137C17.1548 9.26137 16.875 8.98155 16.875 8.63637V7.5C16.875 7.1365 16.875 6.80442 16.8724 6.49798C16.8694 6.15281 17.1468 5.87059 17.4919 5.86761ZM17.5 10.7386C17.8452 10.7386 18.125 11.0185 18.125 11.3636V12.5019C18.125 12.8634 18.125 13.2005 18.1223 13.5128C18.1193 13.858 17.8371 14.1354 17.4919 14.1324C17.1468 14.1294 16.8694 13.8472 16.8724 13.502C16.875 13.1956 16.875 12.8635 16.875 12.5V11.3636C16.875 11.0185 17.1548 10.7386 17.5 10.7386ZM17.4545 15.3093C17.7841 15.4118 17.9683 15.762 17.8659 16.0917C17.7324 16.5211 17.526 16.8934 17.2097 17.2097C16.8934 17.526 16.5211 17.7324 16.0917 17.8659C15.762 17.9683 15.4118 17.7841 15.3093 17.4545C15.2069 17.1249 15.391 16.7746 15.7207 16.6722C15.9883 16.589 16.1746 16.4771 16.3258 16.3258C16.477 16.1746 16.589 15.9883 16.6722 15.7207C16.7746 15.391 17.1249 15.2069 17.4545 15.3093ZM14.1324 17.4919C14.1354 17.8371 13.858 18.1193 13.5128 18.1223C13.2005 18.125 12.8634 18.125 12.5019 18.125H11.7424C11.3972 18.125 11.1174 17.8452 11.1174 17.5C11.1174 17.1548 11.3972 16.875 11.7424 16.875H12.5C12.8635 16.875 13.1956 16.875 13.502 16.8724C13.8472 16.8694 14.1294 17.1468 14.1324 17.4919Z"
fill="#71717A"
fillRule="evenodd"
/>
<path
clipRule="evenodd"
d="M10 1.04167C10.3452 1.04167 10.625 1.32149 10.625 1.66667V18.3333C10.625 18.6785 10.3452 18.9583 10 18.9583C9.65482 18.9583 9.375 18.6785 9.375 18.3333V18.125H9.11966C7.58819 18.125 6.37516 18.125 5.42581 17.9974C4.4488 17.866 3.65801 17.5893 3.03437 16.9656C2.41073 16.342 2.13397 15.5512 2.00261 14.5742C1.87498 13.6248 1.87499 12.4118 1.875 10.8804V9.11966C1.87499 7.58819 1.87498 6.37516 2.00261 5.42582C2.13397 4.4488 2.41073 3.65801 3.03437 3.03437C3.65801 2.41074 4.4488 2.13397 5.42581 2.00262C6.37516 1.87498 7.58819 1.87499 9.11965 1.875L9.375 1.87501V1.66667C9.375 1.32149 9.65482 1.04167 10 1.04167ZM9.375 3.12501H9.16667C7.57765 3.12501 6.44876 3.12633 5.59237 3.24147C4.75397 3.35419 4.27093 3.56558 3.91825 3.91826C3.56558 4.27093 3.35419 4.75397 3.24147 5.59238C3.12633 6.44877 3.125 7.57766 3.125 9.16667V10.8333C3.125 12.4224 3.12633 13.5512 3.24147 14.4076C3.35419 15.246 3.56558 15.7291 3.91825 16.0818C4.27093 16.4344 4.75397 16.6458 5.59237 16.7585C6.44876 16.8737 7.57765 16.875 9.16667 16.875H9.375V3.12501Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
</svg>
);

View File

@ -0,0 +1,19 @@
import {IconSvgProps} from "@/types";
export const PaletteRound = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
focusable="false"
height={size || height}
viewBox="0 0 20 20"
width={size || width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
clipRule="evenodd"
d="M1.04199 4.99999C1.04199 2.81386 2.8142 1.04166 5.00033 1.04166C7.18645 1.04166 8.95866 2.81386 8.95866 4.99999V5.36L10.6531 3.66556C12.1989 2.11973 14.7052 2.11973 16.251 3.66556C17.7969 5.21138 17.7969 7.71766 16.251 9.26349L14.4729 11.0417H15.0003C17.1865 11.0417 18.9587 12.8139 18.9587 15C18.9587 17.1861 17.1865 18.9583 15.0003 18.9583H5.00033C2.8142 18.9583 1.04199 17.1861 1.04199 15V4.99999ZM7.88709 17.7083H15.0003C16.4961 17.7083 17.7087 16.4958 17.7087 15C17.7087 13.5042 16.4961 12.2917 15.0003 12.2917H13.2229L8.24107 17.2734C8.13333 17.4268 8.01498 17.5721 7.88709 17.7083ZM8.95866 14.7881L15.3671 8.3796C16.4248 7.32193 16.4248 5.60711 15.3671 4.54944C14.3095 3.49177 12.5947 3.49177 11.537 4.54944L8.95866 7.12777V14.7881ZM5.00033 2.29166C3.50455 2.29166 2.29199 3.50422 2.29199 4.99999V15C2.29199 16.4958 3.50455 17.7083 5.00033 17.7083C6.4961 17.7083 7.70866 16.4958 7.70866 15V4.99999C7.70866 3.50422 6.4961 2.29166 5.00033 2.29166ZM5.00033 14.7917C4.88527 14.7917 4.79199 14.8849 4.79199 15C4.79199 15.115 4.88527 15.2083 5.00033 15.2083C5.11538 15.2083 5.20866 15.115 5.20866 15C5.20866 14.8849 5.11538 14.7917 5.00033 14.7917ZM3.54199 15C3.54199 14.1946 4.19491 13.5417 5.00033 13.5417C5.80574 13.5417 6.45866 14.1946 6.45866 15C6.45866 15.8054 5.80574 16.4583 5.00033 16.4583C4.19491 16.4583 3.54199 15.8054 3.54199 15Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
</svg>
);

View File

@ -0,0 +1,58 @@
import {IconSvgProps} from "@/types";
export const RadialBlur = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
aria-label="Radial blur"
focusable="false"
height={size || height}
viewBox="0 0 20 20"
width={size || width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
clipRule="evenodd"
d="M10.9211 2.34618C9.32167 2.1523 7.64915 2.45613 6.1457 3.32414C2.45886 5.45274 1.19566 10.1671 3.32426 13.8539C5.45285 17.5408 10.1672 18.804 13.854 16.6754C15.3575 15.8074 16.4569 14.5108 17.0887 13.0287C18.0077 10.873 17.9362 8.32926 16.6755 6.14559C15.4147 3.96192 13.2475 2.62817 10.9211 2.34618ZM5.5207 2.24161C7.2666 1.23361 9.21255 0.879928 11.0716 1.10526C13.7714 1.43252 16.2924 2.98206 17.758 5.52059C19.2236 8.05912 19.3051 11.0172 18.2386 13.5189C17.5042 15.2415 16.2249 16.7499 14.479 17.7579C10.1943 20.2317 4.7155 18.7636 2.24172 14.4789C-0.232051 10.1942 1.236 4.71539 5.5207 2.24161Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
<path
clipRule="evenodd"
d="M8.1247 7.49999C7.77952 7.49999 7.4997 7.77981 7.4997 8.12499C7.4997 8.47017 7.77952 8.74999 8.1247 8.74999C8.46987 8.74999 8.7497 8.47017 8.7497 8.12499C8.7497 7.77981 8.46987 7.49999 8.1247 7.49999ZM6.66636 8.12499C6.66636 7.31957 7.31928 6.66666 8.1247 6.66666C8.93011 6.66666 9.58303 7.31958 9.58303 8.12499C9.58303 8.93041 8.93011 9.58332 8.1247 9.58332C7.31928 9.58332 6.66636 8.93041 6.66636 8.12499ZM11.8747 7.49999C11.5295 7.49999 11.2497 7.77981 11.2497 8.12499C11.2497 8.47017 11.5295 8.74999 11.8747 8.74999C12.2199 8.74999 12.4997 8.47017 12.4997 8.12499C12.4997 7.77981 12.2199 7.49999 11.8747 7.49999ZM10.4164 8.12499C10.4164 7.31957 11.0693 6.66666 11.8747 6.66666C12.6801 6.66666 13.333 7.31957 13.333 8.12499C13.333 8.93041 12.6801 9.58332 11.8747 9.58332C11.0693 9.58332 10.4164 8.93041 10.4164 8.12499ZM8.1247 11.25C7.77952 11.25 7.4997 11.5298 7.4997 11.875C7.4997 12.2202 7.77952 12.5 8.1247 12.5C8.46987 12.5 8.7497 12.2202 8.7497 11.875C8.7497 11.5298 8.46987 11.25 8.1247 11.25ZM6.66636 11.875C6.66636 11.0696 7.31928 10.4167 8.1247 10.4167C8.93011 10.4167 9.58303 11.0696 9.58303 11.875C9.58303 12.6804 8.93011 13.3333 8.1247 13.3333C7.31928 13.3333 6.66636 12.6804 6.66636 11.875ZM11.8747 11.25C11.5295 11.25 11.2497 11.5298 11.2497 11.875C11.2497 12.2202 11.5295 12.5 11.8747 12.5C12.2199 12.5 12.4997 12.2202 12.4997 11.875C12.4997 11.5298 12.2199 11.25 11.8747 11.25ZM10.4164 11.875C10.4164 11.0696 11.0693 10.4167 11.8747 10.4167C12.6801 10.4167 13.333 11.0696 13.333 11.875C13.333 12.6804 12.6801 13.3333 11.8747 13.3333C11.0693 13.3333 10.4164 12.6804 10.4164 11.875Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
<path
d="M12.4997 4.79166C12.4997 5.13684 12.2199 5.41666 11.8747 5.41666C11.5295 5.41666 11.2497 5.13684 11.2497 4.79166C11.2497 4.44648 11.5295 4.16666 11.8747 4.16666C12.2199 4.16666 12.4997 4.44648 12.4997 4.79166Z"
fill="#A1A1AA"
/>
<path
d="M8.7497 4.79166C8.7497 5.13684 8.46987 5.41666 8.1247 5.41666C7.77952 5.41666 7.4997 5.13684 7.4997 4.79166C7.4997 4.44648 7.77952 4.16666 8.1247 4.16666C8.46987 4.16666 8.7497 4.44648 8.7497 4.79166Z"
fill="#A1A1AA"
/>
<path
d="M15.208 7.49999C15.5532 7.49999 15.833 7.77981 15.833 8.12499C15.833 8.47017 15.5532 8.74999 15.208 8.74999C14.8629 8.74999 14.583 8.47017 14.583 8.12499C14.583 7.77981 14.8629 7.49999 15.208 7.49999Z"
fill="#A1A1AA"
/>
<path
d="M4.79136 7.49999C5.13654 7.49999 5.41636 7.77981 5.41636 8.12499C5.41636 8.47017 5.13654 8.74999 4.79136 8.74999C4.44619 8.74999 4.16636 8.47017 4.16636 8.12499C4.16636 7.77981 4.44619 7.49999 4.79136 7.49999Z"
fill="#A1A1AA"
/>
<path
d="M15.208 11.25C15.5532 11.25 15.833 11.5298 15.833 11.875C15.833 12.2202 15.5532 12.5 15.208 12.5C14.8629 12.5 14.583 12.2202 14.583 11.875C14.583 11.5298 14.8629 11.25 15.208 11.25Z"
fill="#A1A1AA"
/>
<path
d="M4.79136 11.25C5.13654 11.25 5.41636 11.5298 5.41636 11.875C5.41636 12.2202 5.13654 12.5 4.79136 12.5C4.44619 12.5 4.16636 12.2202 4.16636 11.875C4.16636 11.5298 4.44619 11.25 4.79136 11.25Z"
fill="#A1A1AA"
/>
<path
d="M12.4997 15.2083C12.4997 15.5535 12.2199 15.8333 11.8747 15.8333C11.5295 15.8333 11.2497 15.5535 11.2497 15.2083C11.2497 14.8631 11.5295 14.5833 11.8747 14.5833C12.2199 14.5833 12.4997 14.8631 12.4997 15.2083Z"
fill="#A1A1AA"
/>
<path
d="M8.7497 15.2083C8.7497 15.5535 8.46987 15.8333 8.1247 15.8333C7.77952 15.8333 7.4997 15.5535 7.4997 15.2083C7.4997 14.8631 7.77952 14.5833 8.1247 14.5833C8.46987 14.5833 8.7497 14.8631 8.7497 15.2083Z"
fill="#A1A1AA"
/>
</svg>
);

View File

@ -0,0 +1,21 @@
import {IconSvgProps} from "@/types";
export const Scaling = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height={size || height}
role="presentation"
viewBox="0 0 24 24"
width={size || width}
{...props}
>
<path
clipRule="evenodd"
d="M11.4072 3.45187L12.21 4.25472C12.4541 4.49879 12.4541 4.89452 12.21 5.1386C11.966 5.38268 11.5702 5.38268 11.3262 5.1386L10.5227 4.33519L9.63886 5.21907L11.0315 6.61174C11.2756 6.85582 11.2756 7.25154 11.0315 7.49562C10.7874 7.7397 10.3917 7.7397 10.1476 7.49562L8.75498 6.10296L7.87109 6.98684L8.6745 7.79025C8.91858 8.03433 8.91858 8.43006 8.6745 8.67413C8.43042 8.91821 8.0347 8.91821 7.79062 8.67413L6.98721 7.87073L6.10332 8.75461L7.49599 10.1473C7.74007 10.3914 7.74007 10.7871 7.49599 11.0312C7.25191 11.2752 6.85618 11.2752 6.61211 11.0312L5.21944 9.63849L4.33556 10.5224L5.13897 11.3258C5.38304 11.5699 5.38304 11.9656 5.13897 12.2097C4.89489 12.4537 4.49916 12.4537 4.25508 12.2097L3.45223 11.4068C3.06534 11.7973 2.78623 12.0911 2.59 12.3483C2.34834 12.665 2.29199 12.8517 2.29199 13.0131C2.29199 13.1745 2.34834 13.3611 2.59 13.6778C2.84267 14.009 3.23276 14.401 3.81341 14.9816L5.01866 16.1869C5.59931 16.7675 5.99128 17.1576 6.32244 17.4103C6.63917 17.6519 6.8258 17.7083 6.98721 17.7083C7.14862 17.7083 7.33524 17.6519 7.65198 17.4103C7.98314 17.1576 8.37511 16.7675 8.95576 16.1869L16.1872 8.95539C16.7679 8.37474 17.158 7.98277 17.4107 7.65161C17.6523 7.33488 17.7087 7.14825 17.7087 6.98684C17.7087 6.82543 17.6523 6.63881 17.4107 6.32207C17.158 5.99091 16.7679 5.59894 16.1872 5.01829L14.982 3.81304C14.4013 3.23239 14.0094 2.8423 13.6782 2.58963C13.3615 2.34797 13.1749 2.29163 13.0134 2.29163C12.852 2.29163 12.6654 2.34797 12.3487 2.58963C12.0915 2.78586 11.7976 3.06497 11.4072 3.45187ZM11.5904 1.59586C12.0184 1.26936 12.4688 1.04163 13.0134 1.04163C13.558 1.04163 14.0085 1.26936 14.4364 1.59586C14.8423 1.90551 15.2932 2.35643 15.8351 2.8984L17.1019 4.16516C17.6438 4.70709 18.0948 5.15799 18.4044 5.56385C18.7309 5.99177 18.9587 6.44224 18.9587 6.98684C18.9587 7.53145 18.7309 7.98191 18.4044 8.40984C18.0948 8.81569 17.6438 9.2666 17.1019 9.80853L9.80889 17.1015C9.26696 17.6435 8.81606 18.0944 8.41021 18.4041C7.98228 18.7306 7.53182 18.9583 6.98721 18.9583C6.4426 18.9583 5.99214 18.7306 5.56421 18.4041C5.15836 18.0944 4.70746 17.6435 4.16552 17.1015L2.89876 15.8347C2.3568 15.2928 1.90587 14.8419 1.59622 14.4361C1.26973 14.0081 1.04199 13.5577 1.04199 13.0131C1.04199 12.4685 1.26973 12.018 1.59622 11.5901C1.90588 11.1842 2.35682 10.7333 2.89879 10.1914L10.1918 2.89841C10.7337 2.35644 11.1846 1.90551 11.5904 1.59586Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
</svg>
);

View File

@ -0,0 +1,23 @@
import {IconSvgProps} from "@/types";
export const TextSquare = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
focusable="false"
height={size || height}
viewBox="0 0 20 20"
width={size || width}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M7.29363 4.20835C6.93542 4.20829 6.60053 4.20824 6.32693 4.24911C6.01785 4.29528 5.70032 4.40444 5.44651 4.68645C5.20118 4.95903 5.11457 5.28386 5.07703 5.59413C5.04193 5.88416 5.04196 6.24367 5.04199 6.64964L5.04199 7.12502C5.04199 7.4702 5.32181 7.75002 5.66699 7.75002C6.01217 7.75002 6.29199 7.4702 6.29199 7.12502V6.68521C6.29199 6.23278 6.29307 5.95012 6.31798 5.74429C6.32966 5.64773 6.34427 5.59208 6.35607 5.5602C6.36604 5.53329 6.37315 5.5254 6.37551 5.52278L6.37619 5.522C6.37733 5.52063 6.37816 5.51963 6.38568 5.51619C6.39953 5.50986 6.43506 5.49683 6.51161 5.48539C6.6817 5.45999 6.92125 5.45835 7.33366 5.45835H8.37533V12.5417H6.91699C6.57181 12.5417 6.29199 12.8215 6.29199 13.1667C6.29199 13.5119 6.57181 13.7917 6.91699 13.7917H11.5003C11.8455 13.7917 12.1253 13.5119 12.1253 13.1667C12.1253 12.8215 11.8455 12.5417 11.5003 12.5417H9.62533V5.45835H10.667C11.0794 5.45835 11.319 5.45999 11.489 5.48539C11.5656 5.49683 11.6011 5.50986 11.615 5.51619C11.6225 5.51963 11.6233 5.52063 11.6245 5.522L11.625 5.52265C11.6274 5.52528 11.6346 5.53329 11.6446 5.5602C11.6564 5.59208 11.671 5.64773 11.6827 5.74429C11.7076 5.95012 11.7087 6.23278 11.7087 6.68521V7.12502C11.7087 7.4702 11.9885 7.75002 12.3337 7.75002C12.6788 7.75002 12.9587 7.4702 12.9587 7.12502L12.9587 6.64963C12.9587 6.24367 12.9587 5.88415 12.9236 5.59413C12.8861 5.28386 12.7995 4.95903 12.5541 4.68645C12.3003 4.40444 11.9828 4.29528 11.6737 4.24911C11.4001 4.20824 11.0652 4.20829 10.707 4.20835H7.29363Z"
fill="#A1A1AA"
/>
<path
clipRule="evenodd"
d="M8.95251 0.0416872C7.02885 0.0416765 5.52132 0.041668 4.34508 0.199809C3.14113 0.361676 2.19111 0.699459 1.44544 1.44513C0.699764 2.19081 0.361981 3.14083 0.200114 4.34478C0.0419731 5.52101 0.0419816 7.02854 0.0419924 8.95221V9.04784C0.0419816 10.9715 0.0419731 12.479 0.200114 13.6553C0.361981 14.8592 0.699764 15.8092 1.44544 16.5549C2.19111 17.3006 3.14113 17.6384 4.34508 17.8002C5.52132 17.9584 7.02885 17.9584 8.95252 17.9584H9.04814C10.9718 17.9584 12.4793 17.9584 13.6556 17.8002C14.8595 17.6384 15.8095 17.3006 16.5552 16.5549C17.3009 15.8092 17.6387 14.8592 17.8005 13.6553C17.9587 12.479 17.9587 10.9715 17.9587 9.04783V8.95221C17.9587 7.02855 17.9587 5.52102 17.8005 4.34478C17.6387 3.14083 17.3009 2.19081 16.5552 1.44513C15.8095 0.699459 14.8595 0.361676 13.6556 0.199809C12.4793 0.041668 10.9718 0.0416765 9.04814 0.0416872H8.95251ZM2.32932 2.32902C2.80404 1.8543 3.4463 1.58189 4.51164 1.43866C5.59496 1.29301 7.01847 1.29169 9.00033 1.29169C10.9822 1.29169 12.4057 1.29301 13.489 1.43866C14.5543 1.58189 15.1966 1.8543 15.6713 2.32902C16.146 2.80373 16.4185 3.446 16.5617 4.51133C16.7073 5.59465 16.7087 7.01817 16.7087 9.00002C16.7087 10.9819 16.7073 12.4054 16.5617 13.4887C16.4185 14.554 16.146 15.1963 15.6713 15.671C15.1966 16.1457 14.5543 16.4181 13.489 16.5614C12.4057 16.707 10.9822 16.7084 9.00033 16.7084C7.01847 16.7084 5.59496 16.707 4.51164 16.5614C3.4463 16.4181 2.80404 16.1457 2.32932 15.671C1.85461 15.1963 1.5822 14.554 1.43897 13.4887C1.29332 12.4054 1.29199 10.9819 1.29199 9.00002C1.29199 7.01817 1.29332 5.59465 1.43897 4.51133C1.5822 3.446 1.85461 2.80373 2.32932 2.32902Z"
fill="#A1A1AA"
fillRule="evenodd"
/>
</svg>
);

View File

@ -34,7 +34,7 @@ export const Hero = () => {
<div className="flex justify-center w-full md:hidden">
<Chip
as={NextLink}
className="bg-foreground-100/50 border-1 hover:bg-foreground-100/80 border-foreground-200/50 cursor-pointer"
className="bg-default-200/50 border-1 hover:bg-default-200/80 border-default-400/50 cursor-pointer"
classNames={{
content: "font-semibold text-foreground text-xs ",
}}

View File

@ -215,7 +215,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
{versionDropdown}
<Chip
as={NextLink}
className="hidden sm:flex bg-foreground-100/50 border-1 hover:bg-foreground-100/80 border-foreground-200/50 cursor-pointer"
className="hidden sm:flex bg-default-200/50 border-1 hover:bg-default-200/80 border-default-400/50 cursor-pointer"
classNames={{
content: "font-semibold text-foreground text-xs ",
}}
@ -333,6 +333,17 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
</NextLink>
</NavbarItem>
<NavbarItem>
<NextLink
className={navLinkClasses}
color="foreground"
data-active={pathname.includes("themes")}
href="/themes"
onClick={() => handlePressNavbarItem("Themes", "/themes")}
>
Theme
</NextLink>
</NavbarItem>
<NavbarItem>
<FbRoadmapLink className={navLinkClasses} />
</NavbarItem>

View File

@ -0,0 +1,128 @@
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 (
<div className="flex">
<Popover
isOpen={isOpen}
placement="bottom"
onClose={() => onClose(selectedColor)}
onOpenChange={setIsOpen}
>
<PopoverTrigger>
<Button
fullWidth
aria-label={`Change ${type} color`}
className={clsx(
getColor(type),
"rounded-lg min-w-9 w-9 h-9",
"border border-black/10 dark:border-white/10",
)}
size="sm"
style={{
color: ["background", "foreground", "focus", "overlay"].includes(type)
? readableColor(selectedColor)
: undefined,
}}
/>
</PopoverTrigger>
<PopoverContent>
<div className="flex flex-col gap-2 max-w-48 my-2">
<div className="grid grid-cols-5 gap-2">
{selectedColorValues
?.slice(0, selectedColorValues.length - 1)
.map((colorValue, index: number) => (
<div key={index} className="flex flex-col items-center">
<div
className="h-6 w-6 rounded"
style={{backgroundColor: colorValuesToRgb(colorValue)}}
/>
<span className="text-xs mt-1">{index === 0 ? 50 : index * 100}</span>
</div>
))}
</div>
<HexColorPicker className="!w-full" color={selectedColor} onChange={handleChange} />
<HexColorInput
prefixed
className="px-2 py-1 w-full rounded-md"
color={selectedColor}
onChange={handleChange}
/>
</div>
</PopoverContent>
</Popover>
</div>
);
}
function getColor(type: ColorPickerType) {
switch (type) {
case "primary":
return "bg-primary text-primary-foreground";
case "secondary":
return "bg-secondary text-secondary-foreground";
case "success":
return "bg-success text-success-foreground";
case "warning":
return "bg-warning text-warning-foreground";
case "danger":
return "bg-danger text-danger-foreground";
case "background":
return "bg-background text-foreground";
case "foreground":
return "bg-foreground text-black";
case "default":
return "bg-default";
case "content1":
return "bg-content1 text-content1-foreground";
case "content2":
return "bg-content2 text-content2-foreground";
case "content3":
return "bg-content3 text-content3-foreground";
case "content4":
return "bg-content4 text-content4-foreground";
case "divider":
return "bg-divider";
case "focus":
return "bg-focus";
case "overlay":
return "bg-overlay";
default:
return undefined;
}
}

View File

@ -0,0 +1,20 @@
import {clsx} from "@heroui/shared-utils";
interface ConfigurationSectionProps {
children: React.ReactNode;
id?: string;
title: string;
icon?: React.ReactNode;
}
export function ConfigSection({children, id, title, icon}: ConfigurationSectionProps) {
return (
<div id={id}>
<div className="text-[#71717A] dark:text-[#A1A1AA] text-md font-medium leading-7 flex gap-1.5 items-center">
<div>{icon}</div>
<div>{title}</div>
</div>
<div className={clsx("flex flex-wrap gap-2 mt-3")}>{children}</div>
</div>
);
}

View File

@ -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 (
<div className="flex gap-2">
<Tooltip content={isLight ? "Dark" : "Light"}>
<Button isIconOnly color="secondary" size="sm" variant="flat" onClick={onToggleTheme}>
{isLight ? (
<Icon className="text-lg" icon={MoonIcon} />
) : (
<Icon className="text-lg" icon={SunIcon} />
)}
</Button>
</Tooltip>
<Tooltip content="Reset theme">
<Button isIconOnly color="secondary" size="sm" variant="flat" onClick={onResetTheme}>
<Icon className="text-lg" icon={UndoLeftIcon} />
</Button>
</Tooltip>
<Tooltip content="Copy configuration">
<Button isIconOnly color="secondary" size="sm" variant="flat" onClick={handleCopyConfig}>
Copy
</Button>
</Tooltip>
</div>
);
}

View File

@ -0,0 +1,92 @@
import {Tooltip} from "@heroui/react";
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 {templates} from "../../templates";
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 (
<ConfigSection icon={<Filters className="h-4 w-4" />} id={colorsId} title="Base colors">
<Tooltip content="primary">
<div>
<ColorPicker
hexColor={config[theme].baseColor.primary}
type="primary"
onChange={(hexColor) =>
setCssColor("primary", hexColor, templates[0].value[theme].baseColor.primary, theme)
}
onClose={(hexColor) => setBaseColor({primary: hexColor}, theme, syncThemes)}
/>
</div>
</Tooltip>
<Tooltip content="secondary">
<div>
<ColorPicker
hexColor={config[theme].baseColor.secondary}
type="secondary"
onChange={(hexColor) =>
setCssColor(
"secondary",
hexColor,
templates[0].value[theme].baseColor.secondary,
theme,
)
}
onClose={(hexColor) => setBaseColor({secondary: hexColor}, theme, syncThemes)}
/>
</div>
</Tooltip>
<Tooltip content="success">
<div>
<ColorPicker
hexColor={config[theme].baseColor.success}
type="success"
onChange={(hexColor) =>
setCssColor("success", hexColor, templates[0].value[theme].baseColor.success, theme)
}
onClose={(hexColor) => setBaseColor({success: hexColor}, theme, syncThemes)}
/>
</div>
</Tooltip>
<Tooltip content="warning">
<div>
<ColorPicker
hexColor={config[theme].baseColor.warning}
type="warning"
onChange={(hexColor) =>
setCssColor("warning", hexColor, templates[0].value[theme].baseColor.warning, theme)
}
onClose={(hexColor) => setBaseColor({warning: hexColor}, theme, syncThemes)}
/>
</div>
</Tooltip>
<Tooltip content="danger">
<div>
<ColorPicker
hexColor={config[theme].baseColor.danger}
type="danger"
onChange={(hexColor) =>
setCssColor("danger", hexColor, templates[0].value[theme].baseColor.danger, theme)
}
onClose={(hexColor) => setBaseColor({danger: hexColor}, theme, syncThemes)}
/>
</div>
</Tooltip>
</ConfigSection>
);
}

View File

@ -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 (
<ConfigSection icon={<Crop className="w-4 h-4" />} title="Border width">
{BORDER_WIDTHS.map(({title, className}) => (
<EditableButton
key={title}
aria-label={`Set border width to ${title}`}
className={className}
setValue={setBorderWidthValue}
title={title}
value={borderWidthValue}
/>
))}
</ConfigSection>
);
}

View File

@ -0,0 +1,77 @@
import {Tooltip} from "@heroui/react";
import {baseColorsId} from "../../constants";
import {setCssContentColor} from "../../css-vars";
import {useThemeBuilder} from "../../provider";
import {Config, ThemeType} from "../../types";
import {templates} from "../../templates";
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 (
<ConfigSection
icon={<PaletteRound className="w-4 h-4" />}
id={baseColorsId}
title="Content colors"
>
<Tooltip content={"content-1"}>
<div>
<ColorPicker
hexColor={config[theme].contentColor.content1}
type="content1"
onChange={(hexColor) =>
setCssContentColor(1, hexColor, templates[0].value[theme].contentColor.content1)
}
onClose={(hexColor) => setContentColor({content1: hexColor}, theme)}
/>
</div>
</Tooltip>
<Tooltip content={"content-2"}>
<div>
<ColorPicker
hexColor={config[theme].contentColor.content2}
type="content2"
onChange={(hexColor) =>
setCssContentColor(2, hexColor, templates[0].value[theme].contentColor.content2)
}
onClose={(hexColor) => setContentColor({content2: hexColor}, theme)}
/>
</div>
</Tooltip>
<Tooltip content={"content-3"}>
<div>
<ColorPicker
hexColor={config[theme].contentColor.content3}
type="content3"
onChange={(hexColor) =>
setCssContentColor(3, hexColor, templates[0].value[theme].contentColor.content3)
}
onClose={(hexColor) => setContentColor({content3: hexColor}, theme)}
/>
</div>
</Tooltip>
<Tooltip content={"content-4"}>
<div>
<ColorPicker
hexColor={config[theme].contentColor.content4}
type="content4"
onChange={(hexColor) =>
setCssContentColor(4, hexColor, templates[0].value[theme].contentColor.content4)
}
onClose={(hexColor) => setContentColor({content4: hexColor}, theme)}
/>
</div>
</Tooltip>
</ConfigSection>
);
}

View File

@ -0,0 +1,35 @@
import {defaultColorsId} from "../../constants";
import {setCssColor} from "../../css-vars";
import {useThemeBuilder} from "../../provider";
import {templates} from "../../templates";
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 (
<ConfigSection
icon={<PaletteRound className="h-4 w-4" />}
id={defaultColorsId}
title="Default Color"
>
<ColorPicker
hexColor={config[theme].defaultColor.default}
type="default"
onChange={(hexColor) =>
setCssColor("default", hexColor, templates[0].value[theme].defaultColor.default, theme)
}
onClose={(hexColor) => setDefaultColor({default: hexColor}, theme, false)}
/>
</ConfigSection>
);
}

View File

@ -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 (
<ConfigSection icon={<RadialBlur className="h-4 w-4" />} title="Disable Opacity">
<ValueButton
currentValue={config.layout.otherParams.disabledOpacity}
setValue={(value) => {
handleChange("disabledOpacity", value);
}}
value={"0.2"}
/>
<ValueButton
currentValue={config.layout.otherParams.disabledOpacity}
setValue={(value) => {
handleChange("disabledOpacity", value);
}}
value={"0.4"}
/>
<ValueButton
currentValue={config.layout.otherParams.disabledOpacity}
setValue={(value) => {
handleChange("disabledOpacity", value);
}}
value={"0.6"}
/>
<ValueButton
currentValue={config.layout.otherParams.disabledOpacity}
setValue={(value) => {
handleChange("disabledOpacity", value);
}}
value={"0.8"}
/>
</ConfigSection>
);
}

View File

@ -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 (
<Button
className={clsx(
"group h-auto py-4 flex flex-col justify-between gap-y-2 min-w-auto w-auto border-black/20 dark:border-white/20",
value === title ? "border-black/60 dark:border-white/60" : "",
)}
variant="bordered"
onPress={() => {
setValue(title);
}}
>
<div
className={clsx(
"h-7 w-7 border-t-2 border-l-2 border-blue-400 bg-gradient-to-b from-[#0077ff1A] to-[#92c5ff00]",
className,
)}
/>
<div className="relative text-tiny font-medium leading-5 text-black/40 dark:text-white/40">
<div className="">{title}</div>
</div>
</Button>
);
};
export default EditableButton;

View File

@ -0,0 +1,60 @@
import {Button} from "@heroui/react";
import {clsx} from "@heroui/shared-utils";
import {FontName, FontType} from "../../types";
interface FontButtonProps {
title: FontName;
className: string;
value: string | undefined;
setValue: (value: FontType) => void;
}
interface FontStyle {
fontFamily: string;
letterSpacing?: string;
}
function getFontStyle(fontName: FontName | undefined): FontStyle {
if (!fontName) {
return {fontFamily: "'Inter', sans-serif"};
}
switch (fontName) {
case "Inter":
return {fontFamily: "'Inter', sans-serif"};
case "Roboto":
return {fontFamily: "'Roboto', sans-serif"};
case "Outfit":
return {fontFamily: "'Outfit', sans-serif"};
case "Lora":
return {fontFamily: "'Lora', serif"};
default:
return {fontFamily: "'Inter', sans-serif"};
}
}
const FontButton = ({title, value, setValue}: FontButtonProps) => {
const style = getFontStyle(title);
return (
<Button
className={clsx(
"group h-24 flex flex-col justify-center items-center gap-y-2 px-0 border-black/20 dark:border-white/20",
value === title ? "border-black/60 dark:border-white/60" : "",
)}
variant="bordered"
onPress={() => {
setValue(title);
}}
>
<div className="font-medium text-2xl text-black/60 dark:text-white/80" style={style}>
Ag12
</div>
<div className="relative text-tiny text-black/40 dark:text-white/60">
<div className="">{title}</div>
</div>
</Button>
);
};
export default FontButton;

View File

@ -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 (
<ConfigSection icon={<TextSquare className="h-4 w-4" />} title="Font Family">
<FontButton className="rounded-tl-none" setValue={setFont} title="Inter" value={font} />
<FontButton className="rounded-tl-sm" setValue={setFont} title="Roboto" value={font} />
<FontButton className="rounded-tl-md" setValue={setFont} title="Outfit" value={font} />
<FontButton className="rounded-tl-lg" setValue={setFont} title="Lora" value={font} />
</ConfigSection>
);
}

View File

@ -0,0 +1,486 @@
import {useEffect, useState, useMemo} 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, ChevronUpIcon, 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 {
config,
resetConfig,
setConfiguration,
templateTheme,
setTemplateTheme,
setRadiusValue,
setBorderWidthValue,
} = useThemeBuilder();
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
const themeProps = useTheme();
const theme = themeProps.theme as ThemeType;
const prevTheme = usePrevious(theme);
const [, setLsConfig] = useLocalStorage<Config>(configKey, initialConfig);
const [syncThemes] = useLocalStorage<boolean>(syncThemesKey, true);
const syncIcon = syncThemes ? <Icon className="flex-shrink-0" icon={LinkSquareIcon} /> : null;
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [selectedSection, setSelectedSection] = useState<
"none" | "color" | "radius" | "font" | "opacity" | "scaling" | "borderWidths"
>("none");
const {theme: currentTheme} = useTheme();
/**
* Update the CSS variables and the configuration when the theme changes.
*/
useEffect(() => {
// Set the CSS variables when the theme changes
if (prevTheme !== theme) {
setAllCssVars(config, theme);
}
// Set the configuration in the local storage when the theme changes
if (prevTheme === theme) {
setLsConfig(config);
}
}, [config, theme, prevTheme]);
useEffect(() => {
return () => {
const template = templates[0];
setConfiguration(template.value, theme, syncThemes);
setAllCssVars(template.value, theme);
setSelectedTemplate(template);
setTemplateTheme(template.name);
};
}, []);
useEffect(() => {
setRadiusValue(templateTheme === "elegant" ? "none" : "md");
setBorderWidthValue(templateTheme === "elegant" ? "thin" : "medium");
}, [templateTheme]);
/**
* Reset the theme to the default one.
*/
function handleResetTheme() {
if (selectedTemplate) {
setConfiguration(selectedTemplate.value, theme, syncThemes);
setAllCssVars(selectedTemplate.value, theme);
} else {
const config = resetConfig(theme, syncThemes);
setAllCssVars(config, theme);
}
setLsConfig(config);
}
function handleCopy() {
navigator.clipboard.writeText(JSON.stringify(generatePluginConfig(config), null, 2));
}
const DesktopView = useMemo(() => {
return (
<Card className="h-auto w-[350px] hidden md:block md:fixed right-3 top-28 z-30 mx-auto m-3">
<CardHeader className="flex justify-between p-4 pb-3">
<div className="flex gap-x-4 items-center">
<div className="text-xl font-medium leading-8 text-default-800 ">Theme</div>
<Button
className="text-tiny h-9 bg-default-200 flex items-center"
size="sm"
onPress={handleResetTheme}
>
Reset
<RotateLeftLinearIcon className="h-4 w-4" />
</Button>
</div>
<div className="h-9">
<ThemeSwitch classNames={{wrapper: "dark:!text-default-500"}} />
</div>
</CardHeader>
<Divider className="bg-default-200" />
<CardBody className="flex flex-col p-4 px-6 h-[60vh] overflow-y-scroll scrollbar-hide">
<ScrollShadow className="pt-1 pb-6 scrollbar-hide" orientation="vertical">
<SelectTemplate
currentTheme={currentTheme}
name={selectedTemplate?.name ?? templateTheme}
onChange={(template) => {
setConfiguration(template.value, theme, syncThemes);
setAllCssVars(template.value, theme);
setSelectedTemplate(template);
setTemplateTheme(template.name);
}}
/>
<div className="flex flex-col gap-6 mt-6">
<DefaultColors config={config} theme={theme} />
<BaseColors
config={config}
syncIcon={syncIcon}
syncThemes={syncThemes}
theme={theme}
/>
<ContentColors config={config} theme={theme} />
<LayoutColors config={config} syncThemes={syncThemes} theme={theme} />
<Fonts />
<Radiuses />
<BorderWidths />
<Scaling />
<DisableOpacity config={config} />
</div>
</ScrollShadow>
</CardBody>
<Divider className="bg-default-200" />
<CardFooter className="flex flex-col h-auto">
<Button fullWidth className="text-white" color="primary" onPress={handleCopy}>
Copy Theme
</Button>
<div className="text-tiny mt-2 text-default-600">
Learn how to setup your theme{" "}
<Link
className="text-default-800 text-tiny underline cursor-pointer"
href="/docs/customization/theme"
>
here
</Link>
</div>
</CardFooter>
</Card>
);
}, [
config,
handleCopy,
handleResetTheme,
selectedTemplate,
syncIcon,
syncThemes,
theme,
templateTheme,
currentTheme,
]);
const MobileView = useMemo(() => {
return (
<div className="md:hidden w-screen fixed bottom-0 right-0 left-0 z-40 dark:bg-[#18181B] bg-[#ffffff] overflow-hidden rounded-t-full shadow-inner">
<Button
disableRipple
isIconOnly
className="dark:bg-[#18181B] bg-[#d4d4d8] group hover:text-default-600 text-default-400 left-1/2 transform -translate-x-1/2 w-full flex-col"
onPress={() => {
setIsDrawerOpen(!isDrawerOpen);
}}
>
<ChevronUpIcon
className="w-6 h-6 dark:text-white/20 text-black/20 group-hover:text-black/80 group-hover:dark:text-white/80"
strokeWidth={2}
/>
</Button>
<Drawer
hideCloseButton
isOpen={isDrawerOpen}
placement="bottom"
onClose={() => {
setIsDrawerOpen(false);
}}
>
<DrawerContent className="backdrop-blur-2xl dark:bg-[#18181B] bg-[#ffffff] max-h-[56rem]">
<Button
isIconOnly
className="group fixed top-0 right-0 dark:bg-white/10 bg-[#d4d4d8] data-[hover=true]:bg-black/30 dark:data-[hover=true]:bg-white/20 z-50 min-w-8 w-8 h-8 rounded-full m-1"
onPress={() => {
setIsDrawerOpen(false);
setSelectedSection("none");
}}
>
<CloseIcon className="h-4 w-4" />
</Button>
{selectedSection === "none" && (
<div className="flex w-full flex-start overflow-x-scroll scrollbar-hide py-6 px-4 h-30 fixed top-5">
{templates.map((template) => {
return (
<div key={template.name} className="flex flex-col items-center px-2">
<Button
className={clsx(
"p-0 min-w-0 w-auto h-12 border border-black/5 gap-0",
templateTheme === template.name ? "outline-2 outline-foreground-800" : "",
)}
onPress={() => {
setConfiguration(template.value, theme, syncThemes);
setAllCssVars(template.value, theme);
setSelectedTemplate(template);
setTemplateTheme(template.name);
}}
>
<Swatch
className="h-full"
colors={
currentTheme === "dark"
? {
background: template.value.dark.layoutColor.background,
...template.value.dark.baseColor,
}
: {
background: template.value.light.layoutColor.background,
...template.value.light.baseColor,
}
}
innerClassName="w-4"
/>
</Button>
<div className="text-sm dark:text-white/60 text-black/60 my-1">
{template.name}
</div>
</div>
);
})}
</div>
)}
<div className="overflow-x-scroll scrollbar-hide p-2 px-4 pt-16">
<ScrollShadow orientation="vertical">
<div className="flex flex-col items-center gap-y-8">
{selectedSection === "none" && (
<>
<div className="w-full grid grid-cols-4 gap-4 flex-wrap items-center justify-around pt-20">
<Button
className="col-span-2 h-14 flex items-center justify-center sm:justify-around gap-x-3 dark:bg-[#3f3f46] bg-[#d4d4d8] dark:text-white"
onPress={() => {
setSelectedSection("color");
}}
>
<Filters className="h-4 w-4 hidden sm:flex" />
<span className="mx-2 w-14 text-sm sm:text-base">Colors</span>{" "}
<ChevronIcon className="h-5 w-5 rotate-180" />
</Button>
<Button
className="col-span-2 h-14 flex items-center justify-center sm:justify-around gap-x-3 dark:bg-[#3f3f46] bg-[#d4d4d8] dark:text-white"
onPress={() => {
setSelectedSection("radius");
}}
>
<Crop className="h-4 w-4 hidden sm:flex" />
<span className="mx-2 w-14 text-sm sm:text-base">Radius</span>{" "}
<ChevronIcon className="h-5 w-5 rotate-180" />
</Button>
<Button
className="col-span-2 h-14 text-lg flex items-center justify-center sm:justify-around gap-x-3 dark:bg-[#3f3f46] bg-[#d4d4d8] dark:text-white"
onPress={() => {
setSelectedSection("borderWidths");
}}
>
<CropMinimalistic className="h-4 w-4 hidden sm:flex" />
<span className="mx-2 w-14 text-sm sm:text-base">Border</span>{" "}
<ChevronIcon className="h-5 w-5 rotate-180" />
</Button>
<Button
className="col-span-2 h-14 text-lg flex items-center justify-center sm:justify-around gap-x-3 dark:bg-[#3f3f46] bg-[#d4d4d8] dark:text-white"
onPress={() => {
setSelectedSection("opacity");
}}
>
<RadialBlur className="h-4 w-4 hidden sm:flex" />
<span className="mx-2 w-14 text-sm sm:text-base">Opacity</span>{" "}
<ChevronIcon className="h-5 w-5 rotate-180" />
</Button>
<Button
className="col-span-2 h-14 text-lg flex items-center justify-center sm:justify-around gap-x-3 dark:bg-[#3f3f46] bg-[#d4d4d8] dark:text-white"
onPress={() => {
setSelectedSection("font");
}}
>
<RadialBlur className="h-4 w-4 hidden sm:flex" />
<span className="mx-2 w-14 text-sm sm:text-base">Font</span>{" "}
<ChevronIcon className="h-5 w-5 rotate-180" />
</Button>
<Button
className="col-span-2 h-14 text-lg flex items-center justify-center sm:justify-around gap-x-3 dark:bg-[#3f3f46] bg-[#d4d4d8] dark:text-white"
onPress={() => {
setSelectedSection("scaling");
}}
>
<ScalingIcon className="h-6 w-6 hidden sm:flex" />
<span className="mx-2 w-14 text-sm sm:text-base">Scaling</span>{" "}
<ChevronIcon className="h-5 w-5 rotate-180" />
</Button>
</div>
</>
)}
{selectedSection === "color" && (
<div className="w-full h-auto">
<Button
isIconOnly
className="absolute left-3 top-1 text-black/60 dark:text-white/60 dark:data-[hover=true]:bg-white/20 data-[hover=true]:bg-black/10 cursor-pointer"
variant="light"
onPress={() => {
setSelectedSection("none");
}}
>
<ArrowLeftIcon className="h-5 w-5" strokeWidth={2} />
</Button>
<div className="flex flex-col gap-y-4 h-auto p-4">
<DefaultColors config={config} theme={theme} />
<BaseColors
config={config}
syncIcon={syncIcon}
syncThemes={syncThemes}
theme={theme}
/>
<ContentColors config={config} theme={theme} />
<LayoutColors config={config} syncThemes={syncThemes} theme={theme} />
</div>
</div>
)}
{selectedSection === "radius" && (
<div className="w-full h-full">
<Button
isIconOnly
className="absolute left-3 top-1 text-black/60 dark:text-white/60 dark:data-[hover=true]:bg-white/20 data-[hover=true]:bg-black/10 cursor-pointer"
variant="light"
onPress={() => {
setSelectedSection("none");
}}
>
<ArrowLeftIcon className="h-5 w-5" strokeWidth={2} />
</Button>
<div className="p-4">
<Radiuses />
</div>
</div>
)}
{selectedSection === "borderWidths" && (
<div className="w-full h-full">
<Button
isIconOnly
className="absolute left-3 top-1 text-black/60 dark:text-white/60 dark:data-[hover=true]:bg-white/20 data-[hover=true]:bg-black/10 cursor-pointer"
variant="light"
onPress={() => {
setSelectedSection("none");
}}
>
<ArrowLeftIcon className="h-5 w-5" strokeWidth={2} />
</Button>
<div className="p-4">
<BorderWidths />
</div>
</div>
)}
{selectedSection === "font" && (
<div className="w-full h-full">
<Button
isIconOnly
className="absolute left-3 top-1 text-black/60 dark:text-white/60 dark:data-[hover=true]:bg-white/20 data-[hover=true]:bg-black/10 cursor-pointer"
variant="light"
onPress={() => {
setSelectedSection("none");
}}
>
<ArrowLeftIcon className="h-5 w-5" strokeWidth={2} />
</Button>
<div className="p-4">
<Fonts />
</div>
</div>
)}
{selectedSection === "opacity" && (
<div className="w-full h-full">
<Button
isIconOnly
className="absolute left-3 top-1 text-black/60 dark:text-white/60 dark:data-[hover=true]:bg-white/20 data-[hover=true]:bg-black/10 cursor-pointer"
variant="light"
onPress={() => {
setSelectedSection("none");
}}
>
<ArrowLeftIcon className="h-5 w-5" strokeWidth={2} />
</Button>
<div className="p-4">
<DisableOpacity config={config} />
</div>
</div>
)}
{selectedSection === "scaling" && (
<div className="w-full h-full">
<Button
isIconOnly
className="absolute left-3 top-1 text-black/60 dark:text-white/60 dark:data-[hover=true]:bg-white/20 data-[hover=true]:bg-black/10 cursor-pointer"
variant="light"
onPress={() => {
setSelectedSection("none");
}}
>
<ArrowLeftIcon className="h-5 w-5" strokeWidth={2} />
</Button>
<div className="p-4">
<Scaling />
</div>
</div>
)}
</div>
</ScrollShadow>
</div>
<Divider className="my-2 p-0" />
<div className="flex flex-col items-center px-8 pb-4">
<Button fullWidth className="text-white bg-blue-500" onPress={handleCopy}>
Copy Theme
</Button>
</div>
</DrawerContent>
</Drawer>
</div>
);
}, [
config,
handleCopy,
isDrawerOpen,
selectedSection,
syncIcon,
syncThemes,
theme,
templateTheme,
currentTheme,
]);
return (
<div id="configuration-container">
{DesktopView}
{MobileView}
</div>
);
}

View File

@ -0,0 +1,69 @@
import {Tooltip} from "@heroui/react";
import {otherColorsId} from "../../constants";
import {useThemeBuilder} from "../../provider";
import {Config, ThemeType} from "../../types";
import {ColorPicker} from "../color-picker";
import {ConfigSection} from "../config-section";
import {setCssOtherColor} from "../../css-vars";
import {PaletteIcon} from "@/components/icons";
interface OtherColorsProps {
config: Config;
syncThemes: boolean;
theme: ThemeType;
}
export function LayoutColors({config, syncThemes, theme}: OtherColorsProps) {
const {setLayoutColor} = useThemeBuilder();
return (
<ConfigSection
icon={<PaletteIcon className="w-4 h-4" />}
id={otherColorsId}
title="Layout colors"
>
<Tooltip content="background">
<div>
<ColorPicker
hexColor={config[theme].layoutColor.background}
type="background"
onChange={(hexColor) => setCssOtherColor("background", hexColor)}
onClose={(hexColor) => setLayoutColor({background: hexColor}, theme, syncThemes)}
/>
</div>
</Tooltip>
<Tooltip content="foreground">
<div>
<ColorPicker
hexColor={config[theme].layoutColor.foreground}
type="foreground"
onChange={(hexColor) => setCssOtherColor("foreground", hexColor)}
onClose={(hexColor) => setLayoutColor({foreground: hexColor}, theme, false)}
/>
</div>
</Tooltip>
<Tooltip content="focus">
<div>
<ColorPicker
hexColor={config[theme].layoutColor.focus}
type="focus"
onChange={(hexColor) => setCssOtherColor("focus", hexColor)}
onClose={(hexColor) => setLayoutColor({focus: hexColor}, theme, syncThemes)}
/>
</div>
</Tooltip>
<Tooltip content="overlay">
<div>
<ColorPicker
hexColor={config[theme].layoutColor.overlay}
type="overlay"
onChange={(hexColor) => setCssOtherColor("overlay", hexColor)}
onClose={(hexColor) => setLayoutColor({overlay: hexColor}, theme, false)}
/>
</div>
</Tooltip>
</ConfigSection>
);
}

View File

@ -0,0 +1,38 @@
import {setOtherCssParams} from "../../css-vars";
import {useThemeBuilder} from "../../provider";
import {Config} from "../../types";
import {ConfigSection} from "../config-section";
import {NumberInput} from "../number-input";
interface OtherProps {
config: Config;
}
export function Other({config}: OtherProps) {
const {setOtherParams} = useThemeBuilder();
const handleChange = (key: keyof Config["layout"]["otherParams"], value: string) => {
setOtherParams({[key]: value});
setOtherCssParams(key, value);
};
return (
<ConfigSection title="Other">
<NumberInput
label="Disabled opacity (0-1)"
value={config.layout.otherParams.disabledOpacity}
onChange={(value) => handleChange("disabledOpacity", value)}
/>
<NumberInput
label="Divider weight (px)"
value={config.layout.otherParams.dividerWeight}
onChange={(value) => handleChange("dividerWeight", value)}
/>
<NumberInput
label="Hover opacity (0-1)"
value={config.layout.otherParams.hoverOpacity}
onChange={(value) => handleChange("hoverOpacity", value)}
/>
</ConfigSection>
);
}

View File

@ -0,0 +1,50 @@
import {useThemeBuilder} from "../../provider";
import {ConfigSection} from "../config-section";
import EditableButton from "./editable-button";
import {CropMinimalistic} from "@/components/icons/crop";
export function Radiuses() {
const {radiusValue, setRadiusValue} = useThemeBuilder();
return (
<ConfigSection icon={<CropMinimalistic className="h-4 w-4" />} title="Radius">
<EditableButton
aria-label="No border radius"
className="rounded-tl-none"
setValue={setRadiusValue}
title="none"
value={radiusValue}
/>
<EditableButton
aria-label="sm border radius"
className="rounded-tl-sm"
setValue={setRadiusValue}
title="sm"
value={radiusValue}
/>
<EditableButton
aria-label="md border radius"
className="rounded-tl-md"
setValue={setRadiusValue}
title="md"
value={radiusValue}
/>
<EditableButton
aria-label="lg border radius"
className="rounded-tl-lg"
setValue={setRadiusValue}
title="lg"
value={radiusValue}
/>
<EditableButton
aria-label="full border radius"
className="rounded-tl-full"
setValue={setRadiusValue}
title="full"
value={radiusValue}
/>
</ConfigSection>
);
}

View File

@ -0,0 +1,25 @@
import {useThemeBuilder} from "../../provider";
import {ConfigSection} from "../config-section";
import ValueButton from "./value-button";
import {Scaling as ScalingIcon} from "@/components/icons/scaling";
export function Scaling() {
const {scaling, setScaling} = useThemeBuilder();
const scaleValues = [90, 95, 100, 105, 110];
return (
<ConfigSection icon={<ScalingIcon className="h-4 w-4" />} title="Scaling">
{scaleValues.map((value) => (
<ValueButton
key={value}
currentValue={scaling}
endContent="%"
setValue={setScaling}
value={value}
/>
))}
</ConfigSection>
);
}

View File

@ -0,0 +1,19 @@
import {clsx} from "@heroui/shared-utils";
import {ConfigColors} from "../../types";
interface SwatchProps {
colors: {background: string} & ConfigColors["baseColor"];
className?: string;
innerClassName?: string;
}
export default function Swatch({colors, className, innerClassName}: SwatchProps) {
return (
<div className={clsx("flex h-6", className)}>
{Object.entries(colors).map(([key, value]) => (
<div key={key} className={clsx("w-2 h-full", innerClassName)} style={{background: value}} />
))}
</div>
);
}

View File

@ -0,0 +1,38 @@
import {Button} from "@heroui/react";
import {clsx} from "@heroui/shared-utils";
interface ValueButtonProps<T extends string | number> {
currentValue: T;
value: T;
setValue: (value: any) => void;
endContent?: string;
}
const ValueButton = ({
currentValue,
value,
setValue,
endContent,
}: ValueButtonProps<string | number>) => {
return (
<Button
isIconOnly
aria-checked={value === currentValue}
aria-label={`Select ${value}${endContent ?? ""}`}
className={clsx(
"group h-auto w-auto rounded-md p-0.5 px-1 text-sm font-normal border-black/20 dark:border-white/20",
value === currentValue ? "border-black/60 dark:border-white/60" : "",
)}
role="radio"
variant="bordered"
onPress={() => {
setValue(value);
}}
>
{value}
{endContent}
</Button>
);
};
export default ValueButton;

View File

@ -0,0 +1,21 @@
import {Input} from "@heroui/react";
import {floatNumberPattern} from "../constants";
interface NumberInputProps {
label: string;
value: string;
onChange: (value: string) => void;
}
export function NumberInput({label, value, onChange}: NumberInputProps) {
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = event.target.value;
if (floatNumberPattern.test(value) || !value) {
onChange(value);
}
}
return <Input label={label} size="sm" value={value.toString()} onChange={handleChange} />;
}

View File

@ -0,0 +1,75 @@
import {cn, Select, SelectItem} from "@heroui/react";
import {templates} from "../templates";
import {Template, TemplateType} from "../types";
import Swatch from "./configuration/swatch";
import {MirrorLeft} from "@/components/icons";
interface SelectTemplateProps {
name: TemplateType | null;
onChange: (template: Template) => void;
currentTheme: string | undefined;
}
export function SelectTemplate({name, onChange, currentTheme}: SelectTemplateProps) {
function handleChange(e: React.ChangeEvent<HTMLSelectElement>) {
const value = e.target.value as TemplateType;
const template = templates.find((template) => template.name === value);
if (template) {
onChange(template);
}
}
return (
<>
<div className="text-[#71717A] dark:text-[#A1A1AA] text-md leading-7 font-medium flex gap-1.5 items-center">
<MirrorLeft className="w-4 h-4 fill-current" />
<div>Prebuilt Themes</div>
</div>
<Select
className="my-3"
classNames={{
trigger: "bg-default-200",
popoverContent: "bg-white dark:bg-[#18181B]",
}}
placeholder="Select a theme"
selectedKeys={name === null ? [] : [name]}
onChange={handleChange}
>
{templates.map((template, index) => (
<SelectItem
key={template.name}
className={cn(
"data-[hover=true]:transition-none dark:data-[hover=true]:bg-[#26262A] dark:text-white dark:data-[hover=true]:text-white dark:data-[selectable=true]:focus:bg-[#26262A] dark:data-[selectable=true]:focus:text-white",
"data-[hover=true]:bg-white text-black data-[hover=true]:text-black data-[selectable=true]:focus:bg-[#F4F4F5] data-[selectable=true]:focus:text-black",
)}
startContent={
<div className="border border-black/5 dark:border-white/5">
<Swatch
colors={
currentTheme === "dark"
? {
background: template.value.dark.layoutColor.background,
...template.value.dark.baseColor,
}
: {
background: template.value.light.layoutColor.background,
...template.value.light.baseColor,
}
}
/>
</div>
}
// @ts-ignore
value={index}
>
{template.label}
</SelectItem>
))}
</Select>
</>
);
}

View File

@ -0,0 +1,56 @@
import {cn, Divider} from "@heroui/react";
import Link from "next/link";
import {Inter, Roboto, Outfit, Lora} from "next/font/google";
import {useThemeBuilder} from "../provider";
import {FontName, TemplateType} from "../types";
interface ShowcaseComponentProps {
children: React.ReactElement | React.ReactElement[];
id?: string;
name: string;
}
const inter = Inter({subsets: ["latin"], weight: ["400", "700"]});
const roboto = Roboto({subsets: ["latin"], weight: ["400", "700"]});
const outfit = Outfit({subsets: ["latin"], weight: ["400", "700"]});
const lora = Lora({subsets: ["latin"], weight: ["400", "700"]});
const FONT_CONFIGS: Record<FontName, {className: string}> = {
Inter: {className: inter.className},
Roboto: {className: roboto.className},
Outfit: {className: outfit.className},
Lora: {className: lora.className},
};
const getFontClass = (templateTheme: TemplateType) => {
if (templateTheme === "elegant") {
return "font-mono";
}
return FONT_CONFIGS["Inter"]?.className || "";
};
export function ShowcaseComponent({children, id, name}: ShowcaseComponentProps) {
const {font, templateTheme} = useThemeBuilder();
const fontClass = font ? FONT_CONFIGS[font]?.className || "" : getFontClass(templateTheme);
return (
<div
className={cn("bg-background text-foreground py-6 p-4 rounded-lg group", fontClass)}
id={id}
>
<div className="flex items-center gap-x-4">
<span className="text-xl font-medium text-black dark:text-white">{name}</span>
<Link
className="text-sm text-blue-400 hover:text-blue-500 dark:text-blue-500 hover:dark:text-blue-600 opacity-0 group-hover:opacity-100 transition-[opacity,color] duration-100 group-hover:flex items-center px-8 py-2"
href={`/docs/components/${name.toLowerCase()}`}
>
View in docs
</Link>
</div>
<Divider className="mt-4 mb-6" />
<div className="flex flex-wrap gap-6 mt-8">{children}</div>
</div>
);
}

View File

@ -0,0 +1,102 @@
import {cloneElement} from "react";
import {AvatarProps, Avatar as HeroUIAvatar} from "@heroui/react";
import {clsx} from "@heroui/shared-utils";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {Border, HeroUIScaling} from "../../types";
import {getBorderWidth} from "../../utils/shared";
type Color = AvatarProps["color"];
type Radius = AvatarProps["radius"];
const SectionBase = ({
color,
radius,
isDisabled,
className,
}: {
color?: Color;
radius?: Radius;
isDisabled?: boolean;
className?: string;
}) => {
return (
<HeroUIAvatar
key={color}
isBordered
className={className}
color={color}
isDisabled={isDisabled}
radius={radius}
src="https://i.pravatar.cc/150?u=a04258114e29026708c"
>
{color}
</HeroUIAvatar>
);
};
const Section = ({
color,
radius,
scaling,
borderWidthValue,
}: {
color: Color;
radius: Radius;
scaling: HeroUIScaling;
borderWidthValue: Border;
}) => {
let className = "h-10 w-10";
const border = getBorderWidth(borderWidthValue);
const borderClassName = border <= 2 ? `ring-${border} ring-offset-2` : `ring ring-offset-2`;
switch (scaling) {
case 90: {
className = clsx("h-6 w-6", borderClassName);
break;
}
case 95: {
className = clsx("h-8 w-8", borderClassName);
break;
}
case 100: {
className = clsx("h-10 w-10", borderClassName);
break;
}
case 105: {
className = clsx("h-12 w-12", borderClassName);
break;
}
case 110: {
className = clsx("h-14 w-14", borderClassName);
break;
}
}
return (
<div key={color} className="flex flex-col gap-y-4">
{cloneElement(<SectionBase />, {color, radius, className, isDisabled: false})}
{cloneElement(<SectionBase />, {color, radius, className, isDisabled: true})}
</div>
);
};
export const Avatar = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {radiusValue, scaling, borderWidthValue} = useThemeBuilder();
return (
<ShowcaseComponent name="Avatar">
{colors.map((color, idx) => (
<Section
key={idx}
borderWidthValue={borderWidthValue}
color={color}
radius={radiusValue}
scaling={scaling}
/>
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,125 @@
import {
BreadcrumbsProps,
Breadcrumbs as HeroUIBreadcrumbs,
BreadcrumbItem as HeroUIBreadcrumbsItem,
} from "@heroui/react";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {Border, Radius} from "../../types";
type Color = BreadcrumbsProps["color"];
const SectionBase = ({
color,
variant,
isDisabled,
classNames,
radius,
}: {
color?: BreadcrumbsProps["color"];
variant?: BreadcrumbsProps["variant"];
isDisabled?: boolean;
classNames?: any;
radius?: Radius;
}) => {
const items = ["Home", "Music", "Artist", "Album", "Song"];
return (
<HeroUIBreadcrumbs
classNames={classNames}
color={color}
isDisabled={isDisabled}
radius={radius}
variant={variant}
>
{items.map((item, index) => (
<HeroUIBreadcrumbsItem key={index}>{item}</HeroUIBreadcrumbsItem>
))}
</HeroUIBreadcrumbs>
);
};
const Section = ({
color,
scaling,
borderWidthValue,
radiusValue,
}: {
color: Color;
scaling: number;
borderWidthValue: Border;
radiusValue: Radius;
}) => {
const variants = ["bordered", "light", "solid", "solid"];
const disabled = [false, false, false, true];
let classNames = {base: "text-small"};
let borderClass = "border-medium";
if (borderWidthValue === "thin") {
borderClass = "border-small";
} else if (borderWidthValue === "thick") {
borderClass = "border-large";
}
switch (scaling) {
case 90: {
classNames = {base: "text-[0.7rem]"};
break;
}
case 95: {
classNames = {base: "text-tiny"};
break;
}
case 100: {
classNames = {base: "text-small p-0.5"};
break;
}
case 105: {
classNames = {base: "text-medium p-1"};
break;
}
case 110: {
classNames = {base: "text-large p-1.5"};
break;
}
}
return (
<div className="flex flex-col gap-y-4">
{variants.map((variant, idx) => (
<SectionBase
key={idx}
classNames={{
...classNames,
list: variant === "bordered" && borderClass,
}}
color={color}
isDisabled={disabled[idx]}
radius={radiusValue}
variant={variant as BreadcrumbsProps["variant"]}
/>
))}
</div>
);
};
export const BreadCrumbs = () => {
const colors: Color[] = ["foreground", "primary", "secondary", "success", "warning", "danger"];
const {scaling, borderWidthValue, radiusValue} = useThemeBuilder();
return (
<ShowcaseComponent name="BreadCrumbs">
{colors.map((color, idx) => (
<Section
key={idx}
borderWidthValue={borderWidthValue}
color={color}
radiusValue={radiusValue}
scaling={scaling}
/>
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,114 @@
import {cloneElement} from "react";
import {ButtonProps, Button as HeroUIButton} from "@heroui/react";
import {clsx} from "@heroui/shared-utils";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {Border} from "../../types";
type Color = ButtonProps["color"];
type Radius = ButtonProps["radius"];
type Variant = ButtonProps["variant"];
const SectionBase = ({
color,
radius,
isDisabled,
variant,
className,
}: {
color?: Color;
radius?: Radius;
isDisabled?: boolean;
variant?: Variant;
className?: string;
}) => {
return (
<HeroUIButton
key={color}
className={className}
color={color}
isDisabled={isDisabled}
radius={radius}
variant={variant}
>
{color}
</HeroUIButton>
);
};
const Section = ({
color,
radius,
scaling,
borderWidthValue,
}: {
color: Color;
radius: Radius;
scaling: number;
borderWidthValue: Border;
}) => {
const variants = ["solid", "shadow", "bordered", "flat", "faded", "ghost"];
let borderClass = "border-medium";
if (borderWidthValue === "thin") {
borderClass = "border-small";
} else if (borderWidthValue === "thick") {
borderClass = "border-large";
}
const scalingClasses = {
90: "px-4 min-w-12 h-8 text-[0.7rem]",
95: "px-5 min-w-14 h-9 text-tiny",
100: "px-6 min-w-16 h-10 text-small",
105: "px-7 min-w-18 h-11 text-medium",
110: "px-8 min-w-20 h-12 text-medium",
};
const className = scalingClasses[scaling] || scalingClasses[100];
return (
<div key={color} className="flex flex-col gap-y-4">
{variants.map((variant) => (
<SectionBase
key={variant}
className={clsx(
className,
variant === "bordered" || variant === "faded" || variant === "ghost" ? borderClass : "",
)}
color={color}
isDisabled={false}
radius={radius}
//@ts-ignore
variant={variant}
/>
))}
{cloneElement(<SectionBase />, {
color,
radius,
className,
isDisabled: true,
variant: "solid",
})}
</div>
);
};
export const Button = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {radiusValue, scaling, borderWidthValue} = useThemeBuilder();
return (
<ShowcaseComponent name="Button">
{colors.map((color, idx) => (
<Section
key={idx}
borderWidthValue={borderWidthValue}
color={color}
radius={radiusValue}
scaling={scaling}
/>
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,113 @@
import {cloneElement} from "react";
import {CheckboxProps, Checkbox as HeroUICheckbox} from "@heroui/react";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {HeroUIScaling} from "../../types";
type Color = CheckboxProps["color"];
type Radius = CheckboxProps["radius"];
const SectionBase = ({
color,
isDisabled,
radius,
classNames,
}: {
color?: Color;
isDisabled?: boolean;
radius?: Radius;
classNames?: any;
}) => {
return (
<HeroUICheckbox
key={radius}
defaultSelected
classNames={classNames}
color={color}
isDisabled={isDisabled}
radius={radius}
>
{color}
</HeroUICheckbox>
);
};
const Section = ({
color,
radius,
scaling,
}: {
color: Color;
radius: Radius;
scaling: HeroUIScaling;
}) => {
let classNames = {
wrapper: "h-6 w-6",
icon: "w-5 h-4",
label: "text-medium",
};
switch (scaling) {
case 90: {
classNames = {
wrapper: "h-5 w-5",
icon: "w-4 h-3",
label: "text-small",
};
break;
}
case 95: {
classNames = {
wrapper: "h-5 w-5",
icon: "w-4 h-3",
label: "text-medium",
};
break;
}
case 100: {
classNames = {
wrapper: "h-6 w-6",
icon: "w-4 h-3",
label: "text-medium",
};
break;
}
case 105: {
classNames = {
wrapper: "h-6 w-6",
icon: "w-4 h-3",
label: "text-large",
};
break;
}
case 110: {
classNames = {
wrapper: "h-7 w-7",
icon: "w-5 h-4",
label: "text-large",
};
break;
}
}
return (
<div key={color} className="flex flex-col gap-y-4">
{cloneElement(<SectionBase />, {color, radius, classNames, isDisabled: false})}
{cloneElement(<SectionBase />, {color, radius, classNames, isDisabled: true})}
</div>
);
};
export const Checkbox = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {radiusValue, scaling} = useThemeBuilder();
return (
<ShowcaseComponent name="Checkbox">
{colors.map((color, idx) => (
<Section key={idx} color={color} radius={radiusValue} scaling={scaling} />
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,128 @@
import {cloneElement} from "react";
import {ChipProps, Chip as HeroUIChip} from "@heroui/react";
import {clsx} from "@heroui/shared-utils";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {Border, HeroUIScaling} from "../../types";
type Color = ChipProps["color"];
type Radius = ChipProps["radius"];
type Variant = ChipProps["variant"];
const SectionBase = ({
color,
isDisabled,
radius,
variant,
className,
}: {
color?: Color;
isDisabled?: boolean;
radius?: Radius;
variant?: Variant;
className?: string;
}) => {
return (
<HeroUIChip
key={radius}
className={className}
color={color}
isDisabled={isDisabled}
radius={radius}
variant={variant}
>
{color}
</HeroUIChip>
);
};
const Section = ({
color,
radius,
scaling,
borderWidthValue,
}: {
color: Color;
radius: Radius;
scaling: HeroUIScaling;
borderWidthValue: Border;
}) => {
const variants = ["solid", "bordered", "light", "flat", "faded", "shadow"];
let borderClass = "border-medium";
if (borderWidthValue === "thin") {
borderClass = "border-small";
} else if (borderWidthValue === "thick") {
borderClass = "border-large";
}
let className = "px-1 h-6 text-tiny";
switch (scaling) {
case 90: {
className = "h-5 text-tiny";
break;
}
case 95: {
className = "h-6 text-tiny";
break;
}
case 100: {
className = "px-1 h-7 text-tiny";
break;
}
case 105: {
className = "px-2 h-8 text-medium";
break;
}
case 110: {
className = "px-3 h-8 text-medium";
break;
}
}
return (
<div key={color} className="flex flex-col gap-y-4">
{variants.map((variant, idx) =>
cloneElement(<SectionBase key={idx} />, {
color,
className: clsx(
className,
variant === "bordered" || variant === "faded" ? borderClass : "",
),
variant,
isDisabled: false,
radius,
}),
)}
{cloneElement(<SectionBase />, {
color,
className,
variant: "solid",
isDisabled: true,
radius,
})}
</div>
);
};
export const Chip = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {radiusValue, scaling, borderWidthValue} = useThemeBuilder();
return (
<ShowcaseComponent name="Chip">
{colors.map((color, idx) => (
<Section
key={idx}
borderWidthValue={borderWidthValue}
color={color}
radius={radiusValue}
scaling={scaling}
/>
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,78 @@
import {CodeProps, Code as HeroUICode} from "@heroui/react";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {HeroUIScaling} from "../../types";
type Color = CodeProps["color"];
type Radius = CodeProps["radius"];
const SectionBase = ({
color,
radius,
className,
}: {
color?: Color;
radius?: Radius;
className?: string;
}) => {
return (
<HeroUICode key={radius} className={className} color={color} radius={radius}>
npm install @heroui/react
</HeroUICode>
);
};
const Section = ({
color,
radius,
scaling,
}: {
color: Color;
radius: Radius;
scaling: HeroUIScaling;
}) => {
let className = "p-0 px-3 text-tiny";
switch (scaling) {
case 90: {
className = "p-2 px-3 text-tiny";
break;
}
case 95: {
className = "p-2 px-4 text-tiny";
break;
}
case 100: {
className = "p-2 px-4 text-medium";
break;
}
case 105: {
className = "p-3 px-5 text-medium";
break;
}
case 110: {
className = "p-2 px-8 text-medium";
break;
}
}
return (
<div key={color} className="flex flex-col gap-y-4">
<SectionBase className={className} color={color} radius={radius} />
</div>
);
};
export const Code = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {radiusValue, scaling} = useThemeBuilder();
return (
<ShowcaseComponent name="Code">
{colors.map((color, idx) => (
<Section key={idx} color={color} radius={radiusValue} scaling={scaling} />
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,30 @@
import {showcaseId} from "../../constants";
import {Avatar} from "./avatar";
import {BreadCrumbs} from "./breadcrumbs";
import {Button} from "./button";
import {Checkbox} from "./checkbox";
import {Chip} from "./chip";
import {Code} from "./code";
import {InputComponent} from "./input";
import {PopoverComponent} from "./popover";
import {SwitchComponent} from "./switch";
import {TabsComponent} from "./tabs";
export function Showcase() {
return (
<div className="grid grid-cols-1 gap-4 w-full min-w-6xl pb-20" id={showcaseId}>
<div className="text-3xl text-default-800 font-bold">Theme Generator</div>
<Avatar />
<BreadCrumbs />
<Button />
<Checkbox />
<Chip />
<Code />
<InputComponent />
<PopoverComponent />
<SwitchComponent />
<TabsComponent />
</div>
);
}

View File

@ -0,0 +1,125 @@
import {InputProps, Input} from "@heroui/react";
import {clsx} from "@heroui/shared-utils";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {Border, HeroUIScaling} from "../../types";
type Color = InputProps["color"];
type Radius = InputProps["radius"];
type Variant = InputProps["variant"];
const SectionBase = ({
color,
isDisabled,
radius,
variant,
classNames,
}: {
color?: Color;
isDisabled?: boolean;
radius?: Radius;
variant?: Variant;
classNames?: any;
}) => {
return (
<Input
classNames={classNames}
color={color}
isDisabled={isDisabled}
label="Input"
radius={radius}
variant={variant}
/>
);
};
const Section = ({
color,
radius,
scaling,
borderWidthValue,
}: {
color: Color;
radius: Radius;
scaling: HeroUIScaling;
borderWidthValue: Border;
}) => {
const variants = ["flat", "bordered", "faded", "underlined"];
let classNames = {base: "h-10 w-[340px]", label: "text-small"};
let borderClass = "border-medium";
if (borderWidthValue === "thin") {
borderClass = "border-small";
} else if (borderWidthValue === "thick") {
borderClass = "border-large";
}
switch (scaling) {
case 90: {
classNames = {base: "h-8 min-w-0 w-[190px]", label: "text-tiny"};
break;
}
case 95: {
classNames = {base: "h-8 min-w-0 w-[210px]", label: "text-tiny"};
break;
}
case 100: {
classNames = {base: "h-10 min-w-0 w-[230px]", label: "text-small"};
break;
}
case 105: {
classNames = {base: "h-12 min-w-0 w-[250px]", label: "text-medium"};
break;
}
case 110: {
classNames = {base: "h-12 min-w-0 w-[270px]", label: "text-medium"};
break;
}
}
return (
<div key={color} className="flex flex-col gap-y-4 w-auto h-auto">
{variants.map((variant, idx) => (
<SectionBase
key={idx}
classNames={{
...classNames,
inputWrapper: clsx(clsx(variant === "bordered" && borderClass)),
}}
color={color}
isDisabled={false}
radius={radius}
variant={variant as Variant}
/>
))}
<SectionBase
classNames={classNames}
color={color}
isDisabled={true}
radius={radius}
variant="flat"
/>
</div>
);
};
export const InputComponent = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {radiusValue, scaling, borderWidthValue} = useThemeBuilder();
return (
<ShowcaseComponent name="Input">
{colors.map((color, idx) => (
<Section
key={idx}
borderWidthValue={borderWidthValue}
color={color}
radius={radiusValue}
scaling={scaling}
/>
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,86 @@
import {PopoverProps, Popover, PopoverTrigger, PopoverContent, Button} from "@heroui/react";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {HeroUIScaling} from "../../types";
type Color = PopoverProps["color"];
type Radius = PopoverProps["radius"];
const SectionBase = ({
color,
radius,
className,
}: {
color?: Color;
radius?: Radius;
className?: string;
}) => {
return (
<Popover color={color === "foreground" ? "default" : color} placement="right" radius={radius}>
<PopoverTrigger>
<Button color={color === "foreground" ? "default" : color}>{color}</Button>
</PopoverTrigger>
<PopoverContent>
<div className={className}>
<div className="font-bold">Popover Content</div>
<div>This is the popover content</div>
</div>
</PopoverContent>
</Popover>
);
};
const Section = ({
color,
radius,
scaling,
}: {
color: Color;
radius: Radius;
scaling: HeroUIScaling;
}) => {
let className = "text-small px-1";
switch (scaling) {
case 90: {
className = "px-1 py-2 text-tiny";
break;
}
case 95: {
className = "text-tiny px-2 py-3";
break;
}
case 100: {
className = "text-small px-2 py-3";
break;
}
case 105: {
className = "text-small px-3 py-3";
break;
}
case 110: {
className = "text-medium px-3 py-3";
break;
}
}
return (
<div key={color} className="flex flex-col gap-y-4">
<SectionBase className={className} color={color} radius={radius} />
</div>
);
};
export const PopoverComponent = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {radiusValue, scaling} = useThemeBuilder();
return (
<ShowcaseComponent name="Popover">
{colors.map((color, idx) => (
<Section key={idx} color={color} radius={radiusValue} scaling={scaling} />
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,96 @@
import {cloneElement} from "react";
import {SwitchProps, Switch} from "@heroui/react";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {HeroUIScaling} from "../../types";
type Color = SwitchProps["color"];
const SectionBase = ({
color,
isDisabled,
classNames,
}: {
color?: Color;
isDisabled?: boolean;
classNames?: {
wrapper?: string;
thumb?: string;
};
}) => {
return (
<Switch
defaultSelected
aria-label="Automatic updates"
classNames={classNames}
color={color}
isDisabled={isDisabled}
/>
);
};
const Section = ({color, scaling}: {color: Color; scaling: HeroUIScaling}) => {
let classNames = {
wrapper: "w-12 h-7",
thumb: "w-5 h-5",
};
switch (scaling) {
case 90: {
classNames = {
wrapper: "w-8 h-4",
thumb: "w-2 h-2 group-data-[selected=true]:ms-4",
};
break;
}
case 95: {
classNames = {
wrapper: "w-10 h-6",
thumb: "w-3 h-3",
};
break;
}
case 100: {
classNames = {
wrapper: "w-12 h-7",
thumb: "w-4 h-4 group-data-[selected=true]:ms-6",
};
break;
}
case 105: {
classNames = {
wrapper: "w-14 h-8",
thumb: "w-5 h-5 group-data-[selected=true]:ms-7",
};
break;
}
case 110: {
classNames = {
wrapper: "w-16 h-9",
thumb: "w-6 h-6 group-data-[selected=true]:ms-8",
};
break;
}
}
return (
<div key={color} className="flex flex-col gap-y-4">
{cloneElement(<SectionBase />, {color, classNames, isDisabled: false})}
{cloneElement(<SectionBase />, {color, classNames, isDisabled: true})}
</div>
);
};
export const SwitchComponent = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {scaling} = useThemeBuilder();
return (
<ShowcaseComponent name="Switch">
{colors.map((color, idx) => (
<Section key={idx} color={color} scaling={scaling} />
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,130 @@
import {cloneElement} from "react";
import {TabsProps, Tabs, Tab} from "@heroui/react";
import {ShowcaseComponent} from "../showcase-component";
import {useThemeBuilder} from "../../provider";
import {Border, HeroUIScaling} from "../../types";
type Color = TabsProps["color"];
type Radius = TabsProps["radius"];
type Variant = TabsProps["variant"];
const SectionBase = ({
color,
isDisabled,
radius,
variant,
classNames,
}: {
color?: Color;
isDisabled?: boolean;
radius?: Radius;
variant?: Variant;
classNames?: any;
}) => {
return (
<Tabs
key={color}
aria-label="Tabs colors"
classNames={classNames}
color={color}
isDisabled={isDisabled}
radius={radius}
variant={variant}
>
<Tab key="photos" title="Photos" />
<Tab key="music" title="Music" />
<Tab key="videos" title="Videos" />
</Tabs>
);
};
const Section = ({
color,
radius,
scaling,
borderWidthValue,
}: {
color: Color;
radius: Radius;
scaling: HeroUIScaling;
borderWidthValue: Border;
}) => {
const variants = ["solid", "bordered", "light", "underlined"];
let borderClass = "border-medium";
if (borderWidthValue === "thin") {
borderClass = "border-small";
} else if (borderWidthValue === "thick") {
borderClass = "border-large";
}
let classNames = {tab: "text-tiny px-2 h-6"};
switch (scaling) {
case 90: {
classNames = {tab: "text-tiny px-2 h-6"};
break;
}
case 95: {
classNames = {tab: "text-tiny px-2 h-7"};
break;
}
case 100: {
classNames = {tab: "text-tiny px-3 h-7"};
break;
}
case 105: {
classNames = {tab: "text-medium px-3 h-8"};
break;
}
case 110: {
classNames = {tab: "text-medium px-4 h-9"};
break;
}
}
return (
<div key={color} className="flex flex-col gap-y-4">
{variants.map((variant, idx) =>
cloneElement(<SectionBase key={idx} />, {
color,
variant,
classNames: {
...classNames,
tabList: variant === "bordered" ? borderClass : "",
},
isDisabled: false,
radius,
}),
)}
{cloneElement(<SectionBase />, {
color,
classNames,
variant: "solid",
isDisabled: true,
radius,
})}
</div>
);
};
export const TabsComponent = () => {
const colors: Color[] = ["default", "primary", "secondary", "success", "warning", "danger"];
const {radiusValue, scaling, borderWidthValue} = useThemeBuilder();
return (
<ShowcaseComponent name="Tabs">
{colors.map((color, idx) => (
<Section
key={idx}
borderWidthValue={borderWidthValue}
color={color}
radius={radiusValue}
scaling={scaling}
/>
))}
</ShowcaseComponent>
);
};

View File

@ -0,0 +1,111 @@
import {colors} from "@heroui/theme";
import {ConfigColors, Config, ConfigLayout} from "./types";
// Colors
export const defaultDarkColorWeight = 20;
export const defaultLightColorWeight = 17.5;
export const colorWeight = 17.5;
// Regex
export const floatNumberPattern = /^\d+(\.\d*)?$/;
// Elements ids
export const colorsId = "th-colors";
export const defaultColorsId = "th-default-colors";
export const baseColorsId = "th-base-colors";
export const otherColorsId = "th-other-colors";
export const showcaseId = "th-showcase";
export const contentShowcaseId = "th-content-showcase";
export const contentColorsId = "th-content-colors";
// Local storage
export const configKey = "config";
export const syncThemesKey = "sync-themes";
// Theme configuration
export const initialLightTheme: ConfigColors = {
defaultColor: {
default: "#d4d4d8",
},
baseColor: {
primary: colors.blue[500],
secondary: colors.purple[500],
success: colors.green[500],
warning: colors.yellow[500],
danger: colors.red[500],
},
layoutColor: {
foreground: colors.black,
background: colors.white,
focus: colors.blue[500],
overlay: colors.black,
},
contentColor: {
content1: colors.white,
content2: colors.zinc[100],
content3: colors.zinc[200],
content4: colors.zinc[300],
},
};
export const initialDarkTheme: ConfigColors = {
defaultColor: {
default: colors.zinc[700],
},
baseColor: {
primary: colors.blue[500],
secondary: colors.purple[500],
success: colors.green[500],
warning: colors.yellow[500],
danger: colors.red[500],
},
layoutColor: {
foreground: colors.white,
background: colors.black,
focus: colors.blue[500],
overlay: colors.white,
},
contentColor: {
content1: colors.zinc[900],
content2: colors.zinc[800],
content3: colors.zinc[700],
content4: colors.zinc[600],
},
};
export const initialLayout: ConfigLayout = {
fontSize: {
tiny: "0.75",
small: "0.875",
medium: "1",
large: "1.125",
},
lineHeight: {
tiny: "1",
small: "1.25",
medium: "1.5",
large: "1.75",
},
radius: {
small: "0.5",
medium: "0.75",
large: "0.875",
},
borderWidth: {
small: "1",
medium: "2",
large: "3",
},
otherParams: {
disabledOpacity: "0.5",
dividerWeight: "1",
hoverOpacity: "0.9",
},
};
export const initialConfig: Config = {
light: initialLightTheme,
dark: initialDarkTheme,
layout: initialLayout,
};

View File

@ -0,0 +1,239 @@
import {readableColor} from "color2k";
import {colorsId, baseColorsId, showcaseId, otherColorsId, defaultColorsId} from "./constants";
import {ColorPickerType, Config, ConfigLayout, ThemeType, ThemeColor} from "./types";
import {generateThemeColor, hexToHsl} from "./utils/colors";
import {templates} from "./templates";
export function setCssColor(
colorType: ColorPickerType,
value: string,
heroUIValue: string,
theme: ThemeType,
) {
const baseColorEl = document.getElementById(colorsId);
const commonColorsEl = document.getElementById(baseColorsId);
const showcaseEl = document.getElementById(showcaseId);
const defaultColorEl = document.getElementById(defaultColorsId);
const appContainer = document.getElementById("app-container");
const configurationContainer = document.getElementById("configuration-container");
const htmlEl = document.documentElement;
const themeColor = generateThemeColor(value, colorType, theme);
const heroThemeColor = generateThemeColor(heroUIValue, colorType, theme);
if (!baseColorEl || !commonColorsEl || !showcaseEl || !defaultColorEl) {
// eslint-disable-next-line no-console
console.error("One or more required elements are missing from the DOM.");
return;
}
Object.keys(themeColor).forEach((key) => {
const value = hexToHsl(themeColor[key as keyof ThemeColor]);
const heroValue = hexToHsl(heroThemeColor[key as keyof ThemeColor]);
if (key === "DEFAULT") {
baseColorEl.style.setProperty(`--heroui-${colorType}`, value);
commonColorsEl.style.setProperty(`--heroui-${colorType}`, value);
showcaseEl.style.setProperty(`--heroui-${colorType}`, value);
defaultColorEl.style.setProperty(`--heroui-${colorType}`, value);
appContainer?.style.setProperty(`--heroui-${colorType}`, heroValue);
configurationContainer?.style.setProperty(`--heroui-${colorType}`, heroValue);
htmlEl.style.setProperty(`--heroui-${colorType}`, value);
} else {
baseColorEl.style.setProperty(`--heroui-${colorType}-${key}`, value);
commonColorsEl.style.setProperty(`--heroui-${colorType}-${key}`, value);
showcaseEl.style.setProperty(`--heroui-${colorType}-${key}`, value);
defaultColorEl.style.setProperty(`--heroui-${colorType}`, value);
appContainer?.style.setProperty(`--heroui-${colorType}-${key}`, heroValue);
configurationContainer?.style.setProperty(`--heroui-${colorType}-${key}`, heroValue);
htmlEl.style.setProperty(`--heroui-${colorType}-${key}`, value);
}
});
}
export function setCssBackground(value: string) {
const showcaseEl = document.getElementById(showcaseId);
const baseColor = document.getElementById(baseColorsId);
const hslValue = hexToHsl(value);
baseColor?.style.setProperty("--heroui-background", hslValue);
showcaseEl?.style.setProperty("--heroui-background", hslValue);
}
export function setCssFontSize(type: keyof ConfigLayout["fontSize"], value: string) {
const el = document.getElementById(showcaseId);
el?.style.setProperty(`--heroui-font-size-${type}`, `${value}rem`);
}
export function setCssLineHeight(type: keyof ConfigLayout["lineHeight"], value: string) {
const el = document.getElementById(showcaseId);
el?.style.setProperty(`--heroui-line-height-${type}`, `${value}rem`);
}
export function setCssRadius(type: keyof ConfigLayout["radius"], value: string) {
const el = document.getElementById(showcaseId);
el?.style.setProperty(`--heroui-radius-${type}`, `${value}rem`);
}
export function setCssBorderWidth(type: keyof ConfigLayout["borderWidth"], value: string) {
const el = document.getElementById(showcaseId);
el?.style.setProperty(`--heroui-border-width-${type}`, `${value}px`);
}
export function setCssContentColor(level: 1 | 2 | 3 | 4, value: string, heroValue: string) {
const showcaseEl = document.getElementById(showcaseId);
const baseColorEl = document.getElementById(baseColorsId);
const configurationContainer = document.getElementById("configuration-container");
const htmlEl = document.documentElement;
const hslValue = hexToHsl(value);
const heroHslValue = hexToHsl(heroValue);
showcaseEl?.style.setProperty(`--heroui-content${level}`, hslValue);
showcaseEl?.style.setProperty(
`--heroui-content${level}-foreground`,
hexToHsl(readableColor(value)),
);
baseColorEl?.style.setProperty(`--heroui-content${level}`, hslValue);
baseColorEl?.style.setProperty(
`--heroui-content${level}-foreground`,
hexToHsl(readableColor(value)),
);
htmlEl.style.setProperty(`--heroui-content${level}`, hslValue);
htmlEl.style.setProperty(`--heroui-content${level}-foreground`, hexToHsl(readableColor(value)));
configurationContainer?.style.setProperty(`--heroui-content${level}`, heroHslValue);
configurationContainer?.style.setProperty(
`--heroui-content${level}-foreground`,
hexToHsl(readableColor(heroValue)),
);
}
export function setCssOtherColor(
type: "background" | "foreground" | "focus" | "overlay",
value: string,
) {
const showcaseEl = document.getElementById(showcaseId);
const otherColors = document.getElementById(otherColorsId);
const hslValue = hexToHsl(value);
otherColors?.style.setProperty(`--heroui-${type}`, hslValue);
showcaseEl?.style.setProperty(`--heroui-${type}`, hslValue);
}
export function setOtherCssParams(type: keyof ConfigLayout["otherParams"], value: string) {
const el = document.getElementById(showcaseId);
if (!el) return;
switch (type) {
case "disabledOpacity":
el.style.setProperty("--heroui-disabled-opacity", value);
break;
case "dividerWeight":
el.style.setProperty("--heroui-divider-weight", `${value}px`);
break;
case "hoverOpacity":
el.style.setProperty("--heroui-hover-opacity", value);
break;
}
}
export function setAllCssVars(config: Config, theme: ThemeType) {
if (!config[theme] || !config[theme].baseColor || !config[theme].layoutColor || !config.layout) {
// eslint-disable-next-line no-console
console.error("Invalid configuration or theme provided.");
return;
}
setCssColor(
"default",
config[theme].defaultColor.default,
templates[0].value[theme].defaultColor.default,
theme,
);
setCssColor(
"primary",
config[theme].baseColor.primary,
templates[0].value[theme].baseColor.primary,
theme,
);
setCssColor(
"secondary",
config[theme].baseColor.secondary,
templates[0].value[theme].baseColor.secondary,
theme,
);
setCssColor(
"success",
config[theme].baseColor.success,
templates[0].value[theme].baseColor.success,
theme,
);
setCssColor(
"warning",
config[theme].baseColor.warning,
templates[0].value[theme].baseColor.warning,
theme,
);
setCssColor(
"danger",
config[theme].baseColor.danger,
templates[0].value[theme].baseColor.danger,
theme,
);
setCssColor(
"foreground",
config[theme].layoutColor.foreground,
templates[0].value[theme].layoutColor.foreground,
theme,
);
setCssContentColor(
1,
config[theme].contentColor.content1,
templates[0].value[theme].contentColor.content1,
);
setCssContentColor(
2,
config[theme].contentColor.content2,
templates[0].value[theme].contentColor.content2,
);
setCssContentColor(
3,
config[theme].contentColor.content3,
templates[0].value[theme].contentColor.content3,
);
setCssContentColor(
4,
config[theme].contentColor.content4,
templates[0].value[theme].contentColor.content4,
);
setCssBackground(config[theme].layoutColor.background);
setCssFontSize("tiny", config.layout.fontSize.tiny);
setCssFontSize("small", config.layout.fontSize.small);
setCssFontSize("medium", config.layout.fontSize.medium);
setCssFontSize("large", config.layout.fontSize.large);
setCssLineHeight("tiny", config.layout.lineHeight.tiny);
setCssLineHeight("small", config.layout.lineHeight.small);
setCssLineHeight("medium", config.layout.lineHeight.medium);
setCssLineHeight("large", config.layout.lineHeight.large);
setCssRadius("small", config.layout.radius.small);
setCssRadius("medium", config.layout.radius.medium);
setCssRadius("large", config.layout.radius.large);
setCssBorderWidth("small", config.layout.borderWidth.small);
setCssBorderWidth("medium", config.layout.borderWidth.medium);
setCssBorderWidth("large", config.layout.borderWidth.large);
setCssOtherColor("focus", config[theme].layoutColor.focus);
setCssOtherColor("overlay", config[theme].layoutColor.overlay);
setOtherCssParams("disabledOpacity", config.layout.otherParams.disabledOpacity);
setOtherCssParams("dividerWeight", config.layout.otherParams.dividerWeight);
setOtherCssParams("hoverOpacity", config.layout.otherParams.hoverOpacity);
}

View File

@ -0,0 +1,22 @@
"use client";
import {useEffect, useState} from "react";
import Configuration from "./components/configuration";
import {Showcase} from "./components/showcase";
import ThemeBuilderProvider from "./provider";
export function ThemeBuilder() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return isClient ? (
<ThemeBuilderProvider>
<Showcase />
<Configuration />
</ThemeBuilderProvider>
) : null;
}

View File

@ -0,0 +1,346 @@
import {useState, createContext, useContext} from "react";
import {useLocalStorage} from "usehooks-ts";
import {configKey, initialConfig} from "./constants";
import {
ConfigColors,
Config,
ConfigLayout,
ThemeType,
Radius,
TemplateType,
FontType,
HeroUIScaling,
Border,
} from "./types";
export interface ThemeBuilderContextProps {
config: Config;
radiusValue: Radius;
borderWidthValue: Border;
templateTheme: TemplateType;
font: FontType | undefined;
scaling: HeroUIScaling;
resetConfig: (theme: ThemeType, sync: boolean) => Config;
setLayoutColor: (
newConfig: Partial<ConfigColors["layoutColor"]>,
theme: ThemeType,
sync: boolean,
) => void;
setContentColor: (newConfig: Partial<ConfigColors["contentColor"]>, theme: ThemeType) => void;
setBorderWidth: (newConfig: Partial<ConfigLayout["borderWidth"]>) => void;
setBaseColor: (
newConfig: Partial<ConfigColors["baseColor"]>,
theme: ThemeType,
sync: boolean,
) => void;
setDefaultColor: (
newConfig: Partial<ConfigColors["defaultColor"]>,
theme: ThemeType,
sync: boolean,
) => void;
setConfiguration: (newConfig: Config, theme: ThemeType, sync: boolean) => void;
setLineHeight: (newConfig: Partial<ConfigLayout["lineHeight"]>) => void;
setFontSize: (newConfig: Partial<ConfigLayout["fontSize"]>) => void;
setOtherParams: (newConfig: Partial<ConfigLayout["otherParams"]>) => void;
setRadius: (newConfig: Partial<ConfigLayout["radius"]>) => void;
setRadiusValue: (radius: Radius) => void;
setBorderWidthValue: (borderWidth: Border) => void;
setTemplateTheme: (theme: TemplateType) => void;
setFont: (font: FontType) => void;
setScaling: (scale: HeroUIScaling) => void;
}
const ThemeBuilderContext = createContext<ThemeBuilderContextProps>({
config: initialConfig,
radiusValue: "md",
borderWidthValue: "thick",
templateTheme: "heroui",
font: undefined,
scaling: 100,
resetConfig: () => initialConfig,
setLayoutColor: () => {},
setBorderWidth: () => {},
setBaseColor: () => {},
setConfiguration: () => {},
setLineHeight: () => {},
setFontSize: () => {},
setOtherParams: () => {},
setRadius: () => {},
setDefaultColor: () => {},
setContentColor: () => {},
setRadiusValue: () => {},
setBorderWidthValue: () => {},
setTemplateTheme: () => {},
setFont: () => {},
setScaling: () => {},
});
interface ThemeBuilderProviderProps {
children: React.ReactNode;
}
export default function ThemeBuilderProvider({children}: ThemeBuilderProviderProps) {
const [lsConfig] = useLocalStorage<Config>(configKey, initialConfig);
const [config, setConfig] = useState<Config>(lsConfig);
const [radiusValue, setRadiusValue] = useState<Radius>("sm");
const [borderWidthValue, setBorderWidthValue] = useState<Border>("thin");
const [templateTheme, setTemplateTheme] = useState<TemplateType>("heroui");
const [font, setFont] = useState<FontType | undefined>(undefined);
const [scaling, setScaling] = useState<HeroUIScaling>(100);
const setConfiguration = (newConfig: Config, theme: ThemeType, sync: boolean) => {
setConfig((prev) =>
sync
? newConfig
: {
...prev,
[theme]: newConfig[theme],
},
);
};
const resetConfig = (theme: ThemeType, sync: boolean) => {
let newConfig = initialConfig;
setConfig((prev) => {
newConfig = sync
? newConfig
: {
...prev,
[theme]: newConfig[theme],
layout: newConfig.layout,
};
return newConfig;
});
return newConfig;
};
const setBaseColor = (
newConfig: Partial<ConfigColors["baseColor"]>,
theme: ThemeType,
sync: boolean,
) => {
setConfig((prev) =>
sync
? {
...prev,
light: {
...prev.light,
baseColor: {
...prev.light.baseColor,
...newConfig,
},
},
dark: {
...prev.dark,
baseColor: {
...prev.dark.baseColor,
...newConfig,
},
},
}
: {
...prev,
[theme]: {
...prev[theme],
baseColor: {
...prev[theme].baseColor,
...newConfig,
},
},
},
);
};
const setDefaultColor = (
newConfig: Partial<ConfigColors["defaultColor"]>,
theme: ThemeType,
sync: boolean,
) => {
setConfig((prev) =>
sync
? {
...prev,
light: {
...prev.light,
defaultColor: {
...prev.light.defaultColor,
...newConfig,
},
},
dark: {
...prev.dark,
defaultColor: {
...prev.dark.defaultColor,
...newConfig,
},
},
}
: {
...prev,
[theme]: {
...prev[theme],
defaultColor: {
...prev[theme].defaultColor,
...newConfig,
},
},
},
);
};
const setContentColor = (newConfig: Partial<ConfigColors["contentColor"]>, theme: ThemeType) => {
setConfig((prev) => ({
...prev,
[theme]: {
...prev[theme],
contentColor: {
...prev[theme].contentColor,
...newConfig,
},
},
}));
};
const setLayoutColor = (
newConfig: Partial<ConfigColors["layoutColor"]>,
theme: ThemeType,
sync: boolean,
) => {
setConfig((prev) =>
sync
? {
...prev,
light: {
...prev.light,
otherColor: {
...prev.light.layoutColor,
...newConfig,
},
},
dark: {
...prev.dark,
otherColor: {
...prev.dark.layoutColor,
...newConfig,
},
},
}
: {
...prev,
[theme]: {
...prev[theme],
otherColor: {
...prev[theme].layoutColor,
...newConfig,
},
},
},
);
};
const setBorderWidth = (newBorderWidths: Partial<ConfigLayout["borderWidth"]>) =>
setConfig((prev) => ({
...prev,
layout: {
...prev.layout,
borderWidth: {
...prev.layout.borderWidth,
...newBorderWidths,
},
},
}));
const setLineHeight = (newLineHeights: Partial<ConfigLayout["lineHeight"]>) =>
setConfig((prev) => ({
...prev,
layout: {
...prev.layout,
lineHeight: {
...prev.layout.lineHeight,
...newLineHeights,
},
},
}));
const setFontSize = (newFontSizes: Partial<ConfigLayout["fontSize"]>) =>
setConfig((prev) => ({
...prev,
layout: {
...prev.layout,
fontSize: {
...prev.layout.fontSize,
...newFontSizes,
},
},
}));
const setRadius = (newRadius: Partial<ConfigLayout["radius"]>) =>
setConfig((prev) => ({
...prev,
layout: {
...prev.layout,
radius: {
...prev.layout.radius,
...newRadius,
},
},
}));
const setOtherParams = (newOtherParams: Partial<ConfigLayout["otherParams"]>) =>
setConfig((prev) => ({
...prev,
layout: {
...prev.layout,
otherParams: {
...prev.layout.otherParams,
...newOtherParams,
},
},
}));
return (
<ThemeBuilderContext.Provider
value={{
config,
radiusValue,
borderWidthValue,
templateTheme,
font,
scaling,
resetConfig,
setLayoutColor,
setBorderWidth,
setBaseColor,
setConfiguration,
setLineHeight,
setFontSize,
setOtherParams,
setRadius,
setDefaultColor,
setContentColor,
setRadiusValue,
setBorderWidthValue,
setTemplateTheme,
setFont,
setScaling,
}}
>
{children}
</ThemeBuilderContext.Provider>
);
}
// Create a custom hook to use the ThemeBuilderContext
export function useThemeBuilder(): ThemeBuilderContextProps {
const context = useContext(ThemeBuilderContext);
if (!context) {
throw new Error("useThemeBuilder must be used within a ThemeBuilderProvider");
}
return context;
}

View File

@ -0,0 +1,56 @@
import {colors} from "@heroui/theme";
import {initialLayout} from "../constants";
import {Config} from "../types";
export const coffee: Config = {
light: {
defaultColor: {
default: "#b4afa8",
},
baseColor: {
primary: "#db924b",
secondary: "#5a8486",
success: "#9db787",
warning: "#ffd25f",
danger: "#fc9581",
},
layoutColor: {
foreground: "#a27225",
background: "#fffbf6",
overlay: colors.black,
focus: "#db924b",
},
contentColor: {
content1: "#fff2e0",
content2: "#ffe9cc",
content3: "#ffe0b8",
content4: "#ffd7a3",
},
},
dark: {
defaultColor: {
default: "#413841",
},
baseColor: {
primary: "#db924b",
secondary: "#5a8486",
success: "#9db787",
warning: "#ffd25f",
danger: "#fc9581",
},
layoutColor: {
foreground: "#c59f60",
background: "#20161F",
focus: "#db924b",
overlay: colors.white,
},
contentColor: {
content1: "#2c1f2b",
content2: "#3e2b3c",
content3: "#50374d",
content4: "#62435f",
},
},
layout: initialLayout,
};

View File

@ -0,0 +1,56 @@
import {colors} from "@heroui/theme";
import {initialLayout} from "../constants";
import {Config} from "../types";
export const elegant: Config = {
light: {
defaultColor: {
default: "#8f8f8f",
},
baseColor: {
primary: "#000000",
secondary: "#d1c4e9",
success: "#81c784",
warning: "#ffb74d",
danger: "#e57373",
},
layoutColor: {
foreground: "#4a4a4a",
background: "#ffffff",
overlay: colors.black,
focus: "#db924b",
},
contentColor: {
content1: "#f0f0f0",
content2: "#e6e6e6",
content3: "#dcdcdc",
content4: "#d2d2d2",
},
},
dark: {
defaultColor: {
default: "#8f8f8f",
},
baseColor: {
primary: "#FFFFFF",
secondary: "#5e5e5e",
success: "#388e3c",
warning: "#f57c00",
danger: "#d32f2f",
},
layoutColor: {
foreground: "#b0b0b0",
background: "#000000",
overlay: colors.white,
focus: "#000000",
},
contentColor: {
content1: "#2a2a2a",
content2: "#3b3b3b",
content3: "#4c4c4c",
content4: "#5d5d5d",
},
},
layout: initialLayout,
};

View File

@ -0,0 +1,54 @@
import {initialDarkTheme, initialLayout, initialLightTheme} from "../constants";
import {Config} from "../types";
export const emerald: Config = {
light: {
defaultColor: {
default: "#b9c9be",
},
baseColor: {
primary: "#66cc8a",
secondary: "#377cfb",
success: "#00a96e",
warning: "#ffbe00",
danger: "#ff5861",
},
layoutColor: {
foreground: "#004c1b",
background: "#f6fffa",
focus: "#66cc8a",
overlay: initialLightTheme.layoutColor.overlay,
},
contentColor: {
content1: "#e0f5e8",
content2: "#c2ebd0",
content3: "#a3e0b9",
content4: "#85d6a1",
},
},
dark: {
defaultColor: {
default: "#485248",
},
baseColor: {
primary: "#66cc8a",
secondary: "#377cfb",
success: "#00a96e",
warning: "#ffbe00",
danger: "#ff5861",
},
layoutColor: {
foreground: "#99d2ad",
background: "#010b06",
focus: "#66cc8a",
overlay: initialDarkTheme.layoutColor.overlay,
},
contentColor: {
content1: "#14291c",
content2: "#295237",
content3: "#3d7a53",
content4: "#52a36e",
},
},
layout: initialLayout,
};

View File

@ -0,0 +1,8 @@
import {initialDarkTheme, initialLayout, initialLightTheme} from "../constants";
import {Config} from "../types";
export const heroui: Config = {
light: initialLightTheme,
dark: initialDarkTheme,
layout: initialLayout,
};

View File

@ -0,0 +1,17 @@
import {Template} from "../types";
import {coffee} from "./coffee";
import {emerald} from "./emerald";
import {heroui} from "./heroui";
import {elegant} from "./elegant";
import {modern} from "./modern";
import {retro} from "./retro";
export const templates: Template[] = [
{label: "HeroUI", name: "heroui", value: heroui},
{label: "Modern", name: "modern", value: modern},
{label: "Elegant", name: "elegant", value: elegant},
{label: "Retro", name: "retro", value: retro},
{label: "Coffee", name: "coffee", value: coffee},
{label: "Emerald", name: "emerald", value: emerald},
];

View File

@ -0,0 +1,56 @@
import {colors} from "@heroui/theme";
import {initialLayout} from "../constants";
import {Config} from "../types";
export const modern: Config = {
light: {
defaultColor: {
default: "#897cc5",
},
baseColor: {
primary: "#7828c8",
secondary: "#5271ff",
success: "#1bc47d",
warning: "#ffb100",
danger: "#ff4f4f",
},
layoutColor: {
foreground: "#4a3d77",
background: "#f9f7fd",
overlay: colors.black,
focus: "#7828c8",
},
contentColor: {
content1: "#f2e8ff",
content2: "#e8daff",
content3: "#dccbff",
content4: "#cfbcff",
},
},
dark: {
defaultColor: {
default: "#282135",
},
baseColor: {
primary: "#9353d3",
secondary: "#637aff",
success: "#23d98d",
warning: "#ffca3a",
danger: "#ff6b6b",
},
layoutColor: {
foreground: "#d0aaff",
background: "#1b1526",
overlay: colors.white,
focus: "#9353d3",
},
contentColor: {
content1: "#392a4a",
content2: "#4c3560",
content3: "#5e4180",
content4: "#704ea0",
},
},
layout: initialLayout,
};

View File

@ -0,0 +1,56 @@
import {colors} from "@heroui/theme";
import {initialLayout} from "../constants";
import {Config} from "../types";
export const retro: Config = {
light: {
defaultColor: {
default: "#FFB42A",
},
baseColor: {
primary: "#FFD34E",
secondary: "#EE457E",
success: "#4CAF50",
warning: "#FF9800",
danger: "#F44336",
},
layoutColor: {
foreground: "#5A4A42",
background: "#F4E8D1",
overlay: colors.black,
focus: "#FFD34E",
},
contentColor: {
content1: "#FBEEC1",
content2: "#F7D8A5",
content3: "#F4C68B",
content4: "#F2B572",
},
},
dark: {
defaultColor: {
default: "#a58956",
},
baseColor: {
primary: "#FFD34E",
secondary: "#EE457E",
success: "#4CAF50",
warning: "#FF9800",
danger: "#F44336",
},
layoutColor: {
foreground: "#000000",
background: "#E1CA9E",
overlay: colors.white,
focus: "#FFD34E",
},
contentColor: {
content1: "#634832",
content2: "#755C44",
content3: "#887059",
content4: "#9A846E",
},
},
layout: initialLayout,
};

View File

@ -0,0 +1,129 @@
// Colors
export interface ColorShades {
50: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
}
export type ColorPickerType =
| "background"
| "content1"
| "content2"
| "content3"
| "content4"
| "danger"
| "default"
| "divider"
| "focus"
| "foreground"
| "overlay"
| "primary"
| "secondary"
| "success"
| "warning";
// HeroUI component props
export type Color = "default" | "primary" | "secondary" | "success" | "warning" | "danger";
export type Size = "sm" | "md" | "lg";
export type Variant =
| "dot"
| "solid"
| "faded"
| "bordered"
| "light"
| "flat"
| "ghost"
| "shadow"
| "underlined";
export type Radius = "none" | "sm" | "md" | "lg" | "full";
export type HeroUIScaling = 90 | 95 | 100 | 105 | 110;
export type Border = "thin" | "medium" | "thick";
export type FontName = "Inter" | "Roboto" | "Outfit" | "Lora";
// Themes
export type ThemeType = "light" | "dark";
export interface ThemeColor extends ColorShades {
foreground: string;
DEFAULT: string;
}
// Configuration
export interface Config {
light: ConfigColors;
dark: ConfigColors;
layout: ConfigLayout;
}
export interface ConfigColors {
defaultColor: {
default: string;
};
baseColor: {
primary: string;
secondary: string;
success: string;
warning: string;
danger: string;
};
layoutColor: {
foreground: string;
background: string;
focus: string;
overlay: string;
};
contentColor: {
content1: string;
content2: string;
content3: string;
content4: string;
};
}
export interface ConfigLayout {
fontSize: {
tiny: string;
small: string;
medium: string;
large: string;
};
lineHeight: {
tiny: string;
small: string;
medium: string;
large: string;
};
radius: {
small: string;
medium: string;
large: string;
};
borderWidth: {
small: string;
medium: string;
large: string;
};
otherParams: {
disabledOpacity: string;
dividerWeight: string;
hoverOpacity: string;
};
}
// Templates
export interface Template {
label: string;
name: TemplateType;
value: Config;
}
export type TemplateType = "coffee" | "emerald" | "heroui" | "elegant" | "modern" | "retro";
export type FontType = "Inter" | "Roboto" | "Outfit" | "Lora";

View File

@ -0,0 +1,138 @@
import {swapColorValues} from "@heroui/theme/src/utils/object";
import {readableColor} from "color2k";
import Values from "values.js";
import {ColorShades, ThemeType, ThemeColor, ColorPickerType} from "../types";
import {colorWeight, defaultDarkColorWeight, defaultLightColorWeight} from "../constants";
/**
* Convert color values to RGB
*/
export function colorValuesToRgb(value: Values) {
return `rgba(${value.rgb.join(", ")}, ${value.alpha})`;
}
/**
* Generate theme color
*/
export function generateThemeColor(
color: string,
type: ColorPickerType,
theme: ThemeType,
): ThemeColor {
const values = new Values(color);
const colorWeight = getColorWeight(type, theme);
const colorValues = values.all(colorWeight);
let shades = colorValues.slice(0, colorValues.length - 1).reduce((acc, shadeValue, index) => {
(acc as any)[index === 0 ? 50 : index * 100] = rgbToHex(shadeValue.rgb);
return acc;
}, {} as ColorShades);
return {
...((theme === "light" ? shades : swapColorValues(shades)) as ColorShades),
foreground: readableColor(shades[500]),
DEFAULT: shades[500],
};
}
/**
* Convert hex color to HSL
*/
export function hexToHsl(hex: string) {
// Convert hex to RGB first
const [r, g, b] = hexToRgb(hex);
// Normalize RGB values
const normalizedR = r / 255;
const normalizedG = g / 255;
const normalizedB = b / 255;
// Find the maximum and minimum values of R, G, B
const max = Math.max(normalizedR, normalizedG, normalizedB);
const min = Math.min(normalizedR, normalizedG, normalizedB);
// Calculate the lightness
const lightness = (max + min) / 2;
// If the maximum and minimum are equal, there is no saturation
if (max === min) {
return `${0} ${0}% ${lightness * 100}%`;
}
// Calculate the saturation
let saturation = 0;
if (lightness < 0.5) {
saturation = (max - min) / (max + min);
} else {
saturation = (max - min) / (2 - max - min);
}
// Calculate the hue
let hue;
if (max === normalizedR) {
hue = (normalizedG - normalizedB) / (max - min);
} else if (max === normalizedG) {
hue = 2 + (normalizedB - normalizedR) / (max - min);
} else {
hue = 4 + (normalizedR - normalizedG) / (max - min);
}
hue *= 60;
if (hue < 0) hue += 360;
return `${hue.toFixed(2)} ${(saturation * 100).toFixed(2)}% ${(lightness * 100).toFixed(2)}%`;
}
/**
* Get the color weight
*/
export function getColorWeight(colorType: ColorPickerType, theme: ThemeType) {
if (colorType === "default") {
return theme === "dark" ? defaultDarkColorWeight : defaultLightColorWeight;
}
return colorWeight;
}
/**
* Convert RGB value to hex
*/
function rgbValueToHex(c: number) {
const hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
/**
* Convert RGB to hex
*/
function rgbToHex([r, g, b]: number[]): string {
return "#" + rgbValueToHex(r) + rgbValueToHex(g) + rgbValueToHex(b);
}
/**
* Convert hex color to RGB
*/
function hexToRgb(hex: string): number[] {
// Convert hex to RGB first
let r = 0,
g = 0,
b = 0;
if (hex.length === 4 || hex.length === 5) {
r = parseInt(hex[1] + hex[1], 16);
g = parseInt(hex[2] + hex[2], 16);
b = parseInt(hex[3] + hex[3], 16);
} else if (hex.length === 7 || hex.length === 9) {
r = parseInt(hex.slice(1, 3), 16);
g = parseInt(hex.slice(3, 5), 16);
b = parseInt(hex.slice(5, 7), 16);
} else {
throw new Error("Invalid hex color format");
}
return [r, g, b];
}

View File

@ -0,0 +1,59 @@
import {HeroUIPluginConfig} from "@heroui/theme";
import {readableColor} from "color2k";
import {Config, ThemeType} from "../types";
import {generateThemeColor} from "./colors";
function generateLayoutConfig(config: Config): HeroUIPluginConfig["layout"] {
return {
disabledOpacity: config.layout.otherParams.disabledOpacity,
};
}
function generateThemeColorsConfig(config: Config, theme: ThemeType) {
return {
default: generateThemeColor(config[theme].defaultColor.default, "default", "light"),
primary: generateThemeColor(config[theme].baseColor.primary, "primary", "light"),
secondary: generateThemeColor(config[theme].baseColor.secondary, "secondary", "light"),
success: generateThemeColor(config[theme].baseColor.success, "success", "light"),
warning: generateThemeColor(config[theme].baseColor.warning, "warning", "light"),
danger: generateThemeColor(config[theme].baseColor.danger, "danger", "light"),
background: config[theme].layoutColor.background,
foreground: generateThemeColor(config[theme].layoutColor.foreground, "foreground", "light"),
content1: {
DEFAULT: config[theme].contentColor.content1,
foreground: readableColor(config[theme].contentColor.content1),
},
content2: {
DEFAULT: config[theme].contentColor.content2,
foreground: readableColor(config[theme].contentColor.content2),
},
content3: {
DEFAULT: config[theme].contentColor.content3,
foreground: readableColor(config[theme].contentColor.content3),
},
content4: {
DEFAULT: config[theme].contentColor.content4,
foreground: readableColor(config[theme].contentColor.content4),
},
focus: config[theme].layoutColor.focus,
overlay: config[theme].layoutColor.overlay,
};
}
/**
* Generate plugin configuration
*/
export function generatePluginConfig(config: Config): HeroUIPluginConfig {
return {
themes: {
light: {
colors: generateThemeColorsConfig(config, "light"),
},
dark: {
colors: generateThemeColorsConfig(config, "dark"),
},
},
layout: generateLayoutConfig(config),
};
}

View File

@ -0,0 +1,30 @@
import {Border} from "../types";
/**
* Copy data to clipboard
* @param data
*/
export function copyData(data: string) {
navigator.clipboard.writeText(data);
}
/**
* Stringify data
*
* @param data
* @returns
*/
export function stringifyData(data: unknown) {
return JSON.stringify(data, null, 2);
}
export function getBorderWidth(data: Border) {
if (data === "thin") {
return 1;
}
if (data === "medium") {
return 2;
}
return 4;
}

View File

@ -0,0 +1,18 @@
import {useEffect, useRef} from "react";
/**
* Holds the previous value of the provided [value] parameter
*
* @param value
* @returns
*/
function usePrevious<T>(value: T) {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
export default usePrevious;

View File

@ -101,7 +101,9 @@
"unified": "^11.0.5",
"unist-util-visit": "5.0.0",
"usehooks-ts": "3.1.0",
"zustand": "5.0.1"
"zustand": "5.0.1",
"react-colorful": "^5.6.1",
"values.js": "^2.1.1"
},
"devDependencies": {
"@docusaurus/utils": "2.0.0-beta.3",

View File

@ -348,7 +348,15 @@ module.exports = {
maxWidth: {
"8xl": "90rem", // 1440px
},
},
utilities: {
'.scrollbar-hide': {
'scrollbar-width': 'none',
'&::-webkit-scrollbar': {
display: 'none',
},
}
},
}
},
plugins: [
heroui({

61
pnpm-lock.yaml generated
View File

@ -454,6 +454,9 @@ importers:
react:
specifier: 18.3.0
version: 18.3.0
react-colorful:
specifier: ^5.6.1
version: 5.6.1(react-dom@18.3.0(react@18.3.0))(react@18.3.0)
react-dom:
specifier: 18.3.0
version: 18.3.0(react@18.3.0)
@ -520,6 +523,9 @@ importers:
usehooks-ts:
specifier: 3.1.0
version: 3.1.0(react@18.3.0)
values.js:
specifier: ^2.1.1
version: 2.1.1
zustand:
specifier: 5.0.1
version: 5.0.1(@types/react@18.2.8)(react@18.3.0)(use-sync-external-store@1.4.0(react@18.3.0))
@ -10955,6 +10961,10 @@ packages:
header-case@2.0.4:
resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
hex-rgb@4.3.0:
resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
engines: {node: '>=6'}
homedir-polyfill@1.0.3:
resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
engines: {node: '>=0.10.0'}
@ -12530,6 +12540,9 @@ packages:
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
mix-css-color@0.2.0:
resolution: {integrity: sha512-mZugANySFPE21tjELbQddhC6HAZNzqp7gDxmW8fJFURSWtJ0nuXU26dyrb/1AR6ZYxdEAtW2bbWT9QnRtI6Jzg==}
mixin-deep@1.3.2:
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
engines: {node: '>=0.10.0'}
@ -13010,6 +13023,12 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
parse-css-color@0.1.2:
resolution: {integrity: sha512-z7v/tf0edGsnlm9VONQtH+u/YVrdUqZXrSBzqM13scef8Abl2VyZfYsZaJoyb/AyY4SIxtoJChSQ4MURHfY3Sg==}
parse-css-color@0.2.0:
resolution: {integrity: sha512-uWQyuOe+SMxnUgHf4mjdn2C/YzA1tOW+uU8Z2UiV3qnao9ZFnvYeyzeoU7TNv8NLIJo0PiRkETW48QNJZ4IA9g==}
parse-entities@2.0.0:
resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==}
@ -13415,6 +13434,9 @@ packages:
resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==}
engines: {node: '>=12.20'}
pure-color@1.3.0:
resolution: {integrity: sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==}
pure-rand@6.1.0:
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
@ -13459,6 +13481,12 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
react-colorful@5.6.1:
resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
peerDependencies:
react: 18.3.0
react-dom: 18.3.0
react-devtools-inline@4.4.0:
resolution: {integrity: sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==}
@ -15142,6 +15170,9 @@ packages:
resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
values.js@2.1.1:
resolution: {integrity: sha512-pI6dKW3kv7BR/WzS0NvIuxegeH1r8gk8y6BuXrIYGVccynmUsUZPhSpTfkt39VBHyciD7WZi+lM+7Zyamowzeg==}
vfile-location@5.0.3:
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
@ -24124,6 +24155,8 @@ snapshots:
capital-case: 1.0.4
tslib: 2.8.1
hex-rgb@4.3.0: {}
homedir-polyfill@1.0.3:
dependencies:
parse-passwd: 1.0.0
@ -26215,6 +26248,11 @@ snapshots:
mitt@3.0.1: {}
mix-css-color@0.2.0:
dependencies:
parse-css-color: 0.1.2
pure-color: 1.3.0
mixin-deep@1.3.2:
dependencies:
for-in: 1.0.2
@ -26867,6 +26905,16 @@ snapshots:
dependencies:
callsites: 3.1.0
parse-css-color@0.1.2:
dependencies:
color-name: 1.1.4
hex-rgb: 4.3.0
parse-css-color@0.2.0:
dependencies:
color-name: 1.1.4
hex-rgb: 4.3.0
parse-entities@2.0.0:
dependencies:
character-entities: 1.2.4
@ -27310,6 +27358,8 @@ snapshots:
dependencies:
escape-goat: 4.0.0
pure-color@1.3.0: {}
pure-rand@6.1.0: {}
querystring@0.2.0: {}
@ -27352,6 +27402,11 @@ snapshots:
minimist: 1.2.8
strip-json-comments: 2.0.1
react-colorful@5.6.1(react-dom@18.3.0(react@18.3.0))(react@18.3.0):
dependencies:
react: 18.3.0
react-dom: 18.3.0(react@18.3.0)
react-devtools-inline@4.4.0:
dependencies:
es6-symbol: 3.1.4
@ -29436,6 +29491,12 @@ snapshots:
validate-npm-package-name@5.0.1: {}
values.js@2.1.1:
dependencies:
mix-css-color: 0.2.0
parse-css-color: 0.2.0
pure-color: 1.3.0
vfile-location@5.0.3:
dependencies:
'@types/unist': 3.0.3