From 395b6c6be515de405004a12d464657f0b36a85e4 Mon Sep 17 00:00:00 2001 From: nnecec Date: Wed, 20 Mar 2024 23:46:58 +0800 Subject: [PATCH] Initial commit --- .changeset/README.md | 8 + .changeset/config.json | 11 + .gitattributes | 2 + .github/workflows/ci.yml | 32 + .github/workflows/release.yml | 54 + .gitignore | 37 + .npmrc | 2 + .prettierrc.json | 1 + .vscode/launch.json | 11 + .vscode/settings.json | 3 + LICENSE | 21 + apps/website/.gitignore | 36 + apps/website/.npmrc | 1 + apps/website/app/favicon.ico | Bin 0 -> 15406 bytes apps/website/app/globals.css | 7 + apps/website/app/layout.tsx | 25 + apps/website/app/page.tsx | 24 + apps/website/app/provider.tsx | 7 + apps/website/app/site.webmanifest | 17 + apps/website/components/color-picker.tsx | 46 + apps/website/components/palette/footer.tsx | 53 + apps/website/components/palette/intro.tsx | 62 + apps/website/components/palette/swatches.tsx | 190 + apps/website/components/palette/tools.tsx | 224 + apps/website/components/palette/utils.tsx | 48 + apps/website/next.config.mjs | 6 + apps/website/package-lock.json | 1749 ++++ apps/website/package.json | 35 + apps/website/postcss.config.js | 6 + .../website/public/android-chrome-192x192.png | Bin 0 -> 13191 bytes .../website/public/android-chrome-512x512.png | Bin 0 -> 42538 bytes apps/website/public/apple-touch-icon.png | Bin 0 -> 11900 bytes apps/website/public/assets/logo.svg | 100 + apps/website/public/favicon-16x16.png | Bin 0 -> 633 bytes apps/website/public/favicon-32x32.png | Bin 0 -> 1592 bytes apps/website/tailwind.config.ts | 17 + apps/website/tsconfig.json | 32 + apps/website/utils/color.ts | 11 + .../utils/use-isomorphic-layout-effect.ts | 3 + apps/website/utils/use-memoized-fn.ts | 17 + apps/website/utils/use-props-value.ts | 29 + eslint.config.js | 40 + package.json | 30 + packages/palette/CHANGELOG.md | 0 packages/palette/package.json | 37 + packages/palette/readme.md | 1 + packages/palette/src/build-option.ts | 24 + packages/palette/src/easing.ts | 144 + packages/palette/src/index.ts | 2 + packages/palette/src/palette.ts | 21 + packages/palette/src/shade.ts | 27 + packages/palette/src/swatch.ts | 28 + packages/palette/src/types.ts | 51 + packages/palette/src/utils.ts | 7 + packages/palette/tsconfig.json | 3 + packages/tailwind-plugin-palette/CHANGELOG.md | 0 packages/tailwind-plugin-palette/package.json | 40 + packages/tailwind-plugin-palette/readme.md | 23 + packages/tailwind-plugin-palette/src/core.ts | 29 + packages/tailwind-plugin-palette/src/index.ts | 3 + .../tailwind-plugin-palette/src/plugin.ts | 31 + packages/tailwind-plugin-palette/src/types.ts | 14 + packages/tailwind-plugin-palette/src/utils.ts | 57 + .../tailwind-plugin-palette/tsconfig.json | 3 + pnpm-lock.yaml | 8512 +++++++++++++++++ pnpm-workspace.yaml | 4 + readme.md | 15 + tools/color-kit-palette.svg | 73 + tools/lib.d.ts | 10 + tsconfig.json | 12 + turbo.json | 19 + 71 files changed, 12187 insertions(+) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .gitattributes create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierrc.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 apps/website/.gitignore create mode 100644 apps/website/.npmrc create mode 100644 apps/website/app/favicon.ico create mode 100644 apps/website/app/globals.css create mode 100644 apps/website/app/layout.tsx create mode 100644 apps/website/app/page.tsx create mode 100644 apps/website/app/provider.tsx create mode 100644 apps/website/app/site.webmanifest create mode 100644 apps/website/components/color-picker.tsx create mode 100644 apps/website/components/palette/footer.tsx create mode 100644 apps/website/components/palette/intro.tsx create mode 100644 apps/website/components/palette/swatches.tsx create mode 100644 apps/website/components/palette/tools.tsx create mode 100644 apps/website/components/palette/utils.tsx create mode 100644 apps/website/next.config.mjs create mode 100644 apps/website/package-lock.json create mode 100644 apps/website/package.json create mode 100644 apps/website/postcss.config.js create mode 100644 apps/website/public/android-chrome-192x192.png create mode 100644 apps/website/public/android-chrome-512x512.png create mode 100644 apps/website/public/apple-touch-icon.png create mode 100644 apps/website/public/assets/logo.svg create mode 100644 apps/website/public/favicon-16x16.png create mode 100644 apps/website/public/favicon-32x32.png create mode 100644 apps/website/tailwind.config.ts create mode 100644 apps/website/tsconfig.json create mode 100644 apps/website/utils/color.ts create mode 100644 apps/website/utils/use-isomorphic-layout-effect.ts create mode 100644 apps/website/utils/use-memoized-fn.ts create mode 100644 apps/website/utils/use-props-value.ts create mode 100644 eslint.config.js create mode 100644 package.json create mode 100644 packages/palette/CHANGELOG.md create mode 100644 packages/palette/package.json create mode 100644 packages/palette/readme.md create mode 100644 packages/palette/src/build-option.ts create mode 100644 packages/palette/src/easing.ts create mode 100644 packages/palette/src/index.ts create mode 100644 packages/palette/src/palette.ts create mode 100644 packages/palette/src/shade.ts create mode 100644 packages/palette/src/swatch.ts create mode 100644 packages/palette/src/types.ts create mode 100644 packages/palette/src/utils.ts create mode 100644 packages/palette/tsconfig.json create mode 100644 packages/tailwind-plugin-palette/CHANGELOG.md create mode 100644 packages/tailwind-plugin-palette/package.json create mode 100644 packages/tailwind-plugin-palette/readme.md create mode 100644 packages/tailwind-plugin-palette/src/core.ts create mode 100644 packages/tailwind-plugin-palette/src/index.ts create mode 100644 packages/tailwind-plugin-palette/src/plugin.ts create mode 100644 packages/tailwind-plugin-palette/src/types.ts create mode 100644 packages/tailwind-plugin-palette/src/utils.ts create mode 100644 packages/tailwind-plugin-palette/tsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 readme.md create mode 100644 tools/color-kit-palette.svg create mode 100644 tools/lib.d.ts create mode 100644 tsconfig.json create mode 100644 turbo.json diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..e5b6d8d --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..80bf895 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["website"] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0650281 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI Test + +on: [pull_request] + +jobs: + test: + timeout-minutes: 20 + + runs-on: ubuntu-latest + + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: 20 + + - name: Use pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install Dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Test + run: pnpm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a3d8888 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,54 @@ +name: Release + +on: + workflow_dispatch: + push: + paths: + - '.changeset/**' + - 'packages/**' + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install Dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Creating .npmrc + run: | + cat << EOF > "$HOME/.npmrc" + //registry.npmjs.org/:_authToken=$NPM_TOKEN + EOF + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Create Release Pull Request or Release to npm + id: changesets + uses: changesets/action@v1 + with: + # This expects you to have a script called release which does a build for your packages and calls changeset publish + publish: pnpm release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b570de0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage + +# dist +.next/ +out/ +build +dist + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo + +# vercel +.vercel diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..d97b929 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +registry=https://registry.npmjs.org/ +public-hoist-pattern[]=*@nextui-org/* diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..01aa840 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +"@nnecec/prettier-config" diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d642209 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..25fa621 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4ab8403 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 nnecec + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/website/.gitignore b/apps/website/.gitignore new file mode 100644 index 0000000..fd3dbb5 --- /dev/null +++ b/apps/website/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/website/.npmrc b/apps/website/.npmrc new file mode 100644 index 0000000..1778f10 --- /dev/null +++ b/apps/website/.npmrc @@ -0,0 +1 @@ +public-hoist-pattern[]=*@nextui-org/* diff --git a/apps/website/app/favicon.ico b/apps/website/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..685cbad04d8d8e416efd2e6a2a1f257959d986ea GIT binary patch literal 15406 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 + {children} + + ) +} diff --git a/apps/website/app/page.tsx b/apps/website/app/page.tsx new file mode 100644 index 0000000..e16fc6a --- /dev/null +++ b/apps/website/app/page.tsx @@ -0,0 +1,24 @@ +'use client' + +import { MotionConfig } from 'framer-motion' +import { Provider } from 'jotai' + +import { PaletteFooter } from '../components/palette/footer' +import { PaletteIntro } from '../components/palette/intro' +import { PaletteSwatches } from '../components/palette/swatches' +import { PaletteTools } from '../components/palette/tools' + +export default function RootPage() { + return ( +
+ + + + + + + + +
+ ) +} diff --git a/apps/website/app/provider.tsx b/apps/website/app/provider.tsx new file mode 100644 index 0000000..71a34a2 --- /dev/null +++ b/apps/website/app/provider.tsx @@ -0,0 +1,7 @@ +'use client' + +import { NextUIProvider } from '@nextui-org/react' + +export default function Providers({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/website/app/site.webmanifest b/apps/website/app/site.webmanifest new file mode 100644 index 0000000..4e81430 --- /dev/null +++ b/apps/website/app/site.webmanifest @@ -0,0 +1,17 @@ +{ + "name": "ColorKit", + "short_name": "ColorKit", + "icons": [ + { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }, + { + "src": "/favicon.ico", + "sizes": "any", + "type": "image/x-icon" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone", + "start_url": "/" +} diff --git a/apps/website/components/color-picker.tsx b/apps/website/components/color-picker.tsx new file mode 100644 index 0000000..8b5c879 --- /dev/null +++ b/apps/website/components/color-picker.tsx @@ -0,0 +1,46 @@ +import type { ButtonProps } from '@nextui-org/react' + +import { useRef } from 'react' + +import { Button } from '@nextui-org/react' + +import { usePropsValue } from '../utils/use-props-value' + +export const ColorPicker = ({ + defaultValue, + onChange, + value, + ...props +}: { defaultValue?: string; onChange?: (value: string) => void; value?: string } & Omit) => { + const ref = useRef(null) + + const [val, setVal] = usePropsValue({ defaultValue: defaultValue ?? '', onChange, value }) + + return ( + + ) +} diff --git a/apps/website/components/palette/footer.tsx b/apps/website/components/palette/footer.tsx new file mode 100644 index 0000000..26cd6a6 --- /dev/null +++ b/apps/website/components/palette/footer.tsx @@ -0,0 +1,53 @@ +import { useState } from 'react' + +import { motion } from 'framer-motion' +import Image from 'next/image' + +import { Link } from '@nextui-org/react' + +const MotionImage = motion(Image) + +export function PaletteFooter() { + const [isHovered, setHovered] = useState(false) + return ( +
+ setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + + +
+ + Github + + + X + +
+
+ Built by{' '} + + nnecec + +
+
+
+
+ ) +} diff --git a/apps/website/components/palette/intro.tsx b/apps/website/components/palette/intro.tsx new file mode 100644 index 0000000..35c02e2 --- /dev/null +++ b/apps/website/components/palette/intro.tsx @@ -0,0 +1,62 @@ +'use client' + +import { useState } from 'react' + +import { motion, useMotionValueEvent, useScroll, useTransform } from 'framer-motion' + +import { Snippet } from '@nextui-org/react' + +export function PaletteIntro() { + const innerHeight = typeof window === undefined ? 600 : window.innerHeight + + const [inPaletteView, setInPaletteView] = useState(false) + + const { scrollY } = useScroll() + + useMotionValueEvent(scrollY, 'change', latest => { + if (latest > innerHeight / 2) { + !inPaletteView && setInPaletteView(true) + } else { + inPaletteView && setInPaletteView(false) + } + }) + + const titleY = useTransform(scrollY, [0, innerHeight], [0, innerHeight / 2.5]) + + return ( +
+ +

tailwind-plugin-palette

+

+ tailwind +
+ plugin +
+ palette +

+ +
+
Install via
+
+ + npm + + + pnpm + + + bun + +
+
+
+
+ ) +} diff --git a/apps/website/components/palette/swatches.tsx b/apps/website/components/palette/swatches.tsx new file mode 100644 index 0000000..27dcc37 --- /dev/null +++ b/apps/website/components/palette/swatches.tsx @@ -0,0 +1,190 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' + +import { CheckIcon, XMarkIcon } from '@heroicons/react/20/solid' +import clsx from 'clsx' +import { motion, useMotionValueEvent, useScroll } from 'framer-motion' +import { useAtom } from 'jotai' + +import { Button, Input, Tooltip } from '@nextui-org/react' + +import { getColorName } from '../../utils/color' +import { ColorPicker } from '../color-picker' +import { colorsAtom, editingSwatchesAtom, paletteAtom } from './utils' + +export function PaletteSwatches() { + const [inPaletteView, setInPaletteView] = useState(false) + const [colors, setColors] = useAtom(colorsAtom) + const [palette] = useAtom(paletteAtom) + + const [editingSwatches, setEditingSwatches] = useAtom(editingSwatchesAtom) + + const isEmptyPalette = colors.length === 0 + + const { scrollY } = useScroll() + + useMotionValueEvent(scrollY, 'change', latest => { + if (latest > window.innerHeight / 2) { + !inPaletteView && setInPaletteView(true) + } else { + inPaletteView && setInPaletteView(false) + } + }) + + return ( +
+ {isEmptyPalette ? +
+

There is no swatch yet,

+

Please click the plus button or choose a primary color

+
+ : colors.map((swatch, index) => { + const editingSwatch = editingSwatches.get(index) + const isInvalid = Boolean(editingSwatch?.errorMessage) + + return ( +
+ + { + const newSwatch = { ...swatch, name: value } + editingSwatches.set(index, newSwatch) + setEditingSwatches(new Map(editingSwatches)) + }} + value={editingSwatch?.name ?? swatch.name} + /> + + {!!swatch.hex && ( + { + const newSwatch = { ...swatch, hex: value, name: getColorName(value) } + editingSwatches.set(index, newSwatch) + setEditingSwatches(new Map(editingSwatches)) + }} + size="sm" + value={editingSwatch?.hex ?? swatch.hex} + /> + )} + + {editingSwatch && (editingSwatch.name !== swatch.name || editingSwatch.hex !== swatch.hex) ? + <> + + + + : null} + + {!!palette[swatch.name] && } +
+ ) + }) + } +
+ ) +} + +function Swatches({ name, swatches }: { name: string; swatches: Record }) { + const ref = useRef(null) + + useEffect(() => { + ref.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }) + }, []) + + return ( + + {Object.entries(swatches as Record).map(([step, shade]) => ( + +
+ +
+ + + ))} + + ) +} diff --git a/apps/website/components/palette/tools.tsx b/apps/website/components/palette/tools.tsx new file mode 100644 index 0000000..8050197 --- /dev/null +++ b/apps/website/components/palette/tools.tsx @@ -0,0 +1,224 @@ +'use client' + +import { useMemo, useRef, useState, useTransition } from 'react' + +import { BookOpenIcon, CogIcon, PlusIcon, XMarkIcon } from '@heroicons/react/20/solid' +import { motion, useMotionValueEvent, useScroll } from 'framer-motion' +import { useAtom } from 'jotai' + +import { + Badge, + Checkbox, + Modal, + ModalBody, + ModalContent, + ModalHeader, + Popover, + PopoverContent, + PopoverTrigger, + Snippet, + Tooltip, + useDisclosure, +} from '@nextui-org/react' + +import { ColorPicker } from '../../components/color-picker' +import { randomColor } from '../../utils/color' +import { IconButton, colorsAtom, optionsAtom } from './utils' + +export function PaletteTools() { + const [inPaletteView, setInPaletteView] = useState(false) + const [options, setOptions] = useAtom(optionsAtom) + const [colors, setColors] = useAtom(colorsAtom) + const { isOpen, onOpen, onOpenChange } = useDisclosure() + const toolsRef = useRef() + + const [, startTransition] = useTransition() + + const isEmptyPalette = colors.length === 0 + + const addSwatch = () => { + let color = randomColor() + + while (colors.some(({ name }) => name === color.name)) { + color = randomColor() + } + setColors([...colors, color]) + } + + const { scrollY } = useScroll() + + useMotionValueEvent(scrollY, 'change', latest => { + if (latest > window.innerHeight / 2) { + !inPaletteView && setInPaletteView(true) + } else { + inPaletteView && setInPaletteView(false) + } + }) + + const copiedOptions = useMemo(() => { + return JSON.stringify( + Object.fromEntries([ + ['colors', Object.fromEntries(colors.map(color => [color.name, color.hex]))], + ...Object.entries(options).filter(([, value]) => Boolean(value)), + ]), + null, + 2, + ) + }, [colors, options]) + + return ( +
+ + + } + isInvisible={!options.primary} + isOneChar + onClick={() => { + setOptions({ ...options, primary: '' }) + setColors(colors.filter(color => !['primary', 'secondary'].includes(color.name))) + }} + shape="circle" + showOutline={false} + > + { + if (!options.primary) { + colors.unshift({ disabled: true, name: 'primary' }, { disabled: true, name: 'secondary' }) + } + startTransition(() => { + setOptions(options => ({ ...options, primary: value })) + }) + }} + value={options.primary} + /> + + + + + + + + + + + + + + + + +
+

