mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
v2.7.0 (#4835)
* chore: org name change (#4596) * chore: update brand name (#4600) * fix(calendar): function components cannot be given refs (#4614) * docs(modal): fix small typos and add clarifying language (#4629) * chore(deps): bump RA versions (#4611) * chore(deps): bump RA versions * chore(deps): bump @internationalized/date * chore(docs): update RA versions * chore(docs): update versions * chore(docs): use string type * chore(deps): update @react-types versions * refactor(docs): undo version change since they will be removed in another PR * feat: tailwind variants upgrade (#4386) * feat: tailwind variants upgrade * chore: restore npmrc * chore: adjust pkgs * fix: versions * fix: lock file * chore(changeset): update package name * chore(deps): use fixed version * fix(test): incorrect package name --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * feat: add fn win alt keys (#4638) * feat: add new keys * feat: add new keys * chore: update docs & storybook as well --------- Co-authored-by: WK Wong <wingkwong.code@gmail.com> * fix(use-image): load images after props change (#4523) * fix(use-image): load image after props change * chore(changeset): add changeset * refactor(use-image): remove unused props * feat(use-image): add test case * fix(use-image): apply useCallback to load & remove status check * chore(changeset): update package name * feat: global labelPlacement prop (#4346) * feat: adding the support for labelPlacement globally * chore: reafctoring * chore: updating the dependency * chore(changeset): update package name * chore: adding Marcus's suggestions --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * fix(form): use native as default validation behavior (#4425) * fix(form): use native as default validation behavior * docs(form): delete explicit validationBehavior=native * test(form): adjusted form test validation behaviors * chore(form): adjusted stories with forms * chore(changeset): changed form default validation behavior to native * chore(changeset): removed packages with only test changes * chore(changeset): change to patch * chore(changeset): update package name * refactor(docs): update package name * refactor(docs): update to heroui --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * feat(spinner): new spinner variants (#4555) * refactor(spinner): add default variant * feature(spinner): add gradient variant * feature(spinner): add dots variant * feature(spinner): add dots-blink variant * feature(spinner): add spinner-bars * chore(spinner): add variants storybook * chore: adding variants to docs * chore: simplyfying the styles and modifying docs * chore: nits * chore: updating the dots and dots-blink animation * chore: nits * chore: adding Marcus' suggestions * chore: adding Marcus's suggestions * chore: adding junior's suggestions --------- Co-authored-by: Maharshi Alpesh <maharshialpesh@gmail.com> * fix: rename wrapper to tab wrapper (#4636) * fix: rename wrapper to tab wrapper * docs: update * docs: update * docs: update * fix: rename wrapper to tab wrapper * refactor: remove feature request from issue template (#4661) * refactor(.github): remove feature request template * refactor(.github): add a link to redirect to discussion (feature request category) * docs(table): include TS examples to show Selection type usage (#4793) * fix(listbox): unexpected scrollShadow on virtualized listbox (#4784) * fix(listbox): add scroll height & scroll top to listbox * fix(use-data-scroll-overflow): handle scrollHeight & scrollTop in virtualization * chore(changeset): add changeset * refactor(theme): replace left & right by start & end to support RTL (#4782) * fix(date-picker): deprecate dateInputClassNames (#4780) * chore(date-picker): add missing slots comments * fix(date-picker): remove dateInputClassNames * fix(date-picker): use classNames instead of dateInputClassNames * chore(docs): add missing attributes * fix(date-picker): use classNames instead of dateInputClassNames * feat(changeset): add changeset * fix(docs): broken type * refactor(navbar): remove dropdown menu width (#4757) * refactor: remove dropdown menu width * refactor: shorter description * refactor: rename instances of NextUI to Hero UI (#4645) * docs: use the correct org for `img.shields.io` license in README * docs: update opencollective org name * docs: use correct org name in site footer * docs: update image urls for heroui pro sections * docs: update laravel installation keywords in route config * docs: add `heroui` tag to `Introducing HeroUI` blog post * fix: use correct names in `plop/components/src` templates * chore: add empty changeset * fix: revert image urls back to `nextuipro.nyc3.cdn.digitaloceanspaces...` * chore: undo footer change * chore: update incorrect brand name * chore(docs): nextui -> heroui --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * fix(input): missing clear button with file input type (#4599) * fix(theme): sync with input theme on labelPlacement (#4597) * fix(theme): sync with input theme on labelPlacement * chore(select): revise width for labelPlacement * chore(changeset): add changeset * test(input): input interaction tests (#4579) * test(input): user interaction tests * test(input): missing act wrappers --------- Co-authored-by: WK Wong <wingkwong.code@gmail.com> * fix(calendar): rtl navigation (#4565) * fix(calendar): rtl navigation * chore(changeset): fixed reverse behavior of NextButton and PrevButton in the RTL calendar * chore(changeset): update package name * refactor(calendar): prefer isRTL and use className in theme package instead * chore(changeset): add theme package as well * chore(calendar): add min theme package to 2.4.7 --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * refactor: remove unnecessary className passing to tv and make naming consistent (#4558) * refactor: remove unnecessary className passing to tv * refactor(button): move styles to getButtonProps * refactor: rename classNames to styles to keep the naming consistent * fix: deprecation warning triggered by internal onClick (#4557) * fix(use-aria-link): onClick deprecation warning * fix(use-aria-button): onClick deprecation warning * feat(changeset): add changeset * fix(use-aria-button): incorrect prop name * chore(changeset): update package name * ci: add pkg pr new (#4540) * ci: add pkg pr new * ci: add pkg pr new * chore(workflow): update repo name --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * chore(docs): remove shouldBlockScroll prop in Tooltip page (#4539) * fix(use-pagination): controlled page after delay (#4536) * fix(use-pagination): add page to dependency for scrollTo * feat(changeset): add changeset * chore(changeset): update package name * fix(tooltip): accessing element.ref was removed in React 19 issue (#4531) * fix(tooltip): accessing element.ref was removed in React 19 issue * chore(changeset): update package name * fix: correctly dismissable default value (#4524) * fix: correctly dismissable default value * fix: correctly dismissable default value * chore(changeset): update package name --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * fix(theme): input height in innerWrapper in Select (#4512) * fix(select): fix input height #4321 * chore(select): changed package name in changeset to theme * chore(select): updated changeset message * chore(changeset): update package name --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * fix: inert value in next15 (#4491) * feat: add post install * feat: add postinstall * feat: add postinstall * fix: type * fix: type * fix: next version * chore(changeset): update package name --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * refactor: remove cursor-hit in hiddenInputClasses (#4474) * refactor: remove cursor-hit in hiddenInputClasses * Create lazy-ants-exercise.md * chore(changeset): update package name --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * feat(table): virtualization (#4285) * feat: baseline virtualization for table * merge branch canary * fix: table layout * fix: calc header height w layouteffect to offset padding * Merge branch 'canary' into feat/eng-1633-virtualization-for-table * chore: remove unused files and comments * chore: add missing package * feat: add shouldVirtualize conditional to render virtualized-table * feat: update docs for table * feat: use wrapper to support theme styles * chore: add changeset * chore(changeset): update package name * chore(deps): pnpm-lock.yaml * fix(table): outdated package name * chore(changeset): add issue number * fix(deps): keep the version consistent with other components * fix(table): incorrect displayName * refactor(table): use VirtualizedTemplate * chore(deps): bump `@tanstack/react-virtua` * chore(deps): typecheck issue * fix(table): do not use any type * chore: remove auto virtualization --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> Co-authored-by: Junior Garcia <jrgarciadev@gmail.com> * feat(toast): introduce Toast component (#4437) * feat: initial commit * chore: adding the animation * chore: nits * chore: fixes and adding draft1 of stories * chore: adding the docs draft * chore: adding the swiping interaction for toast removal * chore: adding the tests * fix: improving the progress bar logix * chore: refactoring and refining the animations * fix: making the animations compatible with the positons * chore: fixing the styles * chore: modifying the animations * chore: improving the animations * chore: adding the decorator to the story-book * chore: fixing the animations and positions * fix: handle expand region on touch * feat: adding the promises support * chore: updating the styles * chore: improving styles * chore: styles correction * fix: adding junior's suggestions * chore: correcting styles * fix: fixing the timer behavior * chore: adding the spinner to the toast * chore: full width for mobile * chore: modifying styles * chore: fixing the positions on smaller devices * chore: adding story with description * chore: adding credits for sonner * fix: adding junior's suggestions * chore: adding the exit animation * fix: adding junior's suggestions * chore: improving the swipe animations * fix: fixing the swipe animations on touch * chore: adding tests * chore: adding swipe threshild and initial position variable * fix: fixing autoclose in timeout * chore: modifying the docs * chore: fixing the conflict * chore: adding marcus' suggestions * chore: adding the bottom animations * chore: modying docs * chore: removing nextui references * chore: adding info about the provider * chore: updating the docs * chore: versions in package.json * chore: nits * chore: adding junior's suggestions * chore: nits * fix: applying junior's suggestions * chore: adding junior's suggestions * chore: using domMax * fix: adding Marcus's suggestions * chore: add global toast props and custom close icon * chore: adding the defaultTimout provider prop * chore: modifying defaultTimeout * chore: nits * fix: adding Marcus' suggestions * chore: fixing bg * chore(deps): bump RA deps * fix: fixing the color discrepancy due to the timer * chore: moving the kapan ai to the left side * refactor(toast): update author * chore: nit * chore: improvements * chore: updating the solid variant --------- Co-authored-by: Junior Garcia <jrgarciadev@gmail.com> Co-authored-by: WK Wong <wingkwong.code@gmail.com> * fix(docs): correct Tab usage example (#4821) * chore(docs): add note itemHeight for virtualization (#4822) * chore(docs): add note itemHeight for virtualization * fix: format * fix(docs): fix horizontal scrolling example in scroll-shadow (#4820) * refactor: update author in package.json (#4800) * feat(button): export PressEvent for onPress event typing (#4819) * fix(docs): failed to install dependencies in StackBlitz (#4639) * chore(Docs): remove step 2 from "Using use-theme-hook" (#4797) * fix(docs): incorrect code Modal placement (#4652) * docs: update DatePicker example to remove "time" label as time selection is not supported in this example (#4443) * feat(button): export PressEvent for onPress event typing * revert unnecessary changes * chore: format --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> Co-authored-by: Praharsh Bhatt <30700808+praharshbhatt@users.noreply.github.com> * fix(listbox): pass missing press events to usePress (#4812) * fix(listbox): pass missing press events to usePress * feat(listbox): add test case for press event * chore(changeset): add changeset * fix(checkbox): inherit stroke in CheckboxIcon (#4811) * fix: `SelectItem`, `ListboxItem`, and `AutocompleteItem` not to accept `value` props (#4653) * fix(select): `SelectItem` does not accept value props * refactor: do not use the index as `key` * Update .changeset/light-hairs-draw.md * chore: remove unnecessary `value` props * chore: update changeset * refactor: remove unnecessary value prop --------- Co-authored-by: WK Wong <wingkwong.code@gmail.com> * fix: pkg package scope (#4823) * fix: pkg package scope * fix: pkg package scope * fix: pkg package scope * fix(theme): border radius in Table when isMultiSelectable (#4808) * fix(theme): border radius in Table when isMultiSelectable * chore(theme): added changeset (#4807) * chore: removing the kapa ai for toast doc page (#4833) * fix(accordion): add data-slot attributes to accordion (#4832) * fix(accordion): add data-slot attributes to accordion * chore --------- Co-authored-by: Hovannes Markarian <hovannes.markarian@socrate.fr> Co-authored-by: աӄա <wingkwong.code@gmail.com> * chore(docs): update versions (#4836) * 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 * feat: introduce NumberInput (#4475) * feat(number-field): init structure * feat(deps): add `@nextui-org/button` & `@react-types/button` * feat(theme): export number-field * feat(number-field): storybook init structure * feat(number-field): add NumberFieldHorizontalStepper * feat(number-field): add NumberFieldHorizontalStepper * feat(theme): init number field theme * feat(number-field): number-field draft * refactor(number-field): revise stepper icons * feat(shared-icons): add ChevronLeftIcon * feat(theme): stepperButton styles * feat(theme): number-field styles * fix(number-field): label layout * feat(number-field): vertical stepper wrapper * feat(number-field): use-number-field (wip) * feat(number-field): add data-direction * feat(theme): center the text if it is horizontal stepper * feat(number-field): add HorizontalStepper * feat(number-field): add HideStepper * chore(number-field): revise minValue & defaultValue * feat(docs): init number field structure * fix(theme): outside-left styles * refactor(theme): remove labelPlacement styles * refactor(number-field): remove labelContent logic * refactor(number-field): remove labelPlacement args * feat(number-field): helper text * feat(number-field): revise number field stories * feat(number-field): description * refactor(number-field): revise number field stories * feat(theme): numberFieldLabelClasses * fix(number-field): incorrect button props * fix(number-field): typing issue on stepper buttons * chore(number-field): add aria-label * refactor(number-field): merge props * fix(number-field): pass originalProps instead * chore(number-field): revise Required story args * feat(number-field): add WithStepValue & WithWheelDisabled & revise stories * chore(number-field): add label to Required * feat(docs): number-field doc page * fix(number-field): typing issue * fix(number-field): test cases * fix(number-field): user.keyboard & defaultValue * fix(number-field): should work with defaultValues * chore(number-field): add type: number * chore(number-field): remove hidden related code * fix(number-field): numeric value * chore(changeset): add changeset * feat(deps): add "@nextui-org/number-field" to docs * feat(react): export `@nextui-org/number-field` * feat(changeset): add @nextui-org/react * feat(docs): number-field examples * chore(number-field): use text instead * refactor(number-field): remove unnecessary filled-within * fix(number-field): test case * chore(number-field): remove aria-label for stepper buttons * feat(docs): add incrementAriaLabel & decrementAriaLabel to NumberField * chore(number-field): reorder WithFormatOptions * fix(deps): update number-field's peerDependencies & dependencies * feat(number-field): hidden input for holding numeric vaule * fix(docs): number field title * feat(docs): add format options to number field * chore(docs): revise number field content * chore(number-field): add type to useDOMRef * fix(number-field): clear button * fix(theme): clear button styles * refactor(theme): stepper button styles * chore(number-field): accept stepperButton class * fix(theme): helper wrapper padding * feat(deps): add `@react-aria/i18n` * fix(number-field): use locale from `@react-aria/i18n` * fix(deps): dependency order * fix(docs): incorrect command * chore(docs): remove type=number * chore(theme): add padding to stepper wrapper * fix(number-field): avoid resetting value * fix(number-field): storybook * chore(docs): remove custom impl * chore(docs): update docs code & content * chore(number-field): migrate to heroui * chore(number-field): migrate to heroui * chore(number-field): migrate to heroui * chore: rename to number input * fix(number-input): incorrect import * chore(docs): rename to number input * chore: change to number input * refactor(number-input): change label to amount * fix(docs): use heroui commands * chore(changeset): update package name * refactor(number-input): remove steps * refactor: remove helper text * feat(number-input): label placement * refactor(number-input): rename stepper * fix(theme): isClearable * feat(docs): add label placements * refactor(docs): update number-input content * fix(docs): incorrect file * feat(docs): add lablePlacement * refactor(docs): remove labelPlacement & startContent * refactor(docs): remove helperText * refactor(docs): remove helperText * refactor(docs): revise description * feat(number-input): add data-slot for stepper-wrapper * fix(number-input): test cases * fix(docs): unexpected change * refactor(number-input): update outdated info * fix(docs): coderabbitai comments * refactor: remove validationState * fix(docs): typo * chore(deps): remove unnecessary dep * chore(deps): bump RA versions * chore(number-input): apply latest labelPlacement change * refactor(number-input): update author * refactor(number-input): revise stepper wrapper alignment * refactor(number-input): stepper button styles * chore(number-input): add disableRipple * fix(theme): increase stepper button click area * fix(number-input): sync latest validationBehavior changes * fix(number-input): pass validationBehavior to useAriaNumberInput * chore(docs): add import react * chore(number-input): remove HorizontalStepper story * chore(number-input): enable ripple * fix(number-input): remove number type * refactor(theme): follow input clear button styles * feat(theme): add color for stepperButton * fix(theme): revise stepperButton size for outside & outside-left cases * fix(number-input): typo * chore(docs): update description for wheel * chore(theme): change opacity when pressed * chore(number-input): add disableRipple * Update .changeset/witty-flies-reflect.md * fix(theme): add hover opacity effect --------- Co-authored-by: Junior Garcia <jrgarciadev@gmail.com> * chore(docs): revised tags in doc routes for 2.7.0 (#4777) * chore(docs): remove last version update tags * chore(docs): add updated tag for 2.7.0 * chore(docs): updated table * chore(docs): update search meta * chore(docs): update github info * Merge branch 'canary' into docs/eng-2003 * chore(docs): update routes.json * chore(docs): update meta info * chore: improve theme builder * v2.7.0 * chore: v2.7.0 combined changeset * fix: changeset * fix: peer deps * feat: toast api improved * chore: toast styles improved * fix: toast styles * chore: toast width style changed * fix: changeset release * fix: changeset peerdeps * chore: toast styles improved * refactor(pagination): rtl (#4843) * refactor(pagination): rtl * chore(changeset): add changeset * feat: new spinner variant * fix(docs): popover shouldBlockScroll default value (#4851) * fix(select): select scroll content will close immediately when popover on click (#4849) * chore(select): update select deps * fix(select): select scroll content will close immediately when popover on click * chore(select): add .changeset file * chore(changeset): add issue number --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> * feat(calendar): add firstDayOfWeek (#4852) * feat(calendar): add firstDayOfWeek * feat(docs): add firstDayOfWeek in Calendar docs * feat(calendar): add firstDayOfWeek to range calendar * feat(docs): add firstDayOfWeek to API table * feat: add firstDayOfWeek to date picker & date range picker * feat(docs): add firstDayOfWeek * feat(changeset): add changeset * feat: add firstDayOfWeek option in storybook * feat(docs): export firstDayOfWeek * chore(docs): update title * chore: spinner variants updated * feat: v2.7.0 blog * ci(changesets): version packages (#4601) Co-authored-by: Junior Garcia <jrgarciadev@gmail.com> * chore: manual release --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> Co-authored-by: millmason <jmsoper@protonmail.com> Co-authored-by: winches <329487092@qq.com> Co-authored-by: Maharshi Alpesh <maharshialpesh@gmail.com> Co-authored-by: Peterl561 <76144929+Peterl561@users.noreply.github.com> Co-authored-by: Paul Ebose <49006567+plbstl@users.noreply.github.com> Co-authored-by: Zarin <thesharifi.maruf@gmail.com> Co-authored-by: Shrinidhi Upadhyaya <shrinidhiupadhyaya1195@gmail.com> Co-authored-by: Avan <layouwen@gmail.com> Co-authored-by: Vincentius Roger Kuswara <vincentiusrkuswara@gmail.com> Co-authored-by: Ryo Matsukawa <76232929+ryo-manba@users.noreply.github.com> Co-authored-by: Praharsh Bhatt <30700808+praharshbhatt@users.noreply.github.com> Co-authored-by: Adrian Szarapow <63786007+Adee1499@users.noreply.github.com> Co-authored-by: Hova25 <75216176+Hova25@users.noreply.github.com> Co-authored-by: Hovannes Markarian <hovannes.markarian@socrate.fr> Co-authored-by: Tsuki <76603360+sudongyuer@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
7a95c7fda9
commit
cfea6f02b0
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 💡 Feature Request
|
||||
url: https://github.com/heroui-inc/heroui/discussions/categories/feature-requests
|
||||
about: 💡 Suggest a new component, improve an existing component and etc
|
||||
- name: 🤔 Long question or ideas?
|
||||
url: https://github.com/heroui-inc/heroui/discussions
|
||||
about: Ask long-form questions and discuss ideas.
|
||||
|
||||
49
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
49
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,49 +0,0 @@
|
||||
name: Feature request
|
||||
title: "[Feature Request] YOUR_FEATURE_TITLE_HERE_REPLACE_ME"
|
||||
labels: [feature request]
|
||||
description: |
|
||||
💡 Suggest an idea for the `HeroUI` project
|
||||
Examples
|
||||
- propose a new component
|
||||
- improve an exiting component
|
||||
- ....etc
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This issue form is for requesting features only! For example, requesting a new component, behavior ... etc
|
||||
If you want to report a bug, please use the [bug report form](https://github.com/heroui-inc/heroui/issues/new?assignees=&labels=&template=bug_report.yml).
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
placeholder: |
|
||||
As a user, I expected ___ behavior but ___ ...
|
||||
|
||||
Ideal Steps I would like to see:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. ....
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshots or Videos
|
||||
description: |
|
||||
If applicable, add screenshots or a video to help explain your problem.
|
||||
For more information on the supported file image/file types and the file size limits, please refer
|
||||
to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files
|
||||
placeholder: |
|
||||
You can drag your video or image files inside of this editor ↓
|
||||
17
.github/workflows/QA.yaml
vendored
17
.github/workflows/QA.yaml
vendored
@ -70,3 +70,20 @@ jobs:
|
||||
|
||||
- name: Run prettier
|
||||
run: pnpm format:check
|
||||
|
||||
continuous-release:
|
||||
name: Continuous Release
|
||||
if: github.repository == 'heroui-inc/heroui'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm build
|
||||
|
||||
- name: Release
|
||||
run: pnpx pkg-pr-new publish --compact --pnpm './packages/components/*' './packages/core/*' './packages/hooks/*' './packages/utilities/*'
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
Hello!, I am very excited that you are interested in contributing with HeroUI. However, before submitting your contribution, be sure to take a moment and read the following guidelines.
|
||||
|
||||
- [Code of Conduct](https://github.com/frontio-ai/heroui/blob/canary/CODE_OF_CONDUCT.md)
|
||||
- [Code of Conduct](https://github.com/heroui-inc/heroui/blob/canary/CODE_OF_CONDUCT.md)
|
||||
- [Extraction request guidelines](#pull-request-guidelines)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Tests](#tests)
|
||||
@ -79,7 +79,7 @@ https://www.conventionalcommits.org/ or check out the
|
||||
commit type. `scope` is just a short id that describes the scope of work.
|
||||
|
||||
3. Make and commit your changes following the
|
||||
[commit convention](https://github.com/frontio-ai/heroui/blob/main/CONTRIBUTING.md#commit-convention).
|
||||
[commit convention](https://github.com/heroui-inc/heroui/blob/main/CONTRIBUTING.md#commit-convention).
|
||||
As you canary, you can run `pnpm build --filter=<module>` and
|
||||
`pnpm test packages/<module>/<pkg>` e.g. `pnpm build --filter=avatar & pnpm test packages/components/avatar` to make sure everything works as expected.
|
||||
|
||||
@ -235,7 +235,7 @@ best to be proactive in reaching out to those that are already helping out.
|
||||
GitHub by default does not publicly state that you are a member of the
|
||||
organization. Please feel free to change that setting for yourself so others
|
||||
will know who's helping out. That can be configured on the [organization
|
||||
list](https://github.com/orgs/frontio-ai/people) page.
|
||||
list](https://github.com/orgs/heroui-inc/people) page.
|
||||
|
||||
Being a maintainer is not an obligation. You can help when you have time and be
|
||||
less active when you don't. If you get a new job and get busy, that's alright.
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Next UI
|
||||
Copyright (c) 2020 Next UI Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Next UI
|
||||
Copyright (c) 2020 Next UI Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@ -59,42 +59,41 @@ export default function Page() {
|
||||
</NavbarItem>
|
||||
<DropdownMenu
|
||||
aria-label="ACME features"
|
||||
className="w-[340px]"
|
||||
itemClasses={{
|
||||
base: "gap-4",
|
||||
}}
|
||||
>
|
||||
<DropdownItem
|
||||
key="autoscaling"
|
||||
description="ACME scales apps to meet user demand, automagically, based on load."
|
||||
description="ACME scales apps based on demand and load"
|
||||
startContent={icons.scale}
|
||||
>
|
||||
Autoscaling
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key="usage_metrics"
|
||||
description="Real-time metrics to debug issues. Slow query added? We’ll show you exactly where."
|
||||
description="Real-time metrics to debug issues"
|
||||
startContent={icons.activity}
|
||||
>
|
||||
Usage Metrics
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key="production_ready"
|
||||
description="ACME runs on ACME, join us and others serving requests at web scale."
|
||||
description="ACME runs on ACME, join us at web scale"
|
||||
startContent={icons.flash}
|
||||
>
|
||||
Production Ready
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key="99_uptime"
|
||||
description="Applications stay on the grid with high availability and high uptime guarantees."
|
||||
description="High availability and uptime guarantees"
|
||||
startContent={icons.server}
|
||||
>
|
||||
+99% Uptime
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key="supreme_support"
|
||||
description="Overcome any challenge with a supporting team ready to respond."
|
||||
description="Support team ready to respond"
|
||||
startContent={icons.user}
|
||||
>
|
||||
+Supreme Support
|
||||
|
||||
@ -430,7 +430,7 @@ export default function HeroUIPerf() {
|
||||
autoCorrect="off"
|
||||
className="z-10 sticky top-1"
|
||||
placeholder="Search..."
|
||||
spellCheck={false}
|
||||
spellCheck="false"
|
||||
startContent={<SearchLinearIcon className="text-default-400" size={18} strokeWidth="2" />}
|
||||
type="text"
|
||||
onValueChange={setInputValue}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {HeroUIProvider} from "@heroui/react";
|
||||
import {HeroUIProvider, ToastProvider} from "@heroui/react";
|
||||
import {ThemeProvider as NextThemesProvider} from "next-themes";
|
||||
import {ThemeProviderProps} from "next-themes";
|
||||
import {useRouter} from "next/navigation";
|
||||
@ -44,6 +44,7 @@ export function Providers({children, themeProps}: ProvidersProps) {
|
||||
return (
|
||||
<ProviderWrapper>
|
||||
<HeroUIProvider navigate={router.push}>
|
||||
<ToastProvider />
|
||||
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
||||
</HeroUIProvider>
|
||||
</ProviderWrapper>
|
||||
|
||||
9
apps/docs/app/themes/page.tsx
Normal file
9
apps/docs/app/themes/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
39
apps/docs/components/icons/crop.tsx
Normal file
39
apps/docs/components/icons/crop.tsx
Normal 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>
|
||||
);
|
||||
19
apps/docs/components/icons/filters.tsx
Normal file
19
apps/docs/components/icons/filters.tsx
Normal 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>
|
||||
);
|
||||
@ -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";
|
||||
|
||||
25
apps/docs/components/icons/mirror-left.tsx
Normal file
25
apps/docs/components/icons/mirror-left.tsx
Normal 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>
|
||||
);
|
||||
19
apps/docs/components/icons/palette-round.tsx
Normal file
19
apps/docs/components/icons/palette-round.tsx
Normal 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>
|
||||
);
|
||||
58
apps/docs/components/icons/radial-blur.tsx
Normal file
58
apps/docs/components/icons/radial-blur.tsx
Normal 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>
|
||||
);
|
||||
21
apps/docs/components/icons/scaling.tsx
Normal file
21
apps/docs/components/icons/scaling.tsx
Normal 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>
|
||||
);
|
||||
23
apps/docs/components/icons/text-square.tsx
Normal file
23
apps/docs/components/icons/text-square.tsx
Normal 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>
|
||||
);
|
||||
@ -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 ",
|
||||
}}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -1,12 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import Script from "next/script";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export function ScriptProviders({ isKapaEnabled = true }: { isKapaEnabled?: boolean }) {
|
||||
const pathname = usePathname();
|
||||
const [isMounted, setIsMounted] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
function hideKapa() {
|
||||
const kapaElements = document.querySelectorAll('[id^="kapa-"]');
|
||||
const display = pathname === "/docs/components/toast" || pathname === "/blog/v2.7.0" ? "none" : "block";
|
||||
|
||||
|
||||
export function ScriptProviders({isKapaEnabled = true}: {isKapaEnabled?: boolean}) {
|
||||
if (!isKapaEnabled) return null;
|
||||
kapaElements.forEach((element) => (element as HTMLElement).style.display = display);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
hideKapa();
|
||||
}, 500);
|
||||
}, [pathname, isMounted]);
|
||||
|
||||
if (!isKapaEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
defer
|
||||
data-modal-disclaimer="This is a custom LLM for HeroUI with access to all developer docs (heroui.com/docs) and GitHub Issues and PRs (github.com/heroui-inc/heroui)."
|
||||
@ -18,6 +42,5 @@ export function ScriptProviders({isKapaEnabled = true}: {isKapaEnabled?: boolean
|
||||
src="https://widget.kapa.ai/kapa-widget.bundle.js"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
128
apps/docs/components/themes/components/color-picker.tsx
Normal file
128
apps/docs/components/themes/components/color-picker.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
20
apps/docs/components/themes/components/config-section.tsx
Normal file
20
apps/docs/components/themes/components/config-section.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
486
apps/docs/components/themes/components/configuration/index.tsx
Normal file
486
apps/docs/components/themes/components/configuration/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
21
apps/docs/components/themes/components/number-input.tsx
Normal file
21
apps/docs/components/themes/components/number-input.tsx
Normal 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} />;
|
||||
}
|
||||
75
apps/docs/components/themes/components/select-template.tsx
Normal file
75
apps/docs/components/themes/components/select-template.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
import {cn, Divider} from "@heroui/react";
|
||||
import Link from "next/link";
|
||||
import {Inter, Roboto, Outfit, Lora} from "next/font/google";
|
||||
import get from "lodash/get";
|
||||
import {useTheme} from "next-themes";
|
||||
import {readableColor} from "color2k";
|
||||
|
||||
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, config} = useThemeBuilder();
|
||||
const fontClass = font ? FONT_CONFIGS[font]?.className || "" : getFontClass(templateTheme);
|
||||
const {theme} = useTheme();
|
||||
|
||||
const defaultColor = get(config, `${theme}.layoutColor.background`);
|
||||
|
||||
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"
|
||||
style={{
|
||||
color: readableColor(defaultColor!),
|
||||
}}
|
||||
>
|
||||
{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 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>
|
||||
);
|
||||
}
|
||||
102
apps/docs/components/themes/components/showcase/avatar.tsx
Normal file
102
apps/docs/components/themes/components/showcase/avatar.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
125
apps/docs/components/themes/components/showcase/breadcrumbs.tsx
Normal file
125
apps/docs/components/themes/components/showcase/breadcrumbs.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
114
apps/docs/components/themes/components/showcase/button.tsx
Normal file
114
apps/docs/components/themes/components/showcase/button.tsx
Normal 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={clsx(className, "capitalize")}
|
||||
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>
|
||||
);
|
||||
};
|
||||
114
apps/docs/components/themes/components/showcase/checkbox.tsx
Normal file
114
apps/docs/components/themes/components/showcase/checkbox.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
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
|
||||
className="capitalize"
|
||||
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>
|
||||
);
|
||||
};
|
||||
128
apps/docs/components/themes/components/showcase/chip.tsx
Normal file
128
apps/docs/components/themes/components/showcase/chip.tsx
Normal 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={clsx(className, "capitalize")}
|
||||
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>
|
||||
);
|
||||
};
|
||||
78
apps/docs/components/themes/components/showcase/code.tsx
Normal file
78
apps/docs/components/themes/components/showcase/code.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
30
apps/docs/components/themes/components/showcase/index.tsx
Normal file
30
apps/docs/components/themes/components/showcase/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
125
apps/docs/components/themes/components/showcase/input.tsx
Normal file
125
apps/docs/components/themes/components/showcase/input.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
88
apps/docs/components/themes/components/showcase/popover.tsx
Normal file
88
apps/docs/components/themes/components/showcase/popover.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
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 className="capitalize" 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>
|
||||
);
|
||||
};
|
||||
96
apps/docs/components/themes/components/showcase/switch.tsx
Normal file
96
apps/docs/components/themes/components/showcase/switch.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
130
apps/docs/components/themes/components/showcase/tabs.tsx
Normal file
130
apps/docs/components/themes/components/showcase/tabs.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
111
apps/docs/components/themes/constants.ts
Normal file
111
apps/docs/components/themes/constants.ts
Normal 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,
|
||||
};
|
||||
229
apps/docs/components/themes/css-vars.ts
Normal file
229
apps/docs/components/themes/css-vars.ts
Normal file
@ -0,0 +1,229 @@
|
||||
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 configurationContainer = document.getElementById("configuration-container");
|
||||
|
||||
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);
|
||||
configurationContainer?.style.setProperty(`--heroui-${colorType}`, heroValue);
|
||||
} 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);
|
||||
configurationContainer?.style.setProperty(`--heroui-${colorType}-${key}`, heroValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 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)),
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
22
apps/docs/components/themes/index.tsx
Normal file
22
apps/docs/components/themes/index.tsx
Normal 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;
|
||||
}
|
||||
346
apps/docs/components/themes/provider.tsx
Normal file
346
apps/docs/components/themes/provider.tsx
Normal 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: "Inter",
|
||||
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;
|
||||
}
|
||||
56
apps/docs/components/themes/templates/coffee.ts
Normal file
56
apps/docs/components/themes/templates/coffee.ts
Normal 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,
|
||||
};
|
||||
56
apps/docs/components/themes/templates/elegant.ts
Normal file
56
apps/docs/components/themes/templates/elegant.ts
Normal 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,
|
||||
};
|
||||
54
apps/docs/components/themes/templates/emerald.ts
Normal file
54
apps/docs/components/themes/templates/emerald.ts
Normal 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,
|
||||
};
|
||||
8
apps/docs/components/themes/templates/heroui.ts
Normal file
8
apps/docs/components/themes/templates/heroui.ts
Normal 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,
|
||||
};
|
||||
18
apps/docs/components/themes/templates/index.ts
Normal file
18
apps/docs/components/themes/templates/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
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},
|
||||
// TODO: Improve colors
|
||||
// {label: "Retro", name: "retro", value: retro},
|
||||
{label: "Coffee", name: "coffee", value: coffee},
|
||||
{label: "Emerald", name: "emerald", value: emerald},
|
||||
];
|
||||
56
apps/docs/components/themes/templates/modern.ts
Normal file
56
apps/docs/components/themes/templates/modern.ts
Normal 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,
|
||||
};
|
||||
56
apps/docs/components/themes/templates/retro.ts
Normal file
56
apps/docs/components/themes/templates/retro.ts
Normal 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: "#ffcf77",
|
||||
},
|
||||
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,
|
||||
};
|
||||
129
apps/docs/components/themes/types.ts
Normal file
129
apps/docs/components/themes/types.ts
Normal 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";
|
||||
138
apps/docs/components/themes/utils/colors.ts
Normal file
138
apps/docs/components/themes/utils/colors.ts
Normal 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];
|
||||
}
|
||||
59
apps/docs/components/themes/utils/config.ts
Normal file
59
apps/docs/components/themes/utils/config.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
30
apps/docs/components/themes/utils/shared.ts
Normal file
30
apps/docs/components/themes/utils/shared.ts
Normal 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;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"stars": { "raw": 22380, "formatted": "22.4K" },
|
||||
"forks": 1580,
|
||||
"subscribers": 85,
|
||||
"openIssues": 428
|
||||
"stars": { "raw": 23060, "formatted": "23.1K" },
|
||||
"forks": 1638,
|
||||
"subscribers": 86,
|
||||
"openIssues": 268
|
||||
}
|
||||
|
||||
@ -40,21 +40,20 @@
|
||||
"key": "forms",
|
||||
"title": "Forms",
|
||||
"keywords": "forms, form validation, heroui",
|
||||
"path": "/docs/guide/forms.mdx"
|
||||
"path": "/docs/guide/forms.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "nextui-to-heroui",
|
||||
"title": "NextUI to HeroUI",
|
||||
"keywords": "migrate, nextui, hero, heroui",
|
||||
"path": "/docs/guide/nextui-to-heroui.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/guide/nextui-to-heroui.mdx"
|
||||
},
|
||||
{
|
||||
"key": "figma",
|
||||
"title": "Figma",
|
||||
"keywords": "figma, design, ui kit",
|
||||
"path": "/docs/guide/figma.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/guide/figma.mdx"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -92,8 +91,7 @@
|
||||
"key": "laravel",
|
||||
"title": "Laravel",
|
||||
"keywords": "laravel, nextui",
|
||||
"path": "/docs/frameworks/laravel.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/frameworks/laravel.mdx"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -111,8 +109,7 @@
|
||||
{
|
||||
"key": "layout",
|
||||
"title": "Layout",
|
||||
"path": "/docs/customization/layout.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/customization/layout.mdx"
|
||||
},
|
||||
{
|
||||
"key": "colors",
|
||||
@ -132,8 +129,7 @@
|
||||
{
|
||||
"key": "dark-mode",
|
||||
"title": "Dark mode",
|
||||
"path": "/docs/customization/dark-mode.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/customization/dark-mode.mdx"
|
||||
},
|
||||
{
|
||||
"key": "override-styles",
|
||||
@ -163,15 +159,13 @@
|
||||
"key": "autocomplete",
|
||||
"title": "Autocomplete",
|
||||
"keywords": "autocomplete, auto suggest, search, typeahead",
|
||||
"path": "/docs/components/autocomplete.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/autocomplete.mdx"
|
||||
},
|
||||
{
|
||||
"key": "alert",
|
||||
"title": "Alert",
|
||||
"keywords": "alert, notification, message",
|
||||
"path": "/docs/components/alert.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/alert.mdx"
|
||||
},
|
||||
{
|
||||
"key": "avatar",
|
||||
@ -195,8 +189,7 @@
|
||||
"key": "button",
|
||||
"title": "Button",
|
||||
"keywords": "button, interactive, action trigger, click events",
|
||||
"path": "/docs/components/button.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/button.mdx"
|
||||
},
|
||||
{
|
||||
"key": "calendar",
|
||||
@ -221,8 +214,7 @@
|
||||
"key": "checkbox-group",
|
||||
"title": "Checkbox Group",
|
||||
"keywords": "checkbox group, binary choice, selection control, toggle",
|
||||
"path": "/docs/components/checkbox-group.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/checkbox-group.mdx"
|
||||
},
|
||||
{
|
||||
"key": "chip",
|
||||
@ -276,15 +268,13 @@
|
||||
"key": "drawer",
|
||||
"title": "Drawer",
|
||||
"keywords": "drawer, panel, slide, overlay",
|
||||
"path": "/docs/components/drawer.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/drawer.mdx"
|
||||
},
|
||||
{
|
||||
"key": "form",
|
||||
"title": "Form",
|
||||
"keywords": "forms, form validation, heroui",
|
||||
"path": "/docs/components/form.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/form.mdx"
|
||||
},
|
||||
{
|
||||
"key": "image",
|
||||
@ -304,13 +294,14 @@
|
||||
"title": "Input OTP",
|
||||
"keywords": "input, otp, auth, verification code",
|
||||
"path": "/docs/components/input-otp.mdx",
|
||||
"newPost": true
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "kbd",
|
||||
"title": "Kbd",
|
||||
"keywords": "keyboard input, shortcut, keys, user input display",
|
||||
"path": "/docs/components/kbd.mdx"
|
||||
"path": "/docs/components/kbd.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "link",
|
||||
@ -322,15 +313,13 @@
|
||||
"key": "listbox",
|
||||
"title": "Listbox",
|
||||
"keywords": "listbox, selection, option list, multiple choice",
|
||||
"path": "/docs/components/listbox.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/listbox.mdx"
|
||||
},
|
||||
{
|
||||
"key": "modal",
|
||||
"title": "Modal",
|
||||
"keywords": "modal, dialog box, popup, overlay, content focus",
|
||||
"path": "/docs/components/modal.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/modal.mdx"
|
||||
},
|
||||
{
|
||||
"key": "navbar",
|
||||
@ -338,6 +327,13 @@
|
||||
"keywords": "navbar, navigation, top menu, website header",
|
||||
"path": "/docs/components/navbar.mdx"
|
||||
},
|
||||
{
|
||||
"key": "number-input",
|
||||
"title": "Number Input",
|
||||
"keywords": "input, numeric input, number input",
|
||||
"path": "/docs/components/number-input.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "pagination",
|
||||
"title": "Pagination",
|
||||
@ -379,8 +375,7 @@
|
||||
"key": "select",
|
||||
"title": "Select",
|
||||
"keywords": "select, selection, option list, multiple choice",
|
||||
"path": "/docs/components/select.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/select.mdx"
|
||||
},
|
||||
{
|
||||
"key": "skeleton",
|
||||
@ -410,40 +405,41 @@
|
||||
"key": "spinner",
|
||||
"title": "Spinner",
|
||||
"keywords": "spinner, loading, activity indicator, processing signal",
|
||||
"path": "/docs/components/spinner.mdx"
|
||||
"path": "/docs/components/spinner.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "switch",
|
||||
"title": "Switch",
|
||||
"keywords": "switch, toggle, binary input, on/off control",
|
||||
"path": "/docs/components/switch.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/switch.mdx"
|
||||
},
|
||||
{
|
||||
"key": "table",
|
||||
"title": "Table",
|
||||
"keywords": "table, data display, grid, spreadsheet",
|
||||
"path": "/docs/components/table.mdx"
|
||||
"path": "/docs/components/table.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "tabs",
|
||||
"title": "Tabs",
|
||||
"keywords": "tabs, section navigation, categorized content, tabbed interface",
|
||||
"path": "/docs/components/tabs.mdx"
|
||||
"path": "/docs/components/tabs.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "toast",
|
||||
"title": "Toast",
|
||||
"keywords": "toast, notification, message",
|
||||
"path": "/docs/components/toast.mdx",
|
||||
"comingSoon": true
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "textarea",
|
||||
"title": "Textarea",
|
||||
"keywords": "textarea, multi-line text input, large text field, form control",
|
||||
"path": "/docs/components/textarea.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/textarea.mdx"
|
||||
},
|
||||
{
|
||||
"key": "time-input",
|
||||
@ -475,8 +471,7 @@
|
||||
"key": "cli-api",
|
||||
"title": "HeroUI CLI",
|
||||
"keywords": "api references, heroui, api, cli",
|
||||
"path": "/docs/api-references/cli-api.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/api-references/cli-api.mdx"
|
||||
},
|
||||
{
|
||||
"key": "heroui-provider",
|
||||
@ -517,8 +512,7 @@
|
||||
"key": "figma",
|
||||
"title": "Figma",
|
||||
"keywords": "figma, heroui, design, ui kit",
|
||||
"path": "/docs/guide/figma.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/guide/figma.mdx"
|
||||
},
|
||||
{
|
||||
"key": "roadmap",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,7 @@ title: "Introducing HeroUI"
|
||||
description: "HeroUI is the new identity for NextUI, bringing the same beautiful components and features you love under a new name."
|
||||
date: "2025-01-16"
|
||||
image: "/blog/introducing-heroui.png"
|
||||
tags: ["nextui", "select", "listbox", "scroll-shadow", "multi-select"]
|
||||
tags: ["nextui", "heroui", "select", "listbox", "scroll-shadow", "multi-select"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
@ -85,7 +85,7 @@ Our 2025 roadmap is packed with exciting new features and components. Upcoming r
|
||||
As always, we are incredibly grateful for your continued support and contributions. There are many exciting milestones ahead, and we invite you to join us on this journey.
|
||||
|
||||
|
||||
Special thanks to our amazing team members who made this rebranding possible after a lot of hard work and dedication [@winchesHe](https://github.com/winchesHe),
|
||||
Special thanks to our amazing team members who made this rebranding possible after a lot of hard work and dedication [@winchesHe](https://github.com/winchesHe),
|
||||
[@wingkwong](https://github.com/wingkwong), [@Peterl561](https://github.com/Peterl561), [@macci001](https://github.com/macci001), [@vinroger](https://github.com/vinroger), [@ryo-manba](https://github.com/ryo-manba), [@tianenpang](https://github.com/tianenpang),
|
||||
[@andressul_](https://x.com/andressul_), [@danieloiteiro](https://dribbble.com/DanielOiteiro/about), [@jrgarciadev](https://x.com/jrgarciadev). 🚀
|
||||
|
||||
|
||||
377
apps/docs/content/blog/v2.7.0.mdx
Normal file
377
apps/docs/content/blog/v2.7.0.mdx
Normal file
@ -0,0 +1,377 @@
|
||||
---
|
||||
title: "HeroUI v2.7.0"
|
||||
description: "New Toast component, NumberInput, Theme Generator, and lots of bug fixes and improvements."
|
||||
date: "2025-02-19"
|
||||
image: "/blog/v2.7.0.jpg"
|
||||
tags: ["heroui", "v2.7.0", "release", "toast", "improvements", "theme"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://x.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
import spinnerVariantsContent from "@/content/components/spinner/variants.ts";
|
||||
import toastUsage from "@/content/components/toast/usage.ts";
|
||||
import simpleToast from "@/content/components/toast/simple.ts";
|
||||
import numberInputContent from "@/content/components/number-input/description.ts";
|
||||
import tableVirtualizationContent from "@/content/components/table/virtualization.ts";
|
||||
|
||||
|
||||
<img
|
||||
src="/blog/v2.7.0_2x.jpg"
|
||||
width={700}
|
||||
height={350}
|
||||
alt="HeroUI v2.7.0"
|
||||
className="w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg"
|
||||
/>
|
||||
|
||||
HeroUI version **v2.7.0** introduces the highly anticipated Toast component, along with exciting new features including NumberInput component, Theme Generator, new Spinner variants, Table virtualization support, and numerous improvements and bug fixes.
|
||||
|
||||
## What's New in v2.7.0?
|
||||
|
||||
- [Toast Component](#toast-component) - A new toast notification system with rich features
|
||||
- [NumberInput Component](#numberinput-component) - A new input component specifically designed for numerical values
|
||||
- [New Spinner Variants](#new-spinner-variants) - Enhanced spinner component with new design variants
|
||||
- [Theme Generator](#theme-generator) - A powerful web-based tool for creating and customizing your themes
|
||||
- [Table Virtualization](#table-virtualization) - Performance improvements for large datasets in Table component
|
||||
- [New Global Props](#new-global-props) - New global configuration options for label placement and spinner variant
|
||||
- [Keyboard Support](#keyboard-support) - Enhanced keyboard support with fn, win, and alt keys
|
||||
- [Type Improvements](#type-improvements) - Better TypeScript support and exported types
|
||||
- [What's Next?](#whats-next) - Upcoming features and improvements
|
||||
- [Breaking Changes](#breaking-changes) - Important changes that may affect existing implementations
|
||||
- [Release Changes](#release-changes) - Detailed list of features, documentation updates, bug fixes and enhancements
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
**Upgrade today by using one of the following methods**:
|
||||
|
||||
1. Upgrading HeroUI using the `cli`
|
||||
|
||||
<PackageManagers
|
||||
commands={{
|
||||
cli: "heroui upgrade --all",
|
||||
npm: "npx heroui-cli@latest upgrade --all",
|
||||
}}
|
||||
/>
|
||||
|
||||
2. Upgrading HeroUI using package managers
|
||||
|
||||
<PackageManagers
|
||||
commands={{
|
||||
npm: "npm install @heroui/react@latest",
|
||||
pnpm: "pnpm add @heroui/react@latest",
|
||||
yarn: "yarn add @heroui/react@latest",
|
||||
bun: "bun add @heroui/react@latest",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Toast Component
|
||||
|
||||
The new Toast component provides a flexible and accessible way to show temporary notifications in your application. It comes with built-in support for different placements, variants, and animations. Inspired by [Sonner](https://sonner.emilkowal.ski/), our Toast component brings a beautiful, minimal, and customizable notification system to HeroUI.
|
||||
|
||||
### Setup
|
||||
|
||||
First, add the `ToastProvider` to your application:
|
||||
|
||||
```tsx
|
||||
// app/providers.tsx
|
||||
import {HeroUIProvider, ToastProvider} from '@heroui/react'
|
||||
// for individual components use the following import
|
||||
// import {ToastProvider} from '@heroui/toast'
|
||||
|
||||
|
||||
export default function Providers({children}) {
|
||||
return (
|
||||
<HeroUIProvider>
|
||||
<ToastProvider />
|
||||
{children}
|
||||
</HeroUIProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tsx
|
||||
import {Button, addToast} from "@heroui/react";
|
||||
|
||||
function Example() {
|
||||
return (
|
||||
<Button
|
||||
onPress={() => {
|
||||
addToast({
|
||||
title: "Success",
|
||||
description: "Your changes have been saved successfully.",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Show Toast
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<CodeDemo
|
||||
title="Simple Usage"
|
||||
files={simpleToast}
|
||||
/>
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
- Multiple placement options (top, bottom, left, right, center)
|
||||
- Different variants (solid, bordered, flat)
|
||||
- Custom timeout duration
|
||||
- Progress indicator
|
||||
- Promise support for loading states
|
||||
- Customizable icons and styling
|
||||
- Accessibility built-in
|
||||
|
||||
<CodeDemo title="Toast Examples" files={toastUsage} />
|
||||
|
||||
For more examples and detailed documentation about the Toast component, visit our [Toast documentation](/docs/components/toast).
|
||||
|
||||
## NumberInput Component
|
||||
|
||||
A new specialized input component for numerical values with built-in validation, formatting, and keyboard controls.
|
||||
|
||||
<CodeDemo title="Number Input Usage" files={numberInputContent} />
|
||||
|
||||
For more examples and detailed documentation about the NumberInput component, visit our [NumberInput documentation](/docs/components/number-input).
|
||||
|
||||
## New Spinner Variants
|
||||
|
||||
The Spinner component now includes new design variants, offering more options for loading states in your applications.
|
||||
|
||||
<CodeDemo title="New Spinner Variants" files={spinnerVariantsContent} />
|
||||
|
||||
For more examples and detailed documentation about the Spinner component, visit our [Spinner documentation](/docs/components/spinner).
|
||||
|
||||
## Theme Generator
|
||||
|
||||
The new Theme Generator is a powerful web-based tool that allows you to create and customize your themes visually. Simply visit our [Theme Generator](/themes) to:
|
||||
|
||||
- Create custom color schemes
|
||||
- Preview components with your theme in real-time
|
||||
- Generate the theme code automatically
|
||||
- Export your theme configuration
|
||||
- Test different color combinations
|
||||
- Ensure proper contrast and accessibility
|
||||
|
||||
This tool makes it easier than ever to maintain consistent design across your application without having to write theme configuration manually.
|
||||
|
||||
For more information about theming and customization, visit our [Theme documentation](/docs/customization/customize-theme).
|
||||
|
||||
## Table Virtualization
|
||||
|
||||
The Table component now supports virtualization, significantly improving performance when working with large datasets.
|
||||
|
||||
```tsx
|
||||
import {Table} from "@heroui/react";
|
||||
|
||||
function Example() {
|
||||
return (
|
||||
<Table
|
||||
isVirtualized
|
||||
itemHeight={50} // Required for virtualization
|
||||
>
|
||||
{/* Table content */}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<CodeDemo title="Table Virtualization" files={tableVirtualizationContent} />
|
||||
|
||||
For more examples and detailed documentation about the Table component, visit our [Table documentation](/docs/components/table).
|
||||
|
||||
## New Global Props
|
||||
|
||||
We've added new global configuration options to the `HeroUIProvider` that allow you to set default behaviors across your application:
|
||||
|
||||
#### Label Placement
|
||||
|
||||
You can now set a global default for label placement across all form-based components. This affects components that have the `labelPlacement` property, including:
|
||||
|
||||
- Input
|
||||
- Select
|
||||
- Autocomplete
|
||||
- DatePicker
|
||||
- DateRangePicker
|
||||
- TimeInput
|
||||
- NumberInput
|
||||
- And more...
|
||||
|
||||
```tsx
|
||||
import {HeroUIProvider} from "@heroui/react";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<HeroUIProvider labelPlacement="outside">
|
||||
{/* All form components will have outside labels by default */}
|
||||
</HeroUIProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Spinner Variant
|
||||
|
||||
You can set a global default spinner variant that will be used by all components that show loading states, including:
|
||||
|
||||
- Select
|
||||
- Autocomplete
|
||||
- Button
|
||||
- And other components that use loading indicators
|
||||
|
||||
```tsx
|
||||
import {HeroUIProvider} from "@heroui/react";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<HeroUIProvider spinnerVariant="simple">
|
||||
{/* All components will use the dots spinner variant by default */}
|
||||
</HeroUIProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can combine multiple global props to maintain consistent behavior throughout your application:
|
||||
|
||||
```tsx
|
||||
import {HeroUIProvider} from "@heroui/react";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<HeroUIProvider
|
||||
labelPlacement="outside"
|
||||
spinnerVariant="simple"
|
||||
>
|
||||
{/* Your app content */}
|
||||
</HeroUIProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
For more information about global configuration options, visit our [Provider documentation](/docs/api-references/heroui-provider).
|
||||
|
||||
## Keyboard Support
|
||||
|
||||
Enhanced keyboard support with the addition of `fn`, `win`, and `alt` keys for better accessibility and user interaction.
|
||||
|
||||
## Type Improvements
|
||||
|
||||
Better TypeScript support with exported `PressEvent` type for `onPress` event handling:
|
||||
|
||||
```tsx
|
||||
import type {PressEvent} from "@heroui/react";
|
||||
|
||||
function handlePress(e: PressEvent) {
|
||||
// Your press event handler
|
||||
}
|
||||
```
|
||||
|
||||
## What's Next?
|
||||
|
||||
We're actively working on **Tailwind CSS v4** support! You can check out our progress at [tv4.heroui.com](https://tv4.heroui.com). We'll be releasing a beta version soon ([PR #4656](https://github.com/heroui-inc/heroui/pull/4656)).
|
||||
|
||||
We're building an exciting new application that will revolutionize frontend development with HeroUI, making your workflow smoother than ever. Stay tuned for updates! 🔥
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- Renamed `wrapper` slot to `tabWrapper` in Tabs component
|
||||
- Deprecated `dateInputClassNames` in favor of new styling approach
|
||||
- Replaced directional terms `left` & `right` with `start` & `end` for better RTL support
|
||||
|
||||
## Release Changes
|
||||
|
||||
<Spacer y={1} />
|
||||
|
||||
### Features 🚀
|
||||
|
||||
- Added Toast component with rich features and customization options by [@macci001](https://github.com/macci001) in [PR #4437](https://github.com/heroui-inc/heroui/pull/4437)
|
||||
- Added NumberInput component by [@wingkwong](https://github.com/wingkwong) in [#4475](https://github.com/heroui-inc/heroui/pull/4475)
|
||||
- Added `fn`, `win`, and `alt` keys by [@winchesHe](https://github.com/winchesHe) in [PR #4638](https://github.com/heroui-inc/heroui/pull/4638)
|
||||
- Added global `labelPlacement` prop by [@macci001](https://github.com/macci001) in [PR #4346](https://github.com/heroui-inc/heroui/pull/4346)
|
||||
- Added new Spinner variants by [@Peter561](https://github.com/Peter561) in [#4555](https://github.com/heroui-inc/heroui/pull/4555)
|
||||
- Added Virtualization to Table by [@vinroger](https://github.com/vinroger) in [#4285](https://github.com/heroui-inc/heroui/pull/4285)
|
||||
- Added Theme Generator by [@macci001](https://github.com/macci001) in [PR #4626](https://github.com/heroui-inc/heroui/pull/4626)
|
||||
- Exported PressEvent for onPress event typing by [@ryo-manba](https://github.com/ryo-manba) in [PR #4819](https://github.com/heroui-inc/heroui/pull/4819)
|
||||
|
||||
### Documentation 📘
|
||||
|
||||
- Fixed custom implementation preview for checkbox & checkbox-group by [@wingkwong](https://github.com/wingkwong) in [#4610](https://github.com/heroui-inc/heroui/pull/4610)
|
||||
- Fixed small typos and added clarifying language in Modal by [@millmason](https://github.com/millmason) in [#4629](https://github.com/heroui-inc/heroui/pull/4629)
|
||||
- Fixed Tab usage example by [@ryo-manba](https://github.com/ryo-manba) in [PR #4821](https://github.com/heroui-inc/heroui/pull/4821)
|
||||
- Fixed horizontal scrolling example in scroll-shadow by [@ryo-manba](https://github.com/ryo-manba) in [PR #4820](https://github.com/heroui-inc/heroui/pull/4820)
|
||||
- Added note about itemHeight for virtualization by [@ryo-manba](https://github.com/ryo-manba) in [PR #4822](https://github.com/heroui-inc/heroui/pull/4822)
|
||||
- Removed dropdown menu width by [@wingkwong](https://github.com/wingkwong) in [#4757](https://github.com/heroui-inc/heroui/pull/4757)
|
||||
- Added TypeScript examples to show Selection type usage by [@wingkwong](https://github.com/wingkwong) in [#4793](https://github.com/heroui-inc/heroui/pull/4793)
|
||||
|
||||
### Bug Fixes 🐛
|
||||
|
||||
- Fixed missing shadow none by [@wingkwong](https://github.com/wingkwong) in [#4587](https://github.com/heroui-inc/heroui/pull/4587)
|
||||
- Fixed function components cannot be given refs by [@wingkwong](https://github.com/wingkwong) in [#4614](https://github.com/heroui-inc/heroui/pull/4614)
|
||||
- Fixed image loading after props change by [@wingkwong](https://github.com/wingkwong) in [#4523](https://github.com/heroui-inc/heroui/pull/4523)
|
||||
- Fixed unexpected scrollShadow on virtualized listbox by [@wingkwong](https://github.com/wingkwong) in [#4784](https://github.com/heroui-inc/heroui/pull/4784)
|
||||
- Fixed missing clear button with file input type by [@wingkwong](https://github.com/wingkwong) in [#4599](https://github.com/heroui-inc/heroui/pull/4599)
|
||||
- Fixed labelPlacement in Select by [@wingkwong](https://github.com/wingkwong) in [#4597](https://github.com/heroui-inc/heroui/pull/4597)
|
||||
- Fixed deprecation warning triggered by internal onClick by [@wingkwong](https://github.com/wingkwong) in [#4557](https://github.com/heroui-inc/heroui/pull/4557)
|
||||
- Fixed controlled page after delay in Pagination by [@wingkwong](https://github.com/wingkwong) in [#4536](https://github.com/heroui-inc/heroui/pull/4536)
|
||||
- Fixed accessing element.ref was removed in React 19 issue by [@wingkwong](https://github.com/wingkwong) in [#4531](https://github.com/heroui-inc/heroui/pull/4531)
|
||||
- Fixed missing press events to usePress by [@wingkwong](https://github.com/wingkwong) in [#4812](https://github.com/heroui-inc/heroui/pull/4812)
|
||||
- Fixed stroke in CheckboxIcon by [@wingkwong](https://github.com/wingkwong) in [#4811](https://github.com/heroui-inc/heroui/pull/4811)
|
||||
- Fixed portalContainer error on NavbarMenu by [@Peter561](https://github.com/Peter561) in [#4506](https://github.com/heroui-inc/heroui/pull/4506)
|
||||
- Fixed default validation behaviour in Form by [@Peter561](https://github.com/Peter561) in [#4425](https://github.com/heroui-inc/heroui/pull/4425)
|
||||
- Fixed RTL navigation in Calendar by [@MarufSharifi](https://github.com/MarufSharifi) in [PR #4565](https://github.com/heroui-inc/heroui/pull/4565)
|
||||
- Fixed inert value in next15 by [@winchesHe](https://github.com/winchesHe) in [PR #4491](https://github.com/heroui-inc/heroui/pull/4491)
|
||||
- Fixed dismissable default value by [@winchesHe](https://github.com/winchesHe) in [PR #4524](https://github.com/heroui-inc/heroui/pull/4524)
|
||||
- Fixed input height in innerWrapper in Select by [@ShrinidhiUpadhyaya](https://github.com/ShrinidhiUpadhyaya) in [PR #4512](https://github.com/heroui-inc/heroui/pull/4512)
|
||||
- Fixed SelectItem, ListboxItem, and AutocompleteItem not to accept value props by [@ryo-manba](https://github.com/ryo-manba) in [PR #4653](https://github.com/heroui-inc/heroui/pull/4653)
|
||||
- Fixed border radius in Table when isMultiSelectable by [@Adee1499](https://github.com/Adee1499) in [PR #4808](https://github.com/heroui-inc/heroui/pull/4808)
|
||||
|
||||
### Enhancements ✨
|
||||
|
||||
- Upgraded Tailwind Variants by [@jrgarciadev](https://github.com/jrgarciadev) in [PR #4386](https://github.com/heroui-inc/heroui/pull/4386)
|
||||
- Renamed `wrapper` slot to `tabWrapper` by [@winchesHe](https://github.com/winchesHe) in [PR #4636](https://github.com/heroui-inc/heroui/pull/4636)
|
||||
- Removed unnecessary className passing to tv and made naming consistent by [@wingkwong](https://github.com/wingkwong) in [#4558](https://github.com/heroui-inc/heroui/pull/4558)
|
||||
- Removed cursor-hit in hiddenInputClasses by [@Layouwen](https://github.com/Layouwen) in [#4474](https://github.com/heroui-inc/heroui/pull/4474)
|
||||
- Removed unnecessary `shouldBlockScroll` prop in Tooltip by [@wingkwong](https://github.com/wingkwong) in [#4539](https://github.com/heroui-inc/heroui/pull/4539)
|
||||
- Replaced left & right by start & end to support RTL by [@wingkwong](https://github.com/wingkwong) in [#4782](https://github.com/heroui-inc/heroui/pull/4782)
|
||||
|
||||
### Chore ⚙️
|
||||
|
||||
- Added `pkg-pr-new` by [@winchesHe](https://github.com/winchesHe) in [PR #4540](https://github.com/heroui-inc/heroui/pull/4540)
|
||||
- Added Input interaction tests by [@Peter561](https://github.com/Peter561) in [#4579](https://github.com/heroui-inc/heroui/pull/4579)
|
||||
- Added data-slot attributes to Accordion by [@Hova25](https://github.com/Hova25) in [#4832](https://github.com/heroui-inc/heroui/pull/4832)
|
||||
- Removed feature request from issue template (moved to Discussion) by [@wingkwong](https://github.com/wingkwong) in [#4661](https://github.com/heroui-inc/heroui/pull/4661)
|
||||
- Removed Kapa AI in Toast page by [@macci001](https://github.com/macci001) in [PR #4833](https://github.com/heroui-inc/heroui/pull/4833)
|
||||
- Deprecated dateInputClassNames by [@wingkwong](https://github.com/wingkwong) in [#4780](https://github.com/heroui-inc/heroui/pull/4780)
|
||||
- Rebranding by [@jrgarciadev](https://github.com/jrgarciadev), [@macci001](https://github.com/macci001), [@plbstl](https://github.com/plbstl) in [PR #4594](https://github.com/heroui-inc/heroui/pull/4594), [PR #4620](https://github.com/heroui-inc/heroui/pull/4620), [PR #4645](https://github.com/heroui-inc/heroui/pull/4645)
|
||||
- Updated author in package.json by [@wingkwong](https://github.com/wingkwong) in [#4800](https://github.com/heroui-inc/heroui/pull/4800)
|
||||
|
||||
For more details about this release, check out our [GitHub release page](https://github.com/heroui-inc/heroui/releases/tag/v2.7.0).
|
||||
|
||||
|
||||
Special thanks to HeroUI Team members [@wingkwong](https://github.com/wingkwong), [@macci001](https://github.com/macci001), [@vinroger](https://github.com/vinroger),
|
||||
[@ryo-manba](https://github.com/ryo-manba), [@winchesHe](https://github.com/winchesHe), [@tianenpang](https://github.com/tianenpang) and contributors for their contributions to this release.
|
||||
|
||||
<Spacer y={6} />
|
||||
|
||||
Thanks for reading and happy coding! 🚀
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
We're excited to see the community adopt NextUI, raise issues, and provide feedback.
|
||||
Whether it's a feature request, bug report, or a project to showcase, please get involved!
|
||||
|
||||
<Community />
|
||||
|
||||
## Contributing
|
||||
|
||||
PR's on HeroUI are always welcome, please see our [contribution guidelines](https://github.com/heroui-inc/heroui/blob/main/CONTRIBUTING.md) to learn how you can contribute to this project.
|
||||
@ -0,0 +1,5 @@
|
||||
import {Calendar} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return <Calendar aria-label="Date (firstDayOfWeek)" firstDayOfWeek="mon" />;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./first-day-of-week.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -10,6 +10,7 @@ import invalidDate from "./invalid-date";
|
||||
import withMonthAndYearPicker from "./with-month-and-year-picker";
|
||||
import internationalCalendars from "./international-calendars";
|
||||
import visibleMonths from "./visible-months";
|
||||
import firstDayOfWeek from "./first-day-of-week";
|
||||
import pageBehaviour from "./page-behaviour";
|
||||
import presets from "./presets";
|
||||
|
||||
@ -26,6 +27,7 @@ export const calendarContent = {
|
||||
withMonthAndYearPicker,
|
||||
internationalCalendars,
|
||||
visibleMonths,
|
||||
firstDayOfWeek,
|
||||
pageBehaviour,
|
||||
presets,
|
||||
};
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import {DatePicker} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
||||
<DatePicker
|
||||
className="max-w-[284px]"
|
||||
description={"This is my birth date."}
|
||||
firstDayOfWeek="mon"
|
||||
label="Birth date"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./first-day-of-week.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -18,6 +18,7 @@ import minAndMaxDate from "./min-and-max-date";
|
||||
import internationalCalendar from "./international-calendar";
|
||||
import unavailableDates from "./unavailable-dates";
|
||||
import visibleMonth from "./visible-month";
|
||||
import firstDayOfWeek from "./first-day-of-week";
|
||||
import pageBehavior from "./page-behavior";
|
||||
import preset from "./preset";
|
||||
|
||||
@ -42,6 +43,7 @@ export const datePickerContent = {
|
||||
internationalCalendar,
|
||||
unavailableDates,
|
||||
visibleMonth,
|
||||
firstDayOfWeek,
|
||||
pageBehavior,
|
||||
preset,
|
||||
};
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import {DateRangePicker} from "@heroui/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<DateRangePicker
|
||||
className="max-w-xs"
|
||||
description="Please enter your stay duration"
|
||||
firstDayOfWeek="mon"
|
||||
label="Stay duration"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./first-day-of-week.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -17,6 +17,7 @@ import minAndMaxDate from "./min-and-max-date";
|
||||
import internationalCalendar from "./international-calendar";
|
||||
import unavailableDates from "./unavailable-dates";
|
||||
import visibleMonth from "./visible-month";
|
||||
import firstDayOfWeek from "./first-day-of-week";
|
||||
import pageBehavior from "./page-behavior";
|
||||
import nonContigous from "./non-contiguous";
|
||||
import presets from "./presets";
|
||||
@ -43,6 +44,7 @@ export const dateRangePickerContent = {
|
||||
internationalCalendar,
|
||||
unavailableDates,
|
||||
visibleMonth,
|
||||
firstDayOfWeek,
|
||||
pageBehavior,
|
||||
nonContigous,
|
||||
presets,
|
||||
|
||||
@ -13,7 +13,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
errorMessage="Please enter a valid email"
|
||||
|
||||
@ -6,7 +6,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
errorMessage={({validationDetails}) => {
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import {Form, Input, Button} from "@heroui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="aria" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
label="Username"
|
||||
labelPlacement="outside"
|
||||
name="username"
|
||||
placeholder="Enter your username"
|
||||
type="text"
|
||||
validate={(value) => {
|
||||
if (value.length < 3) {
|
||||
return "Username must be at least 3 characters long";
|
||||
}
|
||||
|
||||
return value === "admin" ? "Nice try!" : null;
|
||||
}}
|
||||
/>
|
||||
<Button type="submit" variant="bordered">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import App from "./custom-validation-aria.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@ -6,7 +6,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
label="Username"
|
||||
|
||||
@ -59,7 +59,6 @@ export default function App() {
|
||||
return (
|
||||
<Form
|
||||
className="w-full justify-center items-center space-y-4"
|
||||
validationBehavior="native"
|
||||
validationErrors={errors}
|
||||
onReset={() => setSubmitted(null)}
|
||||
onSubmit={onSubmit}
|
||||
@ -117,21 +116,11 @@ export default function App() {
|
||||
name="country"
|
||||
placeholder="Select country"
|
||||
>
|
||||
<SelectItem key="ar" value="ar">
|
||||
Argentina
|
||||
</SelectItem>
|
||||
<SelectItem key="us" value="us">
|
||||
United States
|
||||
</SelectItem>
|
||||
<SelectItem key="ca" value="ca">
|
||||
Canada
|
||||
</SelectItem>
|
||||
<SelectItem key="uk" value="uk">
|
||||
United Kingdom
|
||||
</SelectItem>
|
||||
<SelectItem key="au" value="au">
|
||||
Australia
|
||||
</SelectItem>
|
||||
<SelectItem key="ar">Argentina</SelectItem>
|
||||
<SelectItem key="us">United States</SelectItem>
|
||||
<SelectItem key="ca">Canada</SelectItem>
|
||||
<SelectItem key="uk">United Kingdom</SelectItem>
|
||||
<SelectItem key="au">Australia</SelectItem>
|
||||
</Select>
|
||||
|
||||
<Checkbox
|
||||
|
||||
@ -6,7 +6,6 @@ export default function App() {
|
||||
return (
|
||||
<Form
|
||||
className="w-full max-w-xs flex flex-col gap-4"
|
||||
validationBehavior="native"
|
||||
onReset={() => setAction("reset")}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@ -12,7 +12,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
errorMessage="Please enter a valid email"
|
||||
|
||||
@ -4,6 +4,7 @@ import controlled from "./controlled";
|
||||
import nativeValidation from "./native-validation";
|
||||
import customErrorMessages from "./custom-error-messages";
|
||||
import customValidation from "./custom-validation";
|
||||
import customValidationAria from "./custom-validation-aria";
|
||||
import realTimeValidation from "./real-time-validation";
|
||||
import serverValidation from "./server-validation";
|
||||
import events from "./events";
|
||||
@ -15,6 +16,7 @@ export const formContent = {
|
||||
nativeValidation,
|
||||
customErrorMessages,
|
||||
customValidation,
|
||||
customValidationAria,
|
||||
realTimeValidation,
|
||||
serverValidation,
|
||||
events,
|
||||
|
||||
@ -6,7 +6,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
errorMessage="Please enter a valid email"
|
||||
|
||||
@ -12,7 +12,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
errorMessage="Please enter a valid email"
|
||||
|
||||
@ -34,3 +34,4 @@ export * from "./table";
|
||||
export * from "./autocomplete";
|
||||
export * from "./alert";
|
||||
export * from "./drawer";
|
||||
export * from "./number-input";
|
||||
|
||||
@ -6,7 +6,6 @@ export default function App() {
|
||||
return (
|
||||
<Form
|
||||
className="flex w-full flex-col items-start gap-4"
|
||||
validationBehavior="native"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
@ -21,7 +20,6 @@ export default function App() {
|
||||
length={4}
|
||||
name="otp"
|
||||
placeholder="Enter code"
|
||||
validationBehavior="native"
|
||||
/>
|
||||
<Button size="sm" type="submit" variant="bordered">
|
||||
Submit
|
||||
|
||||
@ -20,7 +20,6 @@ export default function App() {
|
||||
length={4}
|
||||
name="otp"
|
||||
placeholder="Enter code"
|
||||
validationBehavior="native"
|
||||
/>
|
||||
<Button size="sm" type="submit" variant="bordered">
|
||||
Submit
|
||||
|
||||
@ -11,7 +11,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
errorMessage={({validationDetails, validationErrors}) => {
|
||||
|
||||
@ -11,7 +11,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
label="Username"
|
||||
|
||||
@ -23,7 +23,7 @@ export default function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
|
||||
<Form className="w-full max-w-xs" onSubmit={onSubmit}>
|
||||
<Input
|
||||
errorMessage={() => (
|
||||
<ul>
|
||||
|
||||
@ -16,12 +16,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="w-full max-w-xs"
|
||||
validationBehavior="native"
|
||||
validationErrors={errors}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form className="w-full max-w-xs" validationErrors={errors} onSubmit={onSubmit}>
|
||||
<Input
|
||||
isRequired
|
||||
isDisabled={isLoading}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user