More options

+ setOptions({ ...options, dark: value })}> + Dark + + + setOptions({ ...options, reversed: value })} + > + Reversed + + + setOptions({ ...options, harmonize: value })} + > + Harmonize + +
+
+
+ + + + + + + + + +
+
+
+ + + + How to configure your Tailwind.CSS? + +
+

1. Install tailwind-plugin-palette

+
+ + npm + + pnpm + + bun + +
+

2. Configure your tailwind config file

+
{`import palette, { getTailwindColors } from 'tailwind-plugin-palette'
+
+export default {
+  plugins: [
+    palette({
+      colors: getTailwindColors(400),
+      primary: "#ADCE91",
+      dark: true,
+      reversed: true,
+      harmonize: true
+    })
+  ]
+}`}
+

3. Options

+
    +
  • + {'colors: Record'}: A colors object, where the key is the name of the + color and the value is the hexadecimal value of the color. eg,{' '} + {'colors: { red: "#ff0000" }'} +
  • +
  • + primary: string: Provide a hex value as primary color, automatically generate a secondary + color +
  • +
  • + dark: boolean: Reduce the brightness to adapt to the dark mode +
  • +
  • + reversed: boolean: Reverse the color value +
  • +
  • + harmonize: boolean: Make the palette more harmonious with the primary color(primary + required) +
  • +
+
+
+
+
+
+ ) +} diff --git a/apps/website/components/palette/utils.tsx b/apps/website/components/palette/utils.tsx new file mode 100644 index 0000000..a3f62d8 --- /dev/null +++ b/apps/website/components/palette/utils.tsx @@ -0,0 +1,48 @@ +import type { ButtonProps } from '@nextui-org/react' + +import { forwardRef } from 'react' + +import { motion } from 'framer-motion' +import { atom } from 'jotai' +import { createPalette } from 'tailwind-plugin-palette' + +import { Button } from '@nextui-org/react' + +export const IconButton = forwardRef(function IconButton(props: ButtonProps, ref: any) { + return