Compare commits

...

358 Commits

Author SHA1 Message Date
github-actions[bot] e3dabac9f5 ci(changesets): version packages (#1461) 2023-08-21 12:28:39 -03:00
Junior Garcia 4e94c11528 fix(dropdown): popover and dropdown fixes (#1458) 2023-08-21 12:07:28 -03:00
João Vitor a17b6c7502 docs(tooltip): decrease zIndex so it doesn't overlap other elements (#1454)
* fix(navbar): z-index issue on desktop

* Revert "fix(navbar): z-index issue on desktop"

This reverts commit 227b79e5c6493084ee31a62fd5b13079c8a4a2e4.

* docs(tooltip): decrease the zIndex
2023-08-21 14:29:03 +00:00
github-actions[bot] bd72301b01 ci(changesets): version packages (#1456)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-21 13:40:21 +00:00
Junior Garcia cc839cdd1f Fix/select label closes the popover (#1455)
* fix(select): label pointer events removed

* chore(select): changeset
2023-08-21 13:29:49 +00:00
Yannik Hewlik c9e5c2ef7f docs: correct default value for popover radius (#1451)
* Update popover.mdx

* Update apps/docs/content/docs/components/popover.mdx

Co-authored-by: Jakob Guddas <github@jguddas.de>

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
2023-08-21 13:22:50 +00:00
Junior Garcia e8f5264cf8 fix(blog): v2.1.0 date 2023-08-21 10:11:10 -03:00
Junior Garcia df7f773f3d fix(blog): async typo 2023-08-21 09:49:59 -03:00
github-actions[bot] af925d56d1 ci(changesets): version packages (#1453)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-21 12:41:43 +00:00
Junior Garcia baec55029d Feat/select component (#1313)
* chore(root): menu and select components in progress

* chore(root): merged with main

* feat(menu): stories created

* feat(dropdown): menu migrated to menu component

* feat(select): base implementation in progress

* feat(select): listbox implemented

* feat(select): scroll overflow and auto scroll added

* feat(select): colors & variants improved, inside placement complete, outside pending

* feat(scroll-shadow): new component in progress

* feat(select): scroll shadow integrated

* fix(select): popover implementation, more scroll shadow stories added

* feat(listbox): custom styles example added to stories

* chore(listbox): overflow hidden removed

* feat(select): variants and styles completed

* chore(select): description story added

* feat(select): more stories added

* fix(select): dynamic collections inference, styles improved, more stories added

* fix(select): auto scroll to selected item, scroll padding added

* chore(select): scroll behavior changed to nearest

* feat(select): custom item story added

* fix(select): autoscroll fixed

* feat(select): multi select support added

* feat(select): more examples added, clean-package modified to remove dev packages

* chore(modal): useImperativeHandle removed

* feat(select): render value story improved

* feat(docs): listbox & scroll shadow docs done

* feat(docs): select documentation in progress

* fix: select aria label (#1425)

* feat(docs): more examples added

* feat(docs): select multiple added, storybook favicon changed

* fix: added value to aria label of select (#1431)

* feat(select): more examples added, storybook favicon changed

* fix(popover): dialog role removed

* feat(select): api added, async loading exampled added

* fix: fixed list box on click not working with sr click (#1439)

* feat(select): async items loading support added and documented

* feat(root): input styles updated to be as the select ones

* chore(root): listbox and scroll shadow readme updated

* feat(select): ts examples added, banner updated

* fix(popover): voiceover closes when focusing an item fixed

* chore(select): focus scope removed

* fix(popover): free solo popover added to use without a trigger

* feat(select): blog post added

* chore(docs): search meta generated, multi controlled onchange example fixed

* chore(root): changeset added

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
2023-08-21 12:28:01 +00:00
Gaurav Pandey ea2868e58e updated doc handling hydration mismatch error (#1449)
Co-authored-by: btsb78 <gaurav.pandey@kratikal.com>
2023-08-20 20:42:01 +00:00
Junior Garcia 691f380dff fix(docs): client directive added to next app examples on dark mode page 2023-08-20 11:45:23 -03:00
Junior Garcia ac8afec964 fix(modal): docs issues 2023-08-19 18:33:51 -03:00
Loïc GREFFIER 5fe5521a7c fix(docs): replaced ghost by dot in chip variants (#1436) 2023-08-18 20:49:36 -03:00
github-actions[bot] 15403bc67e ci(changesets): version packages (#1435) 2023-08-18 18:16:55 -03:00
Junior Garcia c186f37c98 fix(accordion): hide indicator prop in accordion item (#1433) 2023-08-18 20:59:49 +00:00
github-actions[bot] 1bc2a50e2c ci(changesets): version packages (#1434)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-18 20:57:44 +00:00
Junior Garcia 453f792a7a fix(link): warning onClick removed (#1432) 2023-08-18 20:50:13 +00:00
Junior Garcia 3f6fd48eee fix(docs): modal mail icon imports (#1421) 2023-08-17 12:48:06 +00:00
github-actions[bot] d7c6c79f38 ci(changesets): version packages (#1414)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-15 20:35:07 -03:00
Junior Garcia f068552774 refactor(tooltip): style prop merged with the passed one (#1413) 2023-08-15 20:28:23 -03:00
Cesar Leonardo Insuasty Moreno 6d7372ede1 fix: Import CardFooter in the card component doc (#1412) 2023-08-15 18:52:16 -03:00
github-actions[bot] 1487219a1c ci(changesets): version packages (#1411)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-15 18:30:06 -03:00
Junior Garcia 6e7e834c2d fix(popover): allow pass custom z-index (#1410) 2023-08-15 18:22:08 -03:00
Victor Bejas 80a8f7e94c fix/add import CardFooter at Card component composition (#1408) 2023-08-15 16:19:39 -03:00
github-actions[bot] fae7467b64 ci(changesets): version packages (#1407)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-15 14:41:45 -03:00
Tommaso De Rossi 4e7789c1f6 fix(input): don't always use controlled mode (#1406)
* fix(input): don't always use controlled mode

* fix(input): don't pass value to useTextField

* test(input): added test for mutating input via ref

* chore(input): changelog
2023-08-15 14:24:55 -03:00
Junior Garcia 6694b934e6 fix(docs): accordion item isOpen prop removed 2023-08-14 10:06:07 -03:00
github-actions[bot] 4d341081e3 ci(changesets): version packages (#1393)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-13 22:07:15 -03:00
github-actions[bot] df562449fb ci(changesets): version packages (#1392)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-13 21:50:12 -03:00
Junior Garcia 4063362699 fix(popover): use layout effect on the server warning 2023-08-13 21:42:57 -03:00
Junior Garcia 94d65df533 fix(root): filter dom props function fixed, slot prop added (#1391)
* fix(root): filter dom props function fixed, slot prop added

* chore(root): changeset added
2023-08-13 21:41:42 -03:00
Junior Garcia 8f66c6b564 fix(docs): individual installation highlights 2023-08-13 15:32:22 -03:00
Junior Garcia 4a19a3c906 Merge branch 'main' of github.com:nextui-org/nextui 2023-08-13 15:25:03 -03:00
Junior Garcia 0be398ade9 fix(docs): individual components styles 2023-08-13 15:24:48 -03:00
github-actions[bot] 50d23e5378 ci(changesets): version packages (#1385)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-13 00:03:39 -03:00
Junior Garcia d0341020e6 fix(docs): navbar with search input example (#1384) 2023-08-12 23:53:22 -03:00
github-actions[bot] 4170ecdc76 ci(changesets): version packages (#1383)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-12 23:33:09 -03:00
Junior Garcia e2b0076b53 chore(root): version field moved to bug report template 2023-08-12 23:27:55 -03:00
Junior Garcia e2b0497b07 fix(root): checkbox and radio onChange event types fixed (#1381) 2023-08-12 23:26:07 -03:00
Junior Garcia df3b309542 Merge branch 'main' of github.com:nextui-org/nextui 2023-08-12 22:56:11 -03:00
Junior Garcia 6c35a502fe chore(root): feature request template updated, version added 2023-08-12 22:55:37 -03:00
github-actions[bot] a629f8c088 ci(changesets): version packages (#1378)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-12 11:40:59 -03:00
Junior Garcia fd4e01dcfa fix(dropdown): spread key warning (#1377) 2023-08-12 11:33:13 -03:00
github-actions[bot] e9c08abd55 ci(changesets): version packages (#1374)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-11 21:10:32 -03:00
Junior Garcia 3e263aa882 fix(table): layout property (#1372) 2023-08-11 20:58:57 -03:00
Junior Garcia 9add9aba97 chore(docs): current version added to navbar dropdown (#1373) 2023-08-11 20:48:00 -03:00
Tudor Comparan 0311ae43f1 individual instalation, tailwind setup, require correct nextui package (#1371) 2023-08-11 16:07:23 -03:00
github-actions[bot] 79c5fb9a94 ci(changesets): version packages (#1370)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-11 15:02:25 -03:00
Junior Garcia abe1ba58de Fix/input undefined value (#1369)
* fix(input): undefined value uncontrolled

* fix(input): undefined value uncontrolled
2023-08-11 14:54:29 -03:00
Jakob Guddas e6c3747153 chore: added custom title for release PRs (#1368) 2023-08-11 14:12:11 -03:00
github-actions[bot] d15b16ecab ci(changesets): version packages (#1366)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-11 12:20:17 -03:00
Junior Garcia fe03c42fa1 fix(tooltip): filter dom props function fixed (#1365)
* fix(tooltip): filter dom props function fixed

* fix(root): react types pkg updated
2023-08-11 12:06:47 -03:00
navedqb 25a918d146 Fixed incorrect import in card component (#1364) 2023-08-11 09:04:17 -03:00
github-actions[bot] 87918ac5b4 ci(changesets): version packages (#1360)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-10 23:47:40 -03:00
Junior Garcia a30cec4810 chore(root): react-aria packages upgraded (#1359) 2023-08-10 23:25:24 -03:00
github-actions[bot] 1b0653c97a ci(changesets): version packages (#1353)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-10 17:35:26 -03:00
Junior Garcia 710395f3a2 Refactor/remove needless exports system (#1350)
* fix(root): filter dom props func, accordion keep content mounted, navbaropen menu callbacks

* refactor(system): needeless exports removed

* refactor(system): needeless exports removed
2023-08-10 17:27:53 -03:00
github-actions[bot] e7e58e854a ci(changesets): version packages (#1352)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-10 17:19:51 -03:00
Junior Garcia d13a14facc Fix/events and props issues (#1351)
* fix(root): filter dom props func, accordion keep content mounted, navbaropen menu callbacks

* fix(docs): examples keys
2023-08-10 17:10:53 -03:00
Junior Garcia cef7235c4b fix(docs): compound variants typo 2023-08-10 08:54:38 -03:00
Junior Garcia fcff1a1d36 Merge branch 'main' of github.com:nextui-org/nextui 2023-08-09 23:39:57 -03:00
Junior Garcia c7ba9e1508 fix(docs): customize theme button example, color added 2023-08-09 23:39:44 -03:00
github-actions[bot] 1d8dc4b9d8 ci(changesets): version packages (#1339)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-09 21:51:25 -03:00
github-actions[bot] ea5e0fb096 ci(changesets): version packages (#1338)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-09 21:42:53 -03:00
Junior Garcia 26f64d043d fix(storybook): padding 2023-08-09 21:40:54 -03:00
Jakob Guddas a078c3dcd2 chore: storybook improvements (#1335) 2023-08-09 21:36:54 -03:00
Jakob Guddas 5a50390e3f fix: moved sr only modal close buttons to not show unnecessary scroll bar (#1333)
* fix: moved sr only modal close buttons to not show unnecessary scroll bar

* Create sour-lizards-allow.md
2023-08-09 21:36:14 -03:00
Tianen Pang 894d85d059 docs(table): use case example (#1326) 2023-08-08 14:43:40 -03:00
github-actions[bot] 0ef41d27e7 ci(changesets): version packages (#1324)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-08 12:28:30 -03:00
Tianen Pang 4200164712 fix(input): duplicate triggering of input events (#1323)
* feat(utils): support for omit-names

* fix(input): exclude duplicate event props

* fix(input): exclude duplicate event fn only

* test(input): event fn call count

* chore(changeset): changeset added
2023-08-08 12:19:08 -03:00
Tianen Pang 6d4c6980d5 fix(docs): typo in the loading more data section (#1319) 2023-08-08 09:10:10 -03:00
github-actions[bot] f3f36a7146 ci(changesets): version packages (#1316)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-08 01:04:01 -03:00
Junior Garcia 8de209bff7 feat(root): new changeset 2023-08-08 00:56:56 -03:00
Junior Garcia e9d01be74d chore(root): eslint and prettier ignore updated 2023-08-08 00:45:14 -03:00
Junior Garcia 66ac2e67ae feat(root): new changeset 2023-08-08 00:44:58 -03:00
Junior Garcia 7ed88f39c1 chore(pagination): fix prettier 2023-08-08 00:41:58 -03:00
vaynevayne 32d18bfd41 Update pagination-item.tsx (#1315) 2023-08-08 00:41:09 -03:00
vaynevayne a1b32c1ceb Update use-pagination.ts (#1314) 2023-08-08 00:39:01 -03:00
Junior Garcia 9e683b32ba Merge branch 'main' of github.com:nextui-org/nextui 2023-08-08 00:12:35 -03:00
Junior Garcia b7c947805d feat(storybook): logo adapted to light/dark theme, favicon added 2023-08-08 00:12:21 -03:00
github-actions[bot] a76fdfa774 ci(changesets): version packages (#1310)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-07 23:40:34 -03:00
Junior Garcia ac605eb71f Fix/1307 input on value change (#1309)
* fix(input): inner event handler implemented

* chore(root): changeset added
2023-08-07 23:31:16 -03:00
Junior Garcia 5b343b6181 chore(root): storybook static files removed 2023-08-06 15:56:24 -03:00
Junior Garcia 05562dcf90 feat(root): storybook v7 2023-08-06 15:41:25 -03:00
github-actions[bot] 0a60388258 ci(changesets): version packages (#1302)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-06 15:23:26 -03:00
Junior Garcia d794225cb7 Pr/1224 (#1301)
* chore: storybook migration

* chore: storybook migration

* chore: storybook migration

* fix: moved options in arg types out of control

* fix: fixed type issues

* fix: renamed sb:start script to start

* chore: migrate csf 2 to csf 3

* fix: removed storybook build from root build script

* chore: kick

* chore(root): pnpm lock updated

* chore(root): new changeset

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
2023-08-06 15:04:26 -03:00
Junior Garcia ec2ed200f5 fix(docs): navbar imports 2023-08-06 14:53:19 -03:00
Junior Garcia 2f7703ec8e fix(docs): navbar imports 2023-08-06 14:53:12 -03:00
Junior Garcia 25cbe1146f chore(root): qa action parallelized again 2023-08-06 12:06:57 -03:00
Junior Garcia 3db9b6248e chore(root): qa action parallelized again 2023-08-06 11:59:43 -03:00
github-actions[bot] b1b5eda15b ci(changesets): version packages (#1293)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-06 00:02:27 -03:00
Junior Garcia e3e13a095f chore(root): new changeset 2023-08-05 23:49:42 -03:00
Junior Garcia 86f921d098 Feat/server components (#1291)
* feat(root): rsc components added, packages modified, filter dom props function adapted

* fix(root): eslint/prettier issues

* feat(root): exports changed to named, new pkg for system rsc compatible functions
2023-08-05 23:35:28 -03:00
Junior Garcia eefda8d6e2 feat(root): RSC components added, packages modified, filter dom props function adapted (#1289)
* feat(root): rsc components added, packages modified, filter dom props function adapted

* fix(root): eslint/prettier issues
2023-08-05 18:53:34 -03:00
Junior Garcia 2e4ce7bcee chore(root): all qa processes joined into a single one 2023-08-05 18:40:08 -03:00
Junior Garcia 5a4c4f498d chore(root): build step added to tests QA 2023-08-05 18:34:17 -03:00
Junior Garcia ec30a9adbd refactor(root): sotybook build folder added to repo 2023-08-05 11:14:27 -03:00
Junior Garcia a6954c4156 Merge branch 'main' of github.com:nextui-org/nextui 2023-08-05 11:13:53 -03:00
Junior Garcia b887a2f503 refactor(root): sotybook build folder added to repo 2023-08-05 11:13:37 -03:00
github-actions[bot] edc2bc741e ci(changesets): version packages (#1284)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-05 10:54:54 -03:00
Tianen Pang a1a02dc0db fix(tooltip): behavior of tooltip trigger with delay (#1278)
* fix(tooltip): use trigger-props instead of custom on-pointer events

* chore(changeset): add tooltip changeset
2023-08-05 10:46:01 -03:00
github-actions[bot] cd1dc8b469 ci(changesets): version packages (#1277)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-04 23:31:33 -03:00
Junior Garcia 459ac5ed45 chore: new changeset w/o minor version on theme 2 2023-08-04 23:20:00 -03:00
Junior Garcia 9c4bb22e87 chore: new changeset w/o minor version on theme 2023-08-04 23:03:21 -03:00
Junior Garcia 7fa5459381 feat(root): new changeset 2023-08-04 22:45:21 -03:00
Junior Garcia b77b8fa9d7 fix(modal): content placement changed on mobile when outside scroll (#1275) 2023-08-04 22:37:01 -03:00
Junior Garcia c6c4a4d6a1 fix(dropdown): item keys taken out from the itemProps (#1274) 2023-08-04 22:35:49 -03:00
Junior Garcia e96b1dbab0 fix(docs): radio & checkbox custom styles (#1273) 2023-08-04 22:34:37 -03:00
Junior Garcia 1b55d27645 fix(docs): remove autoFocus on webkit (#1272) 2023-08-04 22:33:41 -03:00
Junior Garcia bdc246a8a4 fix(table): ts use-case example, page state added (#1271) 2023-08-04 22:32:53 -03:00
Tianen Pang 5064bfe4e4 fix(docs): navbar examples (#1269)
* fix(navbar): use button tag and correctly pass props with child

* chore(navbar): sync example and code snippet
2023-08-04 14:41:54 -03:00
brick.ninja 36ddbff557 fix: typo navbar.tsx (#1264) 2023-08-03 18:13:55 -03:00
Junior Garcia 043b42c895 chore(docs): ea perc increased 2023-08-03 09:54:44 -03:00
Junior Garcia c8330b8106 Merge branch 'main' of github.com:nextui-org/nextui 2023-08-03 09:52:30 -03:00
Junior Garcia 7f20346423 chore(docs): ea perc increased 2023-08-03 09:51:01 -03:00
Ikko Eltociear Ashimine 85b3fd7269 fix(docs): typo in dropdown.mdx (#1255)
TThe -> The
2023-08-03 09:45:55 -03:00
Junior Garcia 649204ceac fix(root): qa action 2023-08-03 09:45:40 -03:00
Junior Garcia c7a1376872 fix(docs): poster added to blog videos, play/pause button added 2023-08-02 23:50:38 -03:00
Junior Garcia 3323592211 fix(blog): s3 url replaced by cloudfront 2023-08-02 22:57:16 -03:00
Junior Garcia fb570a372d feat(root): qa github action added 2023-08-02 22:33:37 -03:00
Junior Garcia 184d3a9c49 fix(root): changelog commands removed 2023-08-01 22:51:16 -03:00
github-actions[bot] 849eff5715 ci(changesets): version packages (#1234)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-08-01 18:10:12 -03:00
Junior Garcia b400f3ee6d chore(input): readme changed 2023-08-01 17:54:44 -03:00
Junior Garcia dedede0be0 feat(root): new release pipeline 2023-08-01 17:38:47 -03:00
Junior Garcia e96a23c8a3 Merge branch 'main' of github.com:nextui-org/nextui 2023-08-01 17:25:40 -03:00
Kwesi Ansah 7efd6c97e4 fix: fixes tab submitting form on click (#1233) 2023-08-01 17:25:20 -03:00
Junior Garcia 43e5e4fb26 chore(docs): announcement chip alpha border 2023-08-01 17:09:32 -03:00
Tianen Pang 39da69cf81 fix(docs): custom variants link (#1232) 2023-08-01 15:39:29 -03:00
Junior Garcia 8743a6c6b6 fix(blog): typo 2023-08-01 11:49:31 -03:00
Junior Garcia c635c951d6 fix(blog): typo 2023-08-01 11:44:18 -03:00
Junior Garcia cb05342a4a fix(docs): v1 redirect 2023-08-01 11:33:22 -03:00
Junior Garcia 59ef7bc6b6 feat(blog): rss added 2023-08-01 11:16:50 -03:00
Junior Garcia cfd19415ea fix(docs): v2 blog toc 2023-08-01 10:54:50 -03:00
Junior Garcia 39ca892b4e chore(root): changeset changelogs enabled 2023-07-31 23:40:30 -03:00
Junior Garcia fc2dad8b39 chore(docs): navbar announcement chip border color changed
Tagged Release / Tagged Release (push) Has been cancelled
2023-07-31 23:22:25 -03:00
Junior Garcia d9bbd2e45f feat(docs): next.js updated, sitemap generation added 2023-07-31 23:16:28 -03:00
Junior Garcia 75bc039b4b feat(root): storybook updated, custom variants docs format fixed 2023-07-31 22:58:55 -03:00
Junior Garcia e79bd0fa4c chore(docs): storybook links updated to v2 2023-07-31 22:50:46 -03:00
Junior Garcia 65c5af4f63 chore(core): ci/cd badge removed 2023-07-31 22:46:53 -03:00
Junior Garcia f58c8a7d99 feat(docs): missing resources added 2023-07-31 22:45:39 -03:00
Junior Garcia 9da06aaa50 chore(docs): remove dev-v2 tag from main package 2023-07-31 22:27:45 -03:00
Junior Garcia 508f11e968 chore(docs): v1 link added to navbar 2023-07-31 22:26:31 -03:00
Junior Garcia 2812f61f4f refactor(docs): nextui v2 blog date changed 2023-07-31 22:18:23 -03:00
Junior Garcia 0ee0e95678 fix(docs): invalid redirect 2023-07-31 22:14:28 -03:00
Junior Garcia e940ec06ac feat(root): v2 published 2023-07-31 22:10:19 -03:00
Junior Garcia 1976c62105 feat(docs): new snapshot, release blog in progress 2023-07-30 18:20:18 -03:00
Junior Garcia aedda9d0de chore(root): merge feat/v2 2023-07-29 12:23:24 -03:00
Junior Garcia 38afbba681 chore(docs): custom variants examples improved 2023-07-29 10:28:35 -03:00
Junior Garcia cd941e64c1 Fix/props suggestions (#1225)
* refactor(core): new forwardRef types created, stories modified, important changes implemented

* feat(docs): custom variants api added, components ref fixed

* fix(components): omit ref removed
2023-07-28 18:00:18 -03:00
Junior Garcia eec0ab7584 fix(core): new snapshot, system pkg build fixed 2023-07-27 22:42:25 -03:00
Junior Garcia a415a6f0f7 feat(docs): extendVariants slots support 2023-07-25 16:37:26 -03:00
Cam Cam 0008709813 [Feat/v2] - fix(Chip): style issue (#1214)
* Update chip.ts

fix(Chip): fixed chip style issue one char with close button

* Update use-chip.ts

fix(Chip): added isCloseable variant
2023-07-24 14:57:33 -03:00
Junior Garcia 8248f0c36f feat(root): new snapshot 2023-07-23 18:23:58 -03:00
Jakob Guddas 78556ab9ba fix: fixed switch small pressed state animation (#1210) 2023-07-23 18:18:26 -03:00
Jakob Guddas 02fd961a71 fix: update accordion on child change (#1211) 2023-07-23 18:17:33 -03:00
Jakob Guddas b054bfa3b6 fix: added missing helper text wrapper to textarea (#1216) 2023-07-23 18:15:29 -03:00
Junior Garcia 597ff59e51 chore(docs): update sponsors, search meta and button group example 2023-07-23 18:07:18 -03:00
Jakob Guddas 3e5af52ae1 docs: improved group-use-case example (#1217) 2023-07-23 18:06:33 -03:00
Junior Garcia 698b2a9e25 fix(docs): sandpack depedencies injection fixed 2023-07-23 18:03:35 -03:00
Junior Garcia b7477e008d fix(docs): cv button styles link 2023-07-23 17:31:24 -03:00
Junior Garcia caf6166d49 feat(docs): custom variants doc updated 2023-07-23 17:29:33 -03:00
Junior Garcia 61e07fbdde feat(docs): custom variants doc updated 2023-07-23 17:28:50 -03:00
Junior Garcia 96f55f10b8 feat(root): extendStyles renamed to extendVariants, compound and default variants support added 2023-07-23 16:28:35 -03:00
Junior Garcia 161cd23658 feat(root): extendStyles function created for component without slots 2023-07-22 19:24:42 -03:00
Junior Garcia 49e3009d7d feat(root): extendStyles function in progress 2023-07-21 17:27:04 -03:00
Junior Garcia 3094c3d226 chore(docs): wip custom variants 2023-07-19 09:25:47 -03:00
Junior Garcia 6cd84fed80 feat(docs): custom variants doc added 2023-07-18 23:17:57 -03:00
Junior Garcia b1a9bb2664 feat(docs): customization part done. new snapshot 2023-07-18 18:37:01 -03:00
Junior Garcia 6796ccb76f feat(docs): override styles in progress 2023-07-17 22:50:55 -03:00
Jakob Guddas ad45bf5619 feat: improved animations (#1206) 2023-07-17 20:32:41 -03:00
Jakob Guddas 9f2e1249d5 fix: increased hitbox of radio and checkbox (#1205) 2023-07-17 20:32:22 -03:00
Junior Garcia 12b5f0ed33 feat(docs): customization docs added 2023-07-17 20:24:00 -03:00
Jakob Guddas 2b1841e362 fix(stories): fixed password field story issues (#1201) 2023-07-17 17:59:12 -03:00
Junior Garcia 9bd88dec48 feat(docs): theme docs in progress 2023-07-16 16:42:33 -03:00
Jakob Guddas f0123e6be6 fix: changed checkbox group label to be span (#1204) 2023-07-16 12:17:16 -03:00
Jakob Guddas 70a5853cd8 fix: added alt fallback to avatar component (#1203) 2023-07-16 11:40:58 -03:00
Jakob Guddas ffd1c4d535 fix: fixed navbar link problems in docs (#1199)
* fix: fixed navbar link problems in docs

* fix: fixed navbar link problems in docs
2023-07-16 11:36:40 -03:00
Junior Garcia d745cd114f fix(docs): table use case example 2023-07-15 12:43:36 -03:00
Junior Garcia d1252d7c50 chore: new snapshot 2023-07-15 12:21:09 -03:00
Junior Garcia 3277ef2b0a feat(docs): new snapshot, sandpack fixes, table fixes 2023-07-15 12:02:17 -03:00
Jakob Guddas ea2ab14d43 fix: pagination focus is now maintained after page switching (#1194) 2023-07-15 10:49:08 -03:00
Jakob Guddas 4f096a8096 fix: added min gap to navbar (#1195) 2023-07-15 10:48:55 -03:00
Jakob Guddas 01e764813d fix: added aria hide outside to popover (#1198)
* fix: added aria hide outside and prevent scroll to popover

* chore: readded react-remove-scroll
2023-07-15 10:48:21 -03:00
Junior Garcia 76ef3a9b7d chore(docs): search meta updated 2023-07-14 23:13:32 -03:00
Junior Garcia 5f35439503 chore(docs): view behavior changed on code demo 2023-07-14 23:06:47 -03:00
Junior Garcia 598af42e2c fix(table): styles issues 2023-07-14 23:03:21 -03:00
Junior Garcia 3a1868baf5 feat(docs): table custom styles example added, api added 2023-07-14 22:39:48 -03:00
Junior Garcia 72d7158034 fix(root): table sandpacks, input label placeholder 2023-07-14 00:31:44 -03:00
Junior Garcia 504eb1dc47 feat(docs): more table examples added 2023-07-13 21:09:53 -03:00
Junior Garcia 1ff1c7b111 feat(docs): table docs in progress 2023-07-12 21:52:32 -03:00
Junior Garcia 779f54f726 feat(docs): navbar api added 2023-07-11 23:51:26 -03:00
Junior Garcia a822eddc11 feat(navbar): controlled example added, data atrrs addded 2023-07-11 19:57:57 -03:00
Junior Garcia 9cae2d2625 fix(progress): max vand mix values fixed 2023-07-11 19:07:06 -03:00
Jakob Guddas fee73b4f77 fix: pagination dots changes now on focus visible to arrows (#1191) 2023-07-11 18:38:24 -03:00
Jakob Guddas 4049eae3c6 fix: progress bar with more than 100% is now full and has proper label (#1190) 2023-07-11 18:37:35 -03:00
Junior Garcia 874bbe87e7 feat(navbar): docs improved, new release published 2023-07-11 18:16:15 -03:00
Junior Garcia 950eabacfd chore: confitional add analytics 2023-07-10 23:43:56 -03:00
Junior Garcia 6fe0a81cd2 chore(docs): analytics added again 2023-07-10 19:15:09 -03:00
Junior Garcia 8810699b14 chore(root): tv upgraded 2023-07-10 19:03:38 -03:00
Junior Garcia 7e29dddea3 fix(root): tailwind variants updated to fix the perf issues 2023-07-09 21:54:32 -03:00
Junior Garcia 63cf041067 fix(input): on value change 2023-07-09 11:52:19 -03:00
Junior Garcia b4b75444e7 Merge branch 'feat/v2' of github.com:nextui-org/nextui into feat/v2 2023-07-09 11:34:18 -03:00
Junior Garcia 4ec33a4c78 fix(root): input, textarea and accordion perf issues 2023-07-09 11:33:57 -03:00
Jakob Guddas 5acfb2e7a9 fix: fixed modal button press issue (#1186) 2023-07-09 09:00:45 -03:00
Junior Garcia b64e97c94f fix(root): tabs perf issues, accordion overflow 2023-07-08 23:26:08 -03:00
Junior Garcia f4224c9c46 feat(root): new snap 2023-07-08 16:35:03 -03:00
Junior Garcia 75dfb5461d fix(docs): modal position note 2023-07-08 12:38:47 -03:00
Junior Garcia 1251cc9a9b chore(modal): new snapshot 2023-07-08 12:34:54 -03:00
Junior Garcia 4c52d1d1cf chore(modal): mobile transitions improved 2023-07-08 12:14:00 -03:00
Junior Garcia fdc513cefe chore(modal): mobile improvements 2023-07-08 12:12:35 -03:00
Junior Garcia 09f30f93aa feat(modal): mobile transition changed 2023-07-08 11:44:17 -03:00
Junior Garcia 6ccbabe788 chore(accordion): collapse animation changed againe 2023-07-07 21:29:37 -03:00
Junior Garcia d752438a26 chore(accordion): remove unused code 2023-07-07 21:01:13 -03:00
Junior Garcia 653e7d2031 fix(root): radio and checkbox state memoized, accordion transition changed 2023-07-07 20:53:10 -03:00
Junior Garcia 2750189f66 feat(accordion): transition improved 2023-07-06 21:37:29 -03:00
Junior Garcia 29f0d9a937 feat(root): accordion animation improved, units introduced, button spacing migrated to units 2023-07-06 20:28:09 -03:00
Junior Garcia 5b5756bb33 feat(root): new ripple pkg created 2023-07-06 00:08:39 -03:00
Junior Garcia 10ac6ced32 feat(root): animations management improved 2023-07-04 21:54:13 -03:00
Junior Garcia 3b0584ee83 chore(popover): spring transitions changed 2023-07-04 00:53:07 -03:00
Junior Garcia 4567cf525d feat(docs): navbar docs fixed, popover and dropdown animations improved 2023-07-03 22:57:49 -03:00
Ryo Hirama 77d56aae0c fix: warning keys accordion (#1179) 2023-07-03 15:53:23 -03:00
Junior Garcia 81c45e6336 chore(docs): lockfile updated 2023-07-02 20:19:31 -03:00
Junior Garcia 00dcf6cbc8 feat(docs): next-mdx-remote replaced by contentlayer 2023-07-02 20:17:55 -03:00
Junior Garcia eab6dec219 fix(plugin): lodash dependencies 2023-07-02 14:58:26 -03:00
Junior Garcia ca7309c6eb feat(root): needless useId removed since react-aria now supports react 18 2023-07-02 12:37:44 -03:00
Junior Garcia 316010e210 fix(dropdown): styles fixed 2023-07-02 00:48:24 -03:00
Junior Garcia d11a3a8607 chore(root): boundary color removed, dropdown styles changed 2023-07-02 00:36:39 -03:00
Junior Garcia 15b585c51a feat(root): react aria packages upgraded, SSRProvider removed since is no longer needed 2023-07-02 00:20:08 -03:00
Junior Garcia 234f217a1c fix(docs): card examples 2023-07-01 23:31:37 -03:00
Junior Garcia 0267bf7c2f feat(storybook): updated 2023-07-01 20:06:10 -03:00
Junior Garcia 114c3f5a6b feat(root): layout configuration added to the tailwind plugin, components were adapted 2023-07-01 20:03:52 -03:00
Junior Garcia ce9abed071 feat(docs): navbar in progress 2023-06-25 20:36:09 -03:00
Junior Garcia c24efed184 chore(docs): search meta updated 2023-06-25 12:20:03 -03:00
Junior Garcia 6b88866307 feat(docs): dropdown done 2023-06-25 12:01:59 -03:00
Junior Garcia 436fd99b09 feat(docs): dropdown styles and API changed, custom styles examples improved 2023-06-25 00:54:07 -03:00
Junior Garcia 44722b6430 chore(root): dropdown items improved on mobile, colros small changes 2023-06-24 18:58:00 -03:00
Junior Garcia cbced8df45 feat(docs): dropdown docs in progress, some styles changes 2023-06-24 00:39:05 -03:00
Junior Garcia 99993cd332 feat(root): semantic colors improved, dropdown search meta updated 2023-06-23 20:43:49 -03:00
Junior Garcia f5db202b13 feat(docs): dropdown docs almost done 2023-06-23 19:57:30 -03:00
Junior Garcia 1658afc756 feat(docs): dropdown in progress 2023-06-22 23:42:03 -03:00
Junior Garcia f26cfbe9fb feat(root): new snapshot version 2023-06-22 17:39:17 -03:00
TRAN Duc Khanh 1fd3ad5805 fix: fix image loading status (#1175) 2023-06-22 17:12:46 -03:00
Junior Garcia 6ebdcac4fc feat(root): new snapshot 2023-06-21 23:26:03 -03:00
Junior Garcia 19d0766487 fix(pagination): merge conflicts 2023-06-21 23:17:34 -03:00
Junior Garcia d906885857 fix(pagination): touch and select none added 2023-06-21 23:16:54 -03:00
Jakob Guddas 74e7c378a1 fix: fixed pagination problems (#1174)
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
2023-06-21 23:16:26 -03:00
Junior Garcia 21c7934aba feat(root): pagination and tabs improvements, cmdk recent searches fixed 2023-06-21 22:57:07 -03:00
Ryo Matsukawa 73ce35a1b9 feat(tabs): improve tab accessibility (#1172) 2023-06-21 22:54:39 -03:00
Jakob Guddas 4c61bff868 feat: added locale select to storybook toolbar (#1173) 2023-06-21 22:53:07 -03:00
Junior Garcia 7c3df1b0d9 feat(root): new snapshot 2023-06-20 20:19:29 -03:00
Jakob Guddas ced7e6093a feat: added locale prop to NextUIProvider (#1145)
* feat: added locale prop to NextUIProvider

* fix: added missing locale prop in storybook config

* chore: resolved PR comments
2023-06-20 20:08:49 -03:00
Junior Garcia 4ce707d55f fix(pagination): item tap highlight transparent 2023-06-20 17:06:14 -03:00
Junior Garcia e128447be6 chore(docs): tabs custom styles example rollback 2023-06-20 16:55:46 -03:00
Junior Garcia f6e4aa8adf feat(root): utilities, update effect hook added, tabs improvements 2023-06-20 16:47:35 -03:00
Junior Garcia f4c9f21959 Merge branch 'feat/v2' of github.com:nextui-org/nextui into feat/v2 2023-06-20 16:43:12 -03:00
Junior Garcia 230213853e feat(root): tab highlight transparent and scrollbar hide utilities added, tabs improvements 2023-06-20 16:43:00 -03:00
Jakob Guddas 47053f6875 fix: fixed bordered button styling issues inside group (#1169) 2023-06-20 16:41:29 -03:00
Junior Garcia b56a89d0be feat(paignation): automatic scroll added, styles improved 2023-06-20 12:48:35 -03:00
Junior Garcia a27ca56c08 feat(docs): pagination improved 2023-06-20 11:32:53 -03:00
Junior Garcia fdecb49117 feat(root): pagination docs done, colors a11y improved 2023-06-19 21:48:03 -03:00
Jakob Guddas 63a2f2d4b3 test: added contrast test for semantic colors (#1167) 2023-06-19 16:17:18 -03:00
Junior Garcia 3f77004403 feat(docs): pagination in progress 2023-06-19 16:13:35 -03:00
Junior Garcia a5055bc81c feat(root): new snapshot 2023-06-19 10:54:27 -03:00
camcam 9fadc43657 [feat/v2] allow custom prefix in baseStyles (#1152)
* allow custom prefix

* update corePlugin

* Update classes.ts
2023-06-19 10:48:36 -03:00
Junior Garcia dcd8e0b8a9 feat(root): modal improved, flat colors changed 2023-06-19 10:28:50 -03:00
Junior Garcia 0d16f7d78a fix(docs): build 2023-06-18 23:05:05 -03:00
Junior Garcia bb0fa71e69 feat(docs): search match imporoved, recents search added, navbar items clicable section increased 2023-06-18 22:41:02 -03:00
Junior Garcia a30ba4bc95 fix(docs): a11y section button color 2023-06-18 17:10:37 -03:00
Junior Garcia 06bae4ffaf chore(docs): esc key on cmdk removed from mobile 2023-06-18 15:54:38 -03:00
Junior Garcia a3979a88a8 feat(docs): table header background changed 2023-06-18 15:40:36 -03:00
Junior Garcia cfaf732d27 fix(docs): main content z-index 2023-06-18 15:27:18 -03:00
Junior Garcia 98f0f2357d fix(docs): cmk modal max width 2023-06-18 15:15:37 -03:00
Junior Garcia 129b30f956 feat(docs): search cmdk implemented 2023-06-18 15:06:43 -03:00
Junior Garcia 71741a115a feat(docs): modal docs done 2023-06-17 11:27:01 -03:00
Junior Garcia 4e29da6196 feat(docs): modal custom styles highlighted lines added 2023-06-16 23:25:26 -03:00
Junior Garcia 5da8ea5d0b feat(docs): modal examples added 2023-06-16 23:24:10 -03:00
Junior Garcia 21536b82fc fix(docs): toc overflow 2023-06-16 22:09:44 -03:00
Junior Garcia b475f892e2 feat(root): new snapshot 2023-06-16 21:55:11 -03:00
Junior Garcia 38114b16da feat(root): tv upgraded, modal docs improved, search meta script added 2023-06-16 21:34:41 -03:00
Junior Garcia 5e33f2c816 feat(root): modal, popover and navbar menu issues fixed, navbar styles simplified, docs improved 2023-06-15 23:48:25 -03:00
Junior Garcia 56e8aba53a feat(docs): avatar api updated, small improvements applied 2023-06-15 00:14:40 -03:00
Junior Garcia c589405b94 fix(root): pkg lock 2023-06-14 23:47:34 -03:00
Junior Garcia e70819ec7b feat(docs): a11y and performance optimizations on landing page 2023-06-14 23:46:12 -03:00
Junior Garcia 3406c29911 feat(root): packages updated 2023-06-14 01:48:42 -03:00
Junior Garcia 1ec784b3a5 feat(docs): migrated to app directory 2023-06-14 00:18:45 -03:00
Junior Garcia 810f2b2bd9 fix(root): pkg lock 2023-06-13 18:30:23 -03:00
Junior Garcia e802caa412 feat(docs): home migrated to app directory 2023-06-13 18:29:03 -03:00
Junior Garcia cfa43f22c7 feat(root): warning and success colors a11y improved on light theme, docs bugs fixed 2023-06-12 19:16:18 -03:00
Junior Garcia 0ac048c11f fix(docs): build 2023-06-12 18:52:32 -03:00
Junior Garcia 57ec8944c3 fix(docs): modal dynamic height, snadpack editor replaced by codeblock 2023-06-12 18:41:55 -03:00
Junior Garcia aceafaff56 feat(docs): modal docs in progress 2023-06-10 19:03:49 -03:00
Junior Garcia 5a7e63a40a feat(modal): improved on mobile 2023-06-09 23:51:11 -03:00
Junior Garcia c4f8d7015b feat(docs): scroll mask image linear gradient added to sidebar and toc 2023-06-09 22:25:17 -03:00
Junior Garcia 0a3ea728a1 fix(docs): hero height 2023-06-09 19:36:45 -03:00
Junior Garcia 1ce246d0ad feat(docs): accordion improved, badge fixed, modal started 2023-06-09 19:28:07 -03:00
Junior Garcia d165a4beb6 fix(root): button ring and examples 2023-06-08 21:38:21 -03:00
Junior Garcia 10c1292fc5 feat(docs): tabs docs done 2023-06-08 21:03:40 -03:00
Junior Garcia 27041fa545 fix(docs): tooltip docs 2023-06-06 23:44:57 -03:00
Junior Garcia 54e9c8231f feat(docs): tooltip docs done 2023-06-06 22:36:56 -03:00
Junior Garcia 8ee4cd4e0c feat(docs): popover docs done 2023-06-06 17:38:38 -03:00
Junior Garcia 87ea9a9eba fix(docs): intl message dependency, examples layout improved 2023-06-06 16:04:51 -03:00
Kyun Heo 720d944acd fix: add framer motion to snippet component peerDependencies (#1148) 2023-06-06 11:05:19 -03:00
Junior Garcia cfd6555d6d feat(docs): popover examples added 2023-06-05 23:46:57 -03:00
Junior Garcia 8df3fe624a Merge branch 'feat/v2' of github.com:nextui-org/nextui into feat/v2 2023-06-05 23:10:13 -03:00
Junior Garcia d686129687 chore(root): borders changed, types fixed 2023-06-05 23:08:50 -03:00
Jakob Guddas 2207771540 docs: updated story to use link component instead of basic anchor (#1146) 2023-06-05 23:02:26 -03:00
Junior Garcia b62ca94458 feat(docs): popover docs almost done 2023-06-04 23:33:02 -03:00
Junior Garcia 76a5c07e1f chore(docs): switch icons example color changed 2023-06-04 17:51:15 -03:00
Junior Garcia 51c6b291d5 feat(docs): switch docs done 2023-06-04 17:46:02 -03:00
Junior Garcia c5bcd70457 feat(docs): radio group done 2023-06-04 16:18:32 -03:00
Junior Garcia 78f1c5f957 feat(docs): radio group in progress 2023-06-03 16:40:03 -03:00
Junior Garcia 1ccdfb5b98 fix(docs): image events title 2023-06-03 15:56:29 -03:00
Junior Garcia dc5e4a1fd2 feat(docs): image docs done 2023-06-03 15:42:08 -03:00
Junior Garcia 51cc2f6531 fix(docs): image 2023-06-03 11:06:57 -03:00
Junior Garcia 45bb9fddc4 fix(docs): pkg imports 2023-06-03 10:40:53 -03:00
Junior Garcia a09258b47c fix(docs): checkbox custom styles 2023-06-02 17:45:23 -03:00
Junior Garcia 3a4c530785 fix(docs): nextjs templates 2023-06-02 11:44:33 -03:00
Junior Garcia 8d22a7bc56 feat(root): react aria packages upgraded, fix input types 2023-06-01 22:31:04 -03:00
Junior Garcia 017d40b623 chore(storybook): deployed 2023-05-31 21:34:23 -03:00
Junior Garcia a1f5109365 fix(docs): button colors names 2023-05-31 21:31:05 -03:00
Junior Garcia 25db8f732d fix(docs): lsc navbar fixed 2023-05-31 21:28:44 -03:00
Junior Garcia 89f2bc9be4 feat(docs): some improvements applied, button and spinner styles fixed 2023-05-31 21:20:49 -03:00
Junior Garcia 4b8655e267 feat(docs): textarea docs done 2023-05-30 23:05:11 -03:00
Junior Garcia 849dd9948b chore(docs): textarea docs in progress 2023-05-30 19:38:06 -03:00
Junior Garcia e42d3ab751 chore(docs): mobile navbar enhacements 2023-05-30 19:21:26 -03:00
Junior Garcia e7bfb81b48 feat(docs): mobile navbar improved 2023-05-30 19:16:56 -03:00
Junior Garcia 5df7003482 fix(docs): unified pkg 2023-05-30 18:53:54 -03:00
Junior Garcia 131330713f chore(root): pkg lock updated 2023-05-30 18:40:20 -03:00
Junior Garcia 6f12edbfc6 feat(docs): headings and toc enhacements 2023-05-30 18:38:13 -03:00
Junior Garcia 0e558c60c3 feat(docs): docs structure improved, steps added and toc autoscroll 2023-05-29 23:29:26 -03:00
Junior Garcia a0504a8e0e fix(root): card npm pkg 2023-05-29 20:03:23 -03:00
Junior Garcia 36ba63ff10 feat(docs): input examples responsive improved 2023-05-29 19:56:18 -03:00
Junior Garcia e8dbcab525 feat(docs): input docs done 2023-05-29 19:49:54 -03:00
Junior Garcia 2e4f54a06b feat(docs): input docs in progress 2023-05-28 23:53:22 -03:00
Junior Garcia bb27ca5e11 feat(docs): skeleton docs fixed, redirection added 2023-05-28 19:42:34 -03:00
Junior Garcia 21cae37835 fix(docs): snippet url 2023-05-28 16:23:34 -03:00
Junior Garcia 6a4e16b224 fix(docs): small fixes 2023-05-28 16:23:01 -03:00
Junior Garcia 1569b59725 feat(docs): snippet done 2023-05-28 16:17:22 -03:00
Junior Garcia 6cb0e5a97b feat(docs): skeleton docs completed, navbar menu toggle fixed 2023-05-28 12:34:29 -03:00
Junior Garcia e7f9579389 feat(docs): skeleton docs in progress 2023-05-28 01:17:12 -03:00
Junior Garcia e8fb0c7a09 feat(docs): user docs added 2023-05-28 00:30:22 -03:00
Junior Garcia 90e76df87d feat(docs): progress done, internal APi improved 2023-05-27 23:25:11 -03:00
Junior Garcia 0ac5a339f4 feat(docs): link done 2023-05-27 22:40:39 -03:00
Junior Garcia 69d9e2bcdc feat(docs): link done 2023-05-27 22:38:13 -03:00
camcam 102387a004 fix(Link): fix unknown event warning when passing onPress prop (#1135)
* fix(Link): fix warning unknown event when passing onPress prop

* add useCallback
2023-05-27 22:23:23 -03:00
Junior Garcia fa404913bd feat(docs): import tabs added, link docs in progress 2023-05-27 20:23:15 -03:00
Junior Garcia bee0c50c4b refactor(docs): neutral color renamed to default, next.js guide fixed 2023-05-27 11:55:59 -03:00
Junior Garcia 0b3ebbcfdf feat(docs): navbar manifest added 2023-05-27 10:32:39 -03:00
Junior Garcia dc0b3897d0 feat(docs): spinner docs added 2023-05-26 21:28:46 -03:00
Junior Garcia 2cb983af3e feat(docs): spacer added 2023-05-26 20:56:27 -03:00
Junior Garcia a0d4d39eab fix(docs): kbd api overflow 2023-05-26 20:28:01 -03:00
Junior Garcia c00d3f3199 feat(docs): kbd docs added 2023-05-26 20:18:45 -03:00
Junior Garcia f564f5bd2a fix(docs): data attributes section on divider 2023-05-26 19:46:55 -03:00
Junior Garcia 62c3c71e9e feat(docs): divider docs added 2023-05-26 19:45:57 -03:00
Junior Garcia f7c77e2f14 feat(docs): code documentation added 2023-05-26 19:35:44 -03:00
Junior Garcia 088c08dd63 feat(docs): circular progress improved 2023-05-26 19:11:27 -03:00
Junior Garcia 5168f4f495 feat(docs): chip and circular progress done 2023-05-26 19:02:17 -03:00
1299 changed files with 75153 additions and 49074 deletions
+4 -1
View File
@@ -1,6 +1,9 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"changelog": [
"@changesets/changelog-github",
{ "repo": "nextui-org/nextui" }
],
"commit": false,
"fixed": [],
"linked": [],
+2 -1
View File
@@ -1,7 +1,7 @@
.now/*
.next/*
*.css
examples/*
.changeset
dist
esm/*
public/*
@@ -22,3 +22,4 @@ build
!react-shim.js
!tsup.config.ts
apps/docs/preinstall.js
apps/docs/next-redirect.js
+10
View File
@@ -21,6 +21,16 @@ body:
- NextUI's [Discussions tab](https://github.com/nextui-org/nextui/discussions)
The more information you fill in, the better the community can help you.
- type: input
id: version
attributes:
label: NextUI Version
description: |
Please provide the version of NextUI you are using.
You can find the version number in the package.json file.
placeholder: ex. 2.0.10
validations:
required: true
- type: textarea
id: description
attributes:
+27
View File
@@ -0,0 +1,27 @@
name: "Install"
description: "Sets up Node.js and runs install"
runs:
using: composite
steps:
- name: Install dependencies
uses: pnpm/action-setup@v2.2.4
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: ".nvmrc"
registry-url: "https://registry.npmjs.org"
cache: "pnpm"
- name: Setup Git User
shell: bash
run: |
git config --global user.email "jrgarciadev@gmail.com"
git config --global user.name "Junior Garcia"
- name: Install dependencies
shell: bash
run: pnpm install
+72
View File
@@ -0,0 +1,72 @@
name: QA
on:
pull_request:
branches:
- main
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout branch
uses: actions/checkout@v3
- name: Install
uses: ./.github/common-actions/install
- name: Build packages
run: pnpm build
tests:
name: Tests
runs-on: ubuntu-latest
steps:
- name: Checkout branch
uses: actions/checkout@v3
- name: Install
uses: ./.github/common-actions/install
- name: Run tests
run: pnpm test
eslint:
name: ESLint
runs-on: ubuntu-latest
steps:
- name: Checkout branch
uses: actions/checkout@v3
- name: Install
uses: ./.github/common-actions/install
- name: Run ESLint
run: pnpm lint
types:
name: TypeScript
runs-on: ubuntu-latest
steps:
- name: Checkout branch
uses: actions/checkout@v3
- name: Install
uses: ./.github/common-actions/install
- name: Run typecheck
run: pnpm typecheck
prettier:
name: Prettier
runs-on: ubuntu-latest
steps:
- name: Checkout branch
uses: actions/checkout@v3
- name: Install
uses: ./.github/common-actions/install
- name: Run prettier
run: pnpm format:check
-128
View File
@@ -1,128 +0,0 @@
name: CI/CD
on:
push:
branches: [main]
paths-ignore: ['**/README.md']
pull_request:
branches: [main]
types: [opened, synchronize]
paths-ignore: ['**/README.md']
jobs:
avoid_reduncy:
runs-on: ubuntu-20.04
if: ${{ github.event_name == 'push' }}
steps:
- name: Cancel Previous Redundant Builds
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
lint:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
cache: 'yarn'
node-version: '16'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Lint
run: yarn lint
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
cache: 'yarn'
node-version: '16'
- name: Install dependencies
run: yarn install --frozen-lockfile
- uses: actions/cache@v3
with:
path: ${{ github.workspace }}/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}-
- name: Build
run: yarn build
tests:
needs: [lint, build]
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
cache: 'yarn'
node-version: '16'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Test
run: yarn coverage:update
- uses: codecov/codecov-action@v2
with:
directory: './packages/react'
publish:
needs: [build, tests]
runs-on: ubuntu-20.04
if: ${{ github.event_name == 'push' }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
cache: 'yarn'
node-version: '16'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Pre puiblish NextUI package
run: yarn pre-publish:nextui
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
package: './packages/react/lib/package.json'
- name: Show published version
if: steps.publish.outputs.type != 'none'
run: |
echo "Version changed: ${{ steps.publish.outputs.old-version }} => ${{ steps.publish.outputs.version }}"
- name: Tag new version
if: steps.publish.outputs.type != 'none'
uses: Klemensas/action-autotag@stable
with:
GITHUB_TOKEN: ${{ github.token }}
tag_prefix: "v"
package_root: "./packages/react"
update_algolia_index:
needs: [publish]
runs-on: ubuntu-20.04
if: ${{ github.event_name == 'push' }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
cache: 'yarn'
node-version: '16'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Upload docs on Algolia
run: yarn build:docs-meta
env:
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }}
ALGOLIA_SEARCH_API_KEY: ${{ secrets.ALGOLIA_SEARCH_API_KEY }}
+45 -10
View File
@@ -1,17 +1,52 @@
name: Tagged Release
name: Release
on:
push:
tags:
- "v*"
paths:
- ".changeset/**"
- "packages/**"
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
tagged-release:
name: Tagged Release
runs-on: ubuntu-18.04
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: "marvinpinto/action-automatic-releases@latest"
- name: Checkout branch
uses: actions/checkout@v3
- name: Install
uses: ./.github/common-actions/install
- name: Build
run: pnpm build
- name: Tests
run: pnpm test
- name: Create Release Pull Request or Publish to NPM
id: changesets
uses: changesets/action@v1
with:
repo_token: "${{ github.token }}"
prerelease: false
publish: pnpm release
title: "ci(changesets): :package: version packages"
commit: "ci(changesets): version packages"
setupGitUser: false
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create @dev release
if: steps.changesets.outputs.published != 'true'
run: |
git checkout main
pnpm version:dev
pnpm release:dev
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+6
View File
@@ -20,6 +20,7 @@ coverage
dist/
storybook-static
/packages/storybook/public/tailwind.css
# misc
.DS_Store
.env
@@ -48,6 +49,8 @@ esm
examples/**/yarn.lock
examples/**/out
examples/**/.next
packages/**/*.backup
packages/**/*.backup.ts
.vercel
@@ -57,3 +60,6 @@ apps/**/sitemap.xml
# turbo
.turbo
packages/**/.turbo
# content layer
.contentlayer
+2 -2
View File
@@ -13,12 +13,12 @@ const removeIgnoredFiles = async (files) => {
};
module.exports = {
"**/*.{cjs,mjs,js,ts,jsx,tsx}": async (files) => {
"**/*.{js,ts,jsx,tsx}": async (files) => {
const filesToLint = await removeIgnoredFiles(files);
return [`eslint -c .eslintrc.json --max-warnings=0 --fix ${filesToLint}`];
},
"**/*.{css,json,md}": async (files) => {
"**/*.css": async (files) => {
const filesToLint = await removeIgnoredFiles(files);
return [`prettier --config .prettierrc.json --ignore-path --write ${filesToLint}`];
+1 -1
View File
@@ -1,8 +1,8 @@
dist
examples
node_modules
plop
coverage
.changeset
.next
build
scripts
+1 -1
View File
@@ -9,7 +9,7 @@ NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY=
# Vercel Env (is used for skipping typescript check)
IS_VERCEL_ENV=true/false
IS_PREVIEW=true/false
ANALYZE_BUNDLE=true/false
# Vercel preview env (is used for taking the docs directly from the project files)
NEXT_PUBLIC_PREVIEW=true/false
+312
View File
@@ -0,0 +1,312 @@
[
{
"MemberId": 275354,
"createdAt": "2022-02-18 11:57",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 5,
"currency": "USD",
"lastTransactionAt": "2022-02-18 11:57",
"lastTransactionAmount": 5,
"profile": "https://opencollective.com/guest-7dc076fb",
"name": "jorge",
"company": null,
"description": null,
"image": "/sponsors/undefined",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 277819,
"createdAt": "2022-03-01 09:13",
"type": "USER",
"role": "BACKER",
"isActive": true,
"totalAmountDonated": 10,
"currency": "USD",
"lastTransactionAt": "2022-03-01 09:13",
"lastTransactionAmount": 10,
"profile": "https://opencollective.com/dhananjay-senday",
"name": "Dhananjay Senday",
"company": null,
"description": null,
"image": "/sponsors/undefined",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 292380,
"createdAt": "2022-04-22 07:47",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 10,
"currency": "USD",
"lastTransactionAt": "2022-04-22 07:47",
"lastTransactionAmount": 10,
"profile": "https://opencollective.com/manuel5",
"name": "manuel-rw",
"company": null,
"description": null,
"image": "/sponsors/292380.jpg",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 327844,
"createdAt": "2022-08-26 21:32",
"type": "ORGANIZATION",
"role": "BACKER",
"tier": "Gold Sponsor 🥇",
"isActive": true,
"totalAmountDonated": 200,
"currency": "USD",
"lastTransactionAt": "2022-12-22 07:19",
"lastTransactionAmount": 100,
"profile": "https://opencollective.com/lowdefy",
"name": "Lowdefy",
"company": null,
"description": "Create custom web apps in minutes",
"image": "/sponsors/327844.jpg",
"twitter": "https://twitter.com/lowdefy",
"github": "https://github.com/lowdefy",
"website": "https://lowdefy.com"
},
{
"MemberId": 347048,
"createdAt": "2022-10-30 06:25",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 1,
"currency": "USD",
"lastTransactionAt": "2022-10-30 06:25",
"lastTransactionAmount": 1,
"profile": "https://opencollective.com/william-frank-monroy-mamani",
"name": "William Frank Monroy Mamani",
"company": null,
"description": null,
"image": "/sponsors/347048.jpg",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 374896,
"createdAt": "2022-12-22 05:28",
"type": "ORGANIZATION",
"role": "BACKER",
"tier": "bronze sponsor 🥉",
"isActive": true,
"totalAmountDonated": 70,
"currency": "USD",
"lastTransactionAt": "2023-07-02 09:04",
"lastTransactionAmount": 10,
"profile": "https://opencollective.com/chartbrew",
"name": "Chartbrew",
"company": null,
"description": "Open source visualization and reporting platform",
"image": "/sponsors/374896.jpg",
"twitter": null,
"github": null,
"website": "https://chartbrew.com"
},
{
"MemberId": 375034,
"createdAt": "2022-12-22 13:16",
"type": "USER",
"role": "BACKER",
"isActive": true,
"totalAmountDonated": 50,
"currency": "USD",
"lastTransactionAt": "2022-12-22 13:16",
"lastTransactionAmount": 50,
"profile": "https://opencollective.com/jan-wagebach",
"name": "PRISMA European Capacity Platform GmbH",
"company": "PRISMA European Capacity Platform GmbH",
"description": null,
"image": "/sponsors/375034.jpg",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 395990,
"createdAt": "2023-02-11 13:12",
"type": "ORGANIZATION",
"role": "BACKER",
"tier": "Gold Sponsor 🥇",
"isActive": true,
"totalAmountDonated": 100,
"currency": "USD",
"lastTransactionAt": "2023-02-11 13:12",
"lastTransactionAmount": 100,
"profile": "https://opencollective.com/likn",
"name": "LIKN",
"company": null,
"description": "LIKN is a powerful, but simple Web3 component and protocol. Connect Web2 content to Web3, and solely mint content NFTs by URL. Share and Trade in different platforms and marketplaces. OpenAI empowers LIKN which can automatically generate NFT metadata. ",
"image": "/sponsors/395990.jpg",
"twitter": "https://twitter.com/oxlikn",
"github": "https://github.com/0xLIKN",
"website": "https://www.likn.co/?ref=opencollective"
},
{
"MemberId": 404415,
"createdAt": "2023-03-01 16:22",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 1,
"currency": "USD",
"lastTransactionAt": "2023-03-01 16:22",
"lastTransactionAmount": 1,
"profile": "https://opencollective.com/rharison-lucas-moreira-abreu",
"name": "Rharison Lucas Moreira Abreu",
"company": null,
"description": null,
"image": "/sponsors/404415.jpg",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 407510,
"createdAt": "2023-03-07 22:27",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 1,
"currency": "USD",
"lastTransactionAt": "2023-03-07 22:27",
"lastTransactionAmount": 1,
"profile": "https://opencollective.com/higherror",
"name": "HighError",
"company": null,
"description": null,
"image": "/sponsors/407510.jpg",
"email": null,
"twitter": null,
"github": "https://github.com/HighError",
"website": "https://higherror.github.io/"
},
{
"MemberId": 409558,
"createdAt": "2023-03-12 21:41",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 1,
"currency": "USD",
"lastTransactionAt": "2023-03-12 21:41",
"lastTransactionAmount": 1,
"profile": "https://opencollective.com/guest-6133a45c",
"name": "Kristof",
"company": null,
"description": null,
"image": "/sponsors/undefined",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 438158,
"createdAt": "2023-05-31 22:44",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 5,
"currency": "USD",
"lastTransactionAt": "2023-05-31 22:44",
"lastTransactionAmount": 5,
"profile": "https://opencollective.com/elektrnik",
"name": "ELEKTRNIK",
"company": null,
"description": null,
"image": "/sponsors/438158.jpg",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 438877,
"createdAt": "2023-06-02 17:56",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 1,
"currency": "USD",
"lastTransactionAt": "2023-06-02 17:56",
"lastTransactionAmount": 1,
"profile": "https://opencollective.com/guest-425370ea",
"name": "Gabriel Jiménez",
"company": null,
"description": null,
"image": "/sponsors/undefined",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 439182,
"createdAt": "2023-06-03 15:15",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 1,
"currency": "USD",
"lastTransactionAt": "2023-06-03 15:15",
"lastTransactionAmount": 1,
"profile": "https://opencollective.com/jay-thanawut1",
"name": "Jay Thanawut",
"company": null,
"description": null,
"image": "/sponsors/439182.jpg",
"email": null,
"twitter": null,
"github": null,
"website": null
},
{
"MemberId": 442878,
"createdAt": "2023-06-13 13:51",
"type": "USER",
"role": "BACKER",
"tier": "Backer 🖤",
"isActive": true,
"totalAmountDonated": 5,
"currency": "USD",
"lastTransactionAt": "2023-06-13 13:51",
"lastTransactionAmount": 5,
"profile": "https://opencollective.com/encurme",
"name": "EncurMe",
"company": "EncurMe",
"description": null,
"image": "/sponsors/442878.jpg",
"email": null,
"twitter": null,
"github": null,
"website": "https://encur.me/"
}
]
+127
View File
@@ -0,0 +1,127 @@
import type {Metadata} from "next";
import {notFound} from "next/navigation";
import {allBlogPosts} from "contentlayer/generated";
import {Link, User} from "@nextui-org/react";
import {format, parseISO} from "date-fns";
import NextLink from "next/link";
import {Balancer} from "react-wrap-balancer";
import {MDXContent} from "@/components/mdx-content";
import {siteConfig} from "@/config/site";
import {Route} from "@/libs/docs/page";
import {ChevronRightLinearIcon} from "@/components/icons";
interface BlogPostProps {
params: {
slug: string;
};
}
async function getBlogPostFromParams({params}: BlogPostProps) {
const slug = params.slug || "";
const post = allBlogPosts.find((post) => post.slugAsParams === slug);
if (!post) {
null;
}
const currentRoute: Route = {
key: post?._id,
title: post?.title,
path: `/${post?._raw?.sourceFilePath}`,
};
return {post, currentRoute};
}
export async function generateMetadata({params}: BlogPostProps): Promise<Metadata> {
const {post} = await getBlogPostFromParams({params});
if (!post) {
return {};
}
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
type: "article",
url: post.url,
images: [
{
url: post.imageAsParams || siteConfig.ogImage,
width: 1200,
height: 630,
alt: post.title || siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title: post.title,
description: post.description,
images: [siteConfig.ogImage],
creator: siteConfig.creator,
},
};
}
export async function generateStaticParams(): Promise<BlogPostProps["params"][]> {
return allBlogPosts.map((doc) => ({
slug: doc.slugAsParams,
}));
}
export default async function DocPage({params}: BlogPostProps) {
const {post} = await getBlogPostFromParams({params});
if (!post) {
notFound();
}
return (
<div className="w-full mt-12 flex flex-col justify-start items-center prose prose-neutral">
<div className="w-full max-w-4xl">
<Link
isBlock
as={NextLink}
className="mb-8 -ml-3 text-default-500 hover:text-default-900"
color="foreground"
href="/blog"
size="sm"
>
<ChevronRightLinearIcon className="rotate-180 inline-block mr-1" size={15} />
Back to blog
</Link>
<time className="block text-small mb-2 text-default-500" dateTime={post.date}>
{format(parseISO(post.date), "LLLL d, yyyy")}
</time>
<div className="mb-3 flex w-full flex-col items-start">
<User
isExternal
as={Link}
avatarProps={{
className: "w-9 h-9 text-large",
src: post.author?.avatar,
}}
className="hover:opacity-100"
classNames={{
base: "-ml-2 px-2 py-1.5 hover:bg-default-100 dark:hover:bg-default-50 cursor-pointer transition-colors",
name: "text-foreground",
}}
description={post.author?.username}
href={post.author?.link}
name={post.author?.name}
/>
</div>
<h1 className="mb-2 font-bold text-4xl">
<Balancer>{post.title}</Balancer>
</h1>
<MDXContent code={post.body.code} />
</div>
</div>
);
}
+27
View File
@@ -0,0 +1,27 @@
import {Image} from "@nextui-org/react";
interface DocsLayoutProps {
children: React.ReactNode;
}
export default function DocsLayout({children}: DocsLayoutProps) {
return (
<>
<main className="relative container mx-auto max-w-7xl z-10 px-6 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
{children}
</main>
<div
aria-hidden="true"
className="fixed hidden dark:md:block dark:opacity-70 -bottom-[40%] -left-[20%] z-0"
>
<Image removeWrapper alt="docs left background" src="/gradients/docs-left.png" />
</div>
<div
aria-hidden="true"
className="fixed hidden dark:md:block dark:opacity-70 -top-[80%] -right-[60%] 2xl:-top-[60%] 2xl:-right-[45%] z-0 rotate-12"
>
<Image removeWrapper alt="docs right background" src="/gradients/docs-right.png" />
</div>
</>
);
}
+18
View File
@@ -0,0 +1,18 @@
import {allBlogPosts} from "contentlayer/generated";
import {compareDesc} from "date-fns";
import {BlogPostList} from "@/components/blog-post";
export default function Blog() {
const posts = allBlogPosts.sort((a, b) => compareDesc(new Date(a.date), new Date(b.date)));
return (
<div className="w-full lg:px-16 mt-12">
<div className="text-center">
<h1 className="mb-2 font-bold text-4xl">NextUI Latest Updates</h1>
<h5 className="text-default-500 text-lg">All the latest news about NextUI.</h5>
</div>
<BlogPostList posts={posts} />
</div>
);
}
+108
View File
@@ -0,0 +1,108 @@
import type {Metadata} from "next";
import {notFound} from "next/navigation";
import {allDocs} from "contentlayer/generated";
import {Link} from "@nextui-org/react";
import {MDXContent} from "@/components/mdx-content";
import {siteConfig} from "@/config/site";
import {DocsPager, DocsToc} from "@/components/docs";
import {Route} from "@/libs/docs/page";
import {GITHUB_URL, REPO_NAME} from "@/libs/github/constants";
import {CONTENT_PATH, TAG} from "@/libs/docs/config";
import {getHeadings} from "@/libs/docs/utils";
interface DocPageProps {
params: {
slug: string[];
};
}
async function getDocFromParams({params}: DocPageProps) {
const slug = params.slug?.join("/") || "";
const doc = allDocs.find((doc) => doc.slugAsParams === slug);
if (!doc) {
null;
}
const headings = getHeadings(doc?.body.raw);
const currentRoute: Route = {
key: doc?._id,
title: doc?.title,
path: `/${doc?._raw?.sourceFilePath}`,
};
return {doc, headings, currentRoute};
}
export async function generateMetadata({params}: DocPageProps): Promise<Metadata> {
const {doc} = await getDocFromParams({params});
if (!doc) {
return {};
}
return {
title: doc.title,
description: doc.description,
openGraph: {
title: doc.title,
description: doc.description,
type: "article",
url: doc.url,
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title: doc.title,
description: doc.description,
images: [siteConfig.ogImage],
creator: siteConfig.creator,
},
};
}
export async function generateStaticParams(): Promise<DocPageProps["params"][]> {
return allDocs.map((doc) => ({
slug: doc.slugAsParams.split("/"),
}));
}
export default async function DocPage({params}: DocPageProps) {
const {doc, headings, currentRoute} = await getDocFromParams({params});
if (!doc) {
notFound();
}
const editUrl = `${GITHUB_URL}/${REPO_NAME}/edit/${TAG}/${CONTENT_PATH}${currentRoute?.path}`;
return (
<>
<div className="col-span-12 lg:col-span-10 xl:col-span-8 lg:px-16 mt-10">
<div className="w-full prose prose-neutral">
<MDXContent code={doc.body.code} />
</div>
{currentRoute && <DocsPager currentRoute={currentRoute} />}
<footer>
<Link isExternal showAnchorIcon href={editUrl} size="sm">
Edit this page on GitHub
</Link>
</footer>
</div>
{headings && headings.length > 0 && (
<div className="hidden z-10 xl:flex xl:col-span-2 mt-8 pl-4">
<DocsToc headings={headings} />
</div>
)}
</>
);
}
+35
View File
@@ -0,0 +1,35 @@
import {Image} from "@nextui-org/react";
import manifest from "@/config/routes.json";
import {DocsSidebar} from "@/components/docs/sidebar";
interface DocsLayoutProps {
children: React.ReactNode;
}
export default function DocsLayout({children}: DocsLayoutProps) {
return (
<>
<main className="relative container mx-auto max-w-7xl z-10 px-6 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
<div className="grid grid-cols-12">
<div className="hidden relative z-10 lg:block lg:col-span-2 mt-8 pr-4">
<DocsSidebar routes={manifest.routes} />
</div>
{children}
</div>
</main>
<div
aria-hidden="true"
className="fixed hidden dark:md:block dark:opacity-70 -bottom-[40%] -left-[20%] z-0"
>
<Image removeWrapper alt="docs left background" src="/gradients/docs-left.png" />
</div>
<div
aria-hidden="true"
className="fixed hidden dark:md:block dark:opacity-70 -top-[80%] -right-[60%] 2xl:-top-[60%] 2xl:-right-[45%] z-0 rotate-12"
>
<Image removeWrapper alt="docs right background" src="/gradients/docs-right.png" />
</div>
</>
);
}
+27
View File
@@ -0,0 +1,27 @@
"use client";
import {Card, CardBody, CircularProgress} from "@nextui-org/react";
export default function ButtonDemo() {
return (
<main className="dark bg-background text-foreground">
<div className="flex w-screen h-screen items-center justify-center">
<Card className="w-[240px] h-[240px] bg-default-200 dark:bg-default-50">
<CardBody className="justify-center items-center py-0">
<CircularProgress
classNames={{
svg: "w-36 h-36 drop-shadow-md",
indicator: "stroke-foreground",
track: "stroke-foreground/10",
value: "text-3xl font-semibold text-foreground",
}}
showValueLabel={true}
strokeWidth={4}
value={70}
/>
</CardBody>
</Card>
</div>
</main>
);
}
@@ -0,0 +1,74 @@
"use client";
import {
Modal,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
Button,
useDisclosure,
RadioGroup,
Radio,
ModalProps,
} from "@nextui-org/react";
import {useState} from "react";
export default function Page() {
const {isOpen, onOpen, onOpenChange} = useDisclosure();
const [modalPlacement, setModalPlacement] = useState("auto");
return (
<div className="flex px-10 min-h-[80vh] justify-center items-center flex-col gap-4">
<Button className="max-w-fit" onPress={onOpen}>
Open Modal
</Button>
<RadioGroup
label="Select modal placement"
orientation="horizontal"
value={modalPlacement}
onValueChange={setModalPlacement}
>
<Radio value="auto">auto</Radio>
<Radio value="top">top</Radio>
<Radio value="bottom">bottom</Radio>
<Radio value="center">center</Radio>
<Radio value="top-center">top-center</Radio>
<Radio value="bottom-center">bottom-center</Radio>
</RadioGroup>
<Modal
isOpen={isOpen}
placement={modalPlacement as ModalProps["placement"]}
onOpenChange={onOpenChange}
>
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col gap-1">Modal Title</ModalHeader>
<ModalBody>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non
risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor
quam.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non
risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor
quam.
</p>
</ModalBody>
<ModalFooter>
<Button color="danger" variant="light" onClick={onClose}>
Close
</Button>
<Button color="primary" onPress={onClose}>
Action
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
</div>
);
}
@@ -0,0 +1,62 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Button,
} from "@nextui-org/react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
return (
<Navbar isBordered>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}
@@ -0,0 +1,110 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
NavbarMenu,
NavbarMenuItem,
NavbarMenuToggle,
Button,
Link as NextUiLink,
LinkProps,
} from "@nextui-org/react";
import React from "react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
const [isMenuOpen, setIsMenuOpen] = React.useState<boolean | undefined>(false);
const menuItems = [
"Profile",
"Dashboard",
"Activity",
"Analytics",
"System",
"Deployments",
"My Settings",
"Team Settings",
"Help & Feedback",
"Log Out",
];
return (
<Navbar isBordered onMenuOpenChange={setIsMenuOpen}>
<NavbarContent className="sm:hidden" justify="start">
<NavbarMenuToggle aria-label={isMenuOpen ? "Close menu" : "Open menu"} />
</NavbarContent>
<NavbarContent className="sm:hidden pr-3" justify="center">
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
</NavbarContent>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" color="warning" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="warning" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
<NavbarMenu>
{menuItems.map((item, index) => (
<NavbarMenuItem key={`${item}-${index}`}>
<Link
className="w-full"
color={
index === 2 ? "warning" : index === menuItems.length - 1 ? "danger" : "foreground"
}
href="#"
size="lg"
>
{item}
</Link>
</NavbarMenuItem>
))}
</NavbarMenu>
</Navbar>
);
}
@@ -0,0 +1,79 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Button,
} from "@nextui-org/react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
return (
<Navbar
classNames={{
item: [
"flex",
"relative",
"h-full",
"items-center",
"data-[active=true]:after:content-['']",
"data-[active=true]:after:absolute",
"data-[active=true]:after:bottom-0",
"data-[active=true]:after:left-0",
"data-[active=true]:after:right-0",
"data-[active=true]:after:h-[2px]",
"data-[active=true]:after:rounded-[2px]",
"data-[active=true]:after:bg-primary",
],
}}
>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}
@@ -0,0 +1,108 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Button,
Link as NextUiLink,
LinkProps,
NavbarMenu,
NavbarMenuItem,
NavbarMenuToggle,
} from "@nextui-org/react";
import React from "react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
const menuItems = [
"Profile",
"Dashboard",
"Activity",
"Analytics",
"System",
"Deployments",
"My Settings",
"Team Settings",
"Help & Feedback",
"Log Out",
];
return (
<Navbar disableAnimation isBordered>
<NavbarContent className="sm:hidden" justify="start">
<NavbarMenuToggle />
</NavbarContent>
<NavbarContent className="sm:hidden pr-3" justify="center">
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
</NavbarContent>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" color="warning" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="warning" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
<NavbarMenu>
{menuItems.map((item, index) => (
<NavbarMenuItem key={`${item}-${index}`}>
<Link
className="w-full"
color={
index === 2 ? "warning" : index === menuItems.length - 1 ? "danger" : "foreground"
}
href="#"
size="lg"
>
{item}
</Link>
</NavbarMenuItem>
))}
</NavbarMenu>
</Navbar>
);
}
@@ -0,0 +1,62 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Button,
} from "@nextui-org/react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
return (
<Navbar isBordered isBlurred={false}>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}
@@ -0,0 +1,62 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Button,
} from "@nextui-org/react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
return (
<Navbar shouldHideOnScroll>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}
+78
View File
@@ -0,0 +1,78 @@
const Content = () => (
<div className="px-6 flex gap-4 flex-col pb-16 flex-grow">
<h1 className="mt-4 font-bold text-4xl">Lorem ipsum dolor sit amet</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Purus gravida quis blandit turpis. Augue neque gravida in
fermentum et sollicitudin ac orci. Et sollicitudin ac orci phasellus egestas. Elementum tempus
egestas sed sed risus pretium quam vulputate. Interdum velit euismod in pellentesque massa
placerat duis ultricies.
</p>
<p>
Rhoncus mattis rhoncus urna neque viverra justo nec ultrices dui. Praesent semper feugiat nibh
sed pulvinar. Ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Malesuada
proin libero nunc consequat interdum varius sit amet. Lectus quam id leo in vitae. Sed viverra
tellus in hac habitasse platea dictumst. Vivamus at augue eget arcu. Augue mauris augue neque
gravida in.
</p>
<p>
Tincidunt vitae semper quis lectus nulla at volutpat diam. Gravida dictum fusce ut placerat.
Erat velit scelerisque in dictum non. Tempus quam pellentesque nec nam aliquam sem et tortor
consequat. Eu nisl nunc mi ipsum faucibus. Cras fermentum odio eu feugiat pretium nibh. Vel
pharetra vel turpis nunc eget lorem dolor sed viverra. Sollicitudin tempor id eu nisl nunc mi
ipsum faucibus. Sed id semper risus in hendrerit gravida rutrum. Eget nulla facilisi etiam
dignissim. Erat imperdiet sed euismod nisi. Risus in hendrerit gravida rutrum quisque non
tellus orci ac.
</p>
<p>
Tempor orci dapibus ultrices in iaculis nunc sed augue lacus. In pellentesque massa placerat
duis ultricies. Sit amet massa vitae tortor condimentum. Morbi tincidunt augue interdum velit
euismod. Aliquet enim tortor at auctor urna nunc id. A scelerisque purus semper eget. Vitae
justo eget magna fermentum iaculis. Arcu non odio euismod lacinia at quis. Et leo duis ut diam
quam nulla porttitor massa. Eget nunc scelerisque viverra mauris. Suscipit tellus mauris a
diam maecenas sed enim. Cras sed felis eget velit aliquet. Est placerat in egestas erat
imperdiet sed euismod nisi porta. In ante metus dictum at tempor commodo. In cursus turpis
massa tincidunt dui ut ornare lectus. Tempus iaculis urna id volutpat. Iaculis eu non diam
phasellus vestibulum lorem sed risus.
</p>
<p>
Ridiculus mus mauris vitae ultricies leo integer malesuada nunc vel. Imperdiet massa tincidunt
nunc pulvinar sapien et ligula ullamcorper malesuada. Faucibus pulvinar elementum integer enim
neque volutpat. Gravida arcu ac tortor dignissim convallis aenean. Lectus quam id leo in
vitae. Ultricies tristique nulla aliquet enim tortor. Nec tincidunt praesent semper feugiat
nibh sed. Imperdiet proin fermentum leo vel orci porta non pulvinar neque. Praesent semper
feugiat nibh sed pulvinar proin gravida. Dis parturient montes nascetur ridiculus mus mauris.
Rhoncus dolor purus non enim praesent elementum facilisis leo vel. Ut lectus arcu bibendum at.
Integer enim neque volutpat ac. Diam sit amet nisl suscipit. Eros donec ac odio tempor orci
dapibus ultrices in iaculis. Ullamcorper a lacus vestibulum sed arcu non odio euismod. Quis
lectus nulla at volutpat diam ut. Turpis egestas integer eget aliquet. Adipiscing tristique
risus nec feugiat in fermentum posuere. Morbi tempus iaculis urna id. Amet commodo nulla
facilisi nullam vehicula ipsum a arcu.
</p>
<p>
Rhoncus mattis rhoncus urna neque viverra justo nec ultrices dui. Praesent semper feugiat nibh
sed pulvinar. Ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Malesuada
proin libero nunc consequat interdum varius sit amet. Lectus quam id leo in vitae. Sed viverra
tellus in hac habitasse platea dictumst. Vivamus at augue eget arcu. Augue mauris augue neque
gravida in.
</p>
<p>
Tincidunt vitae semper quis lectus nulla at volutpat diam. Gravida dictum fusce ut placerat.
Erat velit scelerisque in dictum non. Tempus quam pellentesque nec nam aliquam sem et tortor
consequat. Eu nisl nunc mi ipsum faucibus. Cras fermentum odio eu feugiat pretium nibh. Vel
pharetra vel turpis nunc eget lorem dolor sed viverra. Sollicitudin tempor id eu nisl nunc mi
ipsum faucibus. Sed id semper risus in hendrerit gravida rutrum. Eget nulla facilisi etiam
dignissim. Erat imperdiet sed euismod nisi. Risus in hendrerit gravida rutrum quisque non
tellus orci ac.
</p>
</div>
);
export default function NavbarExamplesLayout({children}: {children: React.ReactNode}) {
return (
<div className="flex flex-col">
{children}
<Content />
</div>
);
}
@@ -0,0 +1,62 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Button,
} from "@nextui-org/react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
return (
<Navbar position="static">
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}
@@ -0,0 +1,62 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Button,
} from "@nextui-org/react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
return (
<Navbar>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}
@@ -0,0 +1,86 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
DropdownItem,
DropdownTrigger,
Dropdown,
DropdownMenu,
Avatar,
} from "@nextui-org/react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
return (
<Navbar>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" color="secondary" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent as="div" justify="end">
<Dropdown placement="bottom-end">
<DropdownTrigger>
<Avatar
isBordered
as="button"
className="transition-transform"
color="secondary"
name="Jason Hughes"
size="sm"
src="https://i.pravatar.cc/150?u=a042581f4e29026704d"
/>
</DropdownTrigger>
<DropdownMenu aria-label="Profile Actions" variant="flat">
<DropdownItem key="profile" className="h-14 gap-2">
<p className="font-semibold">Signed in as</p>
<p className="font-semibold">zoey@example.com</p>
</DropdownItem>
<DropdownItem key="settings">My Settings</DropdownItem>
<DropdownItem key="team_settings">Team Settings</DropdownItem>
<DropdownItem key="analytics">Analytics</DropdownItem>
<DropdownItem key="system">System</DropdownItem>
<DropdownItem key="configurations">Configurations</DropdownItem>
<DropdownItem key="help_and_feedback">Help & Feedback</DropdownItem>
<DropdownItem key="logout" color="danger">
Log Out
</DropdownItem>
</DropdownMenu>
</Dropdown>
</NavbarContent>
</Navbar>
);
}
@@ -0,0 +1,130 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Button,
DropdownItem,
DropdownTrigger,
Dropdown,
DropdownMenu,
} from "@nextui-org/react";
import {ChevronDown, Lock, Activity, Flash, Server, TagUser, Scale} from "@nextui-org/shared-icons";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
const icons = {
chevron: <ChevronDown fill="currentColor" size={16} />,
scale: <Scale className="text-warning" fill="currentColor" size={30} />,
lock: <Lock className="text-success" fill="currentColor" size={30} />,
activity: <Activity className="text-secondary" fill="currentColor" size={30} />,
flash: <Flash className="text-primary" fill="currentColor" size={30} />,
server: <Server className="text-success" fill="currentColor" size={30} />,
user: <TagUser className="text-danger" fill="currentColor" size={30} />,
};
return (
<Navbar>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<Dropdown>
<NavbarItem>
<DropdownTrigger>
<Button
disableRipple
className="p-0 bg-transparent data-[hover=true]:bg-transparent"
endContent={icons.chevron}
radius="sm"
variant="light"
>
Features
</Button>
</DropdownTrigger>
</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."
startContent={icons.scale}
>
Autoscaling
</DropdownItem>
<DropdownItem
key="usage_metrics"
description="Real-time metrics to debug issues. Slow query added? Well show you exactly where."
startContent={icons.activity}
>
Usage Metrics
</DropdownItem>
<DropdownItem
key="production_ready"
description="ACME runs on ACME, join us and others serving requests 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."
startContent={icons.server}
>
+99% Uptime
</DropdownItem>
<DropdownItem
key="supreme_support"
description="Overcome any challenge with a supporting team ready to respond."
startContent={icons.user}
>
+Supreme Support
</DropdownItem>
</DropdownMenu>
</Dropdown>
<NavbarItem isActive>
<Link aria-current="page" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}
@@ -0,0 +1,104 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Button,
NavbarMenuToggle,
NavbarMenu,
NavbarMenuItem,
} from "@nextui-org/react";
import React from "react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
const [isMenuOpen, setIsMenuOpen] = React.useState<boolean | undefined>(false);
const menuItems = [
"Profile",
"Dashboard",
"Activity",
"Analytics",
"System",
"Deployments",
"My Settings",
"Team Settings",
"Help & Feedback",
"Log Out",
];
return (
<Navbar onMenuOpenChange={setIsMenuOpen}>
<NavbarContent>
<NavbarMenuToggle
aria-label={isMenuOpen ? "Close menu" : "Open menu"}
className="sm:hidden"
/>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ACME</p>
</NavbarBrand>
</NavbarContent>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
<NavbarMenu>
{menuItems.map((item, index) => (
<NavbarMenuItem key={`${item}-${index}`}>
<Link
className="w-full"
color={
index === 2 ? "primary" : index === menuItems.length - 1 ? "danger" : "foreground"
}
href="#"
size="lg"
>
{item}
</Link>
</NavbarMenuItem>
))}
</NavbarMenu>
</Navbar>
);
}
@@ -0,0 +1,104 @@
"use client";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Link as NextUiLink,
LinkProps,
Input,
DropdownItem,
DropdownTrigger,
Dropdown,
DropdownMenu,
Avatar,
} from "@nextui-org/react";
const Link = (props: LinkProps) => <NextUiLink {...props} onClick={(e) => e.preventDefault()} />;
import {SearchLinearIcon} from "@/components/icons";
const AcmeLogo = () => (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default function Page() {
return (
<Navbar isBordered>
<NavbarContent justify="start">
<NavbarBrand className="mr-4">
<AcmeLogo />
<p className="hidden sm:block font-bold text-inherit">ACME</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-3">
<NavbarItem>
<Link color="foreground" href="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" color="secondary" href="#">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
</NavbarContent>
<NavbarContent as="div" className="items-center" justify="end">
<Input
classNames={{
base: "max-w-full sm:max-w-[10rem] h-10",
input: "text-small",
inputWrapper:
"h-full font-normal text-default-500 bg-default-400/20 dark:bg-default-500/20",
}}
placeholder="Type to search..."
startContent={<SearchLinearIcon size={18} />}
type="search"
/>
<Dropdown placement="bottom-end">
<NavbarItem>
<DropdownTrigger>
<Avatar
isBordered
as="button"
className="transition-transform"
color="secondary"
name="Jason Hughes"
size="sm"
src="https://i.pravatar.cc/150?u=a042581f4e29026704d"
/>
</DropdownTrigger>
</NavbarItem>
<DropdownMenu aria-label="Profile Actions" variant="flat">
<DropdownItem key="profile" className="h-14 gap-2">
<p className="font-semibold">Signed in as</p>
<p className="font-semibold">zoey@example.com</p>
</DropdownItem>
<DropdownItem key="settings">My Settings</DropdownItem>
<DropdownItem key="team_settings">Team Settings</DropdownItem>
<DropdownItem key="analytics">Analytics</DropdownItem>
<DropdownItem key="system">System</DropdownItem>
<DropdownItem key="configurations">Configurations</DropdownItem>
<DropdownItem key="help_and_feedback">Help & Feedback</DropdownItem>
<DropdownItem key="logout" color="danger">
Log Out
</DropdownItem>
</DropdownMenu>
</Dropdown>
</NavbarContent>
</Navbar>
);
}
+259
View File
@@ -0,0 +1,259 @@
"use client";
import {
RadioGroup,
Radio,
Button,
Accordion,
Tabs,
Textarea,
Input,
Tab,
AccordionItem,
extendVariants,
} from "@nextui-org/react";
import {useState} from "react";
import {SearchLinearIcon} from "@/components/icons";
const MyRadioGroup = () => {
const [radio, setRadio] = useState("1");
return (
<RadioGroup value={radio} onValueChange={setRadio}>
<Radio value="1">Radio 1</Radio>
<Radio value="2">Radio 2</Radio>
<Radio value="3">Radio 3</Radio>
<Radio value="4">Radio 4</Radio>
<Radio value="5">Radio 5</Radio>
<Radio value="6">Radio 6</Radio>
<Radio value="7">Radio 7</Radio>
<Radio value="8">Radio 8</Radio>
<Radio value="9">Radio 9</Radio>
<Radio value="10">Radio 10</Radio>
<Radio value="11">Radio 11</Radio>
<Radio value="12">Radio 12</Radio>
<Radio value="13">Radio 13</Radio>
<Radio value="14">Radio 14</Radio>
<Radio value="15">Radio 15</Radio>
<Radio value="16">Radio 16</Radio>
<Radio value="17">Radio 17</Radio>
<Radio value="18">Radio 18</Radio>
<Radio value="19">Radio 19</Radio>
<Radio value="20">Radio 20</Radio>
<Radio value="21">Radio 21</Radio>
<Radio value="22">Radio 22</Radio>
<Radio value="23">Radio 23</Radio>
<Radio value="24">Radio 24</Radio>
<Radio value="25">Radio 25</Radio>
<Radio value="26">Radio 26</Radio>
<Radio value="27">Radio 27</Radio>
<Radio value="28">Radio 28</Radio>
<Radio value="29">Radio 29</Radio>
<Radio value="30">Radio 30</Radio>
<Radio value="31">Radio 31</Radio>
<Radio value="32">Radio 32</Radio>
<Radio value="33">Radio 33</Radio>
<Radio value="34">Radio 34</Radio>
<Radio value="35">Radio 35</Radio>
<Radio value="36">Radio 36</Radio>
<Radio value="37">Radio 37</Radio>
<Radio value="38">Radio 38</Radio>
<Radio value="39">Radio 39</Radio>
<Radio value="40">Radio 40</Radio>
<Radio value="41">Radio 41</Radio>
<Radio value="42">Radio 42</Radio>
<Radio value="43">Radio 43</Radio>
<Radio value="44">Radio 44</Radio>
<Radio value="45">Radio 45</Radio>
<Radio value="46">Radio 46</Radio>
<Radio value="47">Radio 47</Radio>
<Radio value="48">Radio 48</Radio>
<Radio value="49">Radio 49</Radio>
<Radio value="50">Radio 50</Radio>
<Radio value="51">Radio 51</Radio>
<Radio value="52">Radio 52</Radio>
<Radio value="53">Radio 53</Radio>
<Radio value="54">Radio 54</Radio>
<Radio value="55">Radio 55</Radio>
<Radio value="56">Radio 56</Radio>
<Radio value="57">Radio 57</Radio>
<Radio value="58">Radio 58</Radio>
<Radio value="59">Radio 59</Radio>
<Radio value="60">Radio 60</Radio>
<Radio value="61">Radio 61</Radio>
<Radio value="62">Radio 62</Radio>
</RadioGroup>
);
};
const MyInput = extendVariants(Input, {
variants: {
color: {
stone: {
inputWrapper: [
"bg-zinc-100",
"border",
"shadow",
"transition-colors",
"focus-within:bg-zinc-100",
"data-[hover=true]:border-zinc-600",
"data-[hover=true]:bg-zinc-100",
"group-data-[focus=true]:border-zinc-600",
// dark theme
"dark:bg-zinc-900",
"dark:border-zinc-800",
"dark:data-[hover=true]:bg-zinc-900",
"dark:focus-within:bg-zinc-900",
],
input: [
"text-zinc-800",
"placeholder:text-zinc-600",
// dark theme
"dark:text-zinc-400",
"dark:placeholder:text-zinc-600",
],
},
},
size: {
xs: {
inputWrapper: "h-unit-6 min-h-unit-6 px-1",
input: "text-tiny",
},
md: {
inputWrapper: "h-unit-10 min-h-unit-10",
input: "text-small",
},
xl: {
inputWrapper: "h-unit-14 min-h-unit-14",
input: "text-medium",
},
},
radius: {
xs: {
inputWrapper: "rounded",
},
sm: {
inputWrapper: "rounded-[4px]",
},
},
textSize: {
base: {
input: "text-base",
},
},
removeLabel: {
true: {
label: "hidden",
},
false: {},
},
},
defaultVariants: {
color: "stone",
textSize: "base",
removeLabel: true,
},
});
const MyButton2 = extendVariants(Button, {
variants: {
color: {
foreground:
"bg-foreground text-background data-[hover=true]:bg-foreground/90 data-[pressed=true]:bg-foreground/80",
},
isScalable: {
true: "scale-125",
false: "",
},
size: {
xl: "size--xl",
"2xl": "size--2xl",
},
mySize: {
lg: "px-12 py-6 text-lg",
xl: "px-12 py-6 text-xl",
},
},
defaultVariants: {},
});
export default function NextUIPerf() {
const [textA, setTextA] = useState<string>("");
const [textB, setTextB] = useState<string>("");
const [textC, setTextC] = useState<string>("");
return (
<div className="w-full p-24 gap-4 flex flex-col">
<Accordion>
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
cupidatat voluptate eu officia. Ut laborum sunt nostrud magna. Ex magna esse cillum enim
incididunt pariatur qui veniam dolor. Exercitation id culpa et enim mollit duis duis
aliquip. Magna ullamco est cupidatat laboris irure pariatur aliquip duis aute cillum.
Officia irure do laboris ea nisi sunt reprehenderit laboris irure. Ex eiusmod in duis
veniam excepteur. Sunt et et laboris culpa. Mollit excepteur occaecat elit anim officia.
Laborum commodo proident cupidatat pariatur eu veniam id qui do culpa. Quis consectetur
adipisicing anim ex ea velit excepteur. Deserunt laboris ex aute sunt laborum tempor ea
enim dolore ut in. Id aliqua Lorem exercitation qui velit nostrud anim reprehenderit enim.
Nisi elit fugiat deserunt elit. Sit excepteur ipsum enim excepteur irure irure sint veniam
elit consequat ea id. Lorem ea qui sunt enim occaecat excepteur officia ex consequat
nostrud. Tempor sint Lorem est culpa do.
</AccordionItem>
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
cupidatat voluptate eu officia. Ut laborum sunt nostrud magna. Ex magna esse cillum enim
</AccordionItem>
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
</AccordionItem>
</Accordion>
<Tabs classNames={{panel: "flex flex-col gap-5"}} variant="underlined">
<Tab title="Test 1">
<Textarea defaultValue="ASdasd" label="Default value (uncontrolled)" />
<Input label="Text B Tab 1" value={textB} onValueChange={setTextB} />
<Textarea label="Text C Tab 1" value={textC} onValueChange={setTextC} />
</Tab>
<Tab title="Test 2">
<Textarea label="Text B Tab 2" value={textB} onValueChange={setTextB} />
<Textarea label="Text C Tab 2" value={textC} onValueChange={setTextC} />
</Tab>
<Tab title="Test 3">
<Textarea label="Text B Tab 3" value={textB} onValueChange={setTextB} />
<Textarea label="Text C Tab 3" value={textC} onValueChange={setTextC} />
</Tab>
</Tabs>
<h2>Outside</h2>
<Textarea label="Text A" placeholder="Text A" value={textA} onValueChange={setTextA} />
<Textarea label="Text B" placeholder="Text B" value={textB} onValueChange={setTextB} />
<Textarea label="Text C" placeholder="Text C" value={textC} onValueChange={setTextC} />
<MyRadioGroup />
<MyInput
isClearable
placeholder="Search..."
radius="md"
size="md"
startContent={<SearchLinearIcon className="text-zinc-500" size={16} />}
/>
<Button>Click Me!</Button>
<MyButton2 color="foreground">Press Me!</MyButton2>
</div>
);
}
@@ -0,0 +1,113 @@
/* eslint-disable no-console */
"use client";
import {Select, SelectItem} from "@nextui-org/react";
import {useEffect, useState} from "react";
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
type Pokemon = {
name: string;
url: string;
};
type UsePokemonListProps = {
/** Delay to wait before fetching more items */
fetchDelay?: number;
};
function usePokemonList({fetchDelay = 0}: UsePokemonListProps = {}) {
const [items, setItems] = useState<Pokemon[]>([]);
const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(false);
const [offset, setOffset] = useState(0);
const limit = 10; // Number of items per page, adjust as necessary
const loadPokemon = async (currentOffset: number) => {
const controller = new AbortController();
const {signal} = controller;
try {
setIsLoading(true);
if (offset > 0) {
// Delay to simulate network latency
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
}
let res = await fetch(
`https://pokeapi.co/api/v2/pokemon?offset=${currentOffset}&limit=${limit}`,
{signal},
);
if (!res.ok) {
throw new Error("Network response was not ok");
}
let json = await res.json();
setHasMore(json.next !== null);
// Append new results to existing ones
setItems((prevItems) => [...prevItems, ...json.results]);
} catch (error) {
// @ts-ignore
if (error.name === "AbortError") {
console.log("Fetch aborted");
} else {
console.error("There was an error with the fetch operation:", error);
}
} finally {
setIsLoading(false);
}
};
useEffect(() => {
loadPokemon(offset);
}, []);
const onLoadMore = () => {
const newOffset = offset + limit;
setOffset(newOffset);
loadPokemon(newOffset);
};
return {
items,
hasMore,
isLoading,
onLoadMore,
};
}
export default function Page() {
const [isOpen, setIsOpen] = useState(false);
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});
const [, scrollerRef] = useInfiniteScroll({
hasMore,
isEnabled: isOpen,
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
onLoadMore,
});
return (
<div className="p-6">
<Select
className="max-w-xs"
isLoading={isLoading}
items={items}
label="Pick a Pokemon"
placeholder="Select a Pokemon"
scrollRef={scrollerRef}
selectionMode="single"
onOpenChange={setIsOpen}
>
{(item) => (
<SelectItem key={item.name} className="capitalize">
{item.name}
</SelectItem>
)}
</Select>
</div>
);
}
@@ -0,0 +1,112 @@
"use client";
import {
Table,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
getKeyValue,
Spinner,
Pagination,
} from "@nextui-org/react";
import {useAsyncList} from "@react-stately/data";
import {useCallback, useMemo, useState} from "react";
type SWCharacter = {
name: string;
height: string;
mass: string;
birth_year: string;
};
export default function Page() {
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const rowsPerPage = 10;
let list = useAsyncList<SWCharacter>({
async load({signal, cursor}) {
// If no cursor is available, then we're loading the first page.
// Otherwise, the cursor is the next URL to load, as returned from the previous page.
const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
let json = await res.json();
setTotal(json.count);
setIsLoading(false);
return {
items: json.results,
cursor: json.next,
};
},
});
const pages = Math.ceil(total / rowsPerPage);
const items = useMemo(() => {
const start = (page - 1) * rowsPerPage;
const end = start + rowsPerPage;
return list.items.slice(start, end);
}, [page, list.items?.length]);
const onPaginationChange = useCallback(
(page: number) => {
setIsLoading(true);
if (page >= list.items.length / rowsPerPage) {
list.loadMore();
}
setPage(page);
},
[list.items.length],
);
return (
<div className="p-6">
<Table
aria-label="Example table with client async pagination"
bottomContent={
pages > 0 ? (
<div className="flex w-full justify-center">
<Pagination
isCompact
showControls
showShadow
color="primary"
page={page}
total={pages}
onChange={onPaginationChange}
/>
</div>
) : null
}
classNames={{
table: "min-h-[400px]",
}}
>
<TableHeader>
<TableColumn key="name">Name</TableColumn>
<TableColumn key="height">Height</TableColumn>
<TableColumn key="mass">Mass</TableColumn>
<TableColumn key="birth_year">Birth year</TableColumn>
</TableHeader>
<TableBody
isLoading={isLoading && !items.length}
items={items}
loadingContent={<Spinner />}
>
{(item) => (
<TableRow key={item.name}>
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
@@ -0,0 +1,579 @@
/* eslint-disable jsx-a11y/no-onchange */
"use client";
import {
Table,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
Input,
Button,
DropdownTrigger,
Dropdown,
DropdownMenu,
DropdownItem,
Selection,
Chip,
User,
ChipProps,
Pagination,
SortDescriptor,
} from "@nextui-org/react";
import {ChevronDownIcon, SearchIcon} from "@nextui-org/shared-icons";
import {useCallback, useMemo, useState} from "react";
import {capitalize} from "lodash";
import {PlusLinearIcon} from "@/components/icons";
import {VerticalDotsIcon} from "@/components/icons/vertical-dots";
const statusColorMap: Record<string, ChipProps["color"]> = {
active: "success",
paused: "danger",
vacation: "warning",
};
const columns = [
{name: "ID", uid: "id", sortable: true},
{name: "NAME", uid: "name", sortable: true},
{name: "AGE", uid: "age", sortable: true},
{name: "ROLE", uid: "role", sortable: true},
{name: "TEAM", uid: "team"},
{name: "EMAIL", uid: "email"},
{name: "STATUS", uid: "status", sortable: true},
{name: "ACTIONS", uid: "actions"},
];
const statusOptions = [
{name: "Active", uid: "active"},
{name: "Paused", uid: "paused"},
{name: "Vacation", uid: "vacation"},
];
const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"];
const users = [
{
id: 1,
name: "Tony Reichert",
role: "CEO",
team: "Management",
status: "active",
age: "29",
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d",
email: "tony.reichert@example.com",
},
{
id: 2,
name: "Zoey Lang",
role: "Tech Lead",
team: "Development",
status: "paused",
age: "25",
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026704d",
email: "zoey.lang@example.com",
},
{
id: 3,
name: "Jane Fisher",
role: "Sr. Dev",
team: "Development",
status: "active",
age: "22",
avatar: "https://i.pravatar.cc/150?u=a04258114e29026702d",
email: "jane.fisher@example.com",
},
{
id: 4,
name: "William Howard",
role: "C.M.",
team: "Marketing",
status: "vacation",
age: "28",
avatar: "https://i.pravatar.cc/150?u=a048581f4e29026701d",
email: "william.howard@example.com",
},
{
id: 5,
name: "Kristen Copper",
role: "S. Manager",
team: "Sales",
status: "active",
age: "24",
avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d",
email: "kristen.cooper@example.com",
},
{
id: 6,
name: "Brian Kim",
role: "P. Manager",
team: "Management",
age: "29",
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d",
email: "brian.kim@example.com",
status: "Active",
},
{
id: 7,
name: "Michael Hunt",
role: "Designer",
team: "Design",
status: "paused",
age: "27",
avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d",
email: "michael.hunt@example.com",
},
{
id: 8,
name: "Samantha Brooks",
role: "HR Manager",
team: "HR",
status: "active",
age: "31",
avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d",
email: "samantha.brooks@example.com",
},
{
id: 9,
name: "Frank Harrison",
role: "F. Manager",
team: "Finance",
status: "vacation",
age: "33",
avatar: "https://i.pravatar.cc/150?img=4",
email: "frank.harrison@example.com",
},
{
id: 10,
name: "Emma Adams",
role: "Ops Manager",
team: "Operations",
status: "active",
age: "35",
avatar: "https://i.pravatar.cc/150?img=5",
email: "emma.adams@example.com",
},
{
id: 11,
name: "Brandon Stevens",
role: "Jr. Dev",
team: "Development",
status: "active",
age: "22",
avatar: "https://i.pravatar.cc/150?img=8",
email: "brandon.stevens@example.com",
},
{
id: 12,
name: "Megan Richards",
role: "P. Manager",
team: "Product",
status: "paused",
age: "28",
avatar: "https://i.pravatar.cc/150?img=10",
email: "megan.richards@example.com",
},
{
id: 13,
name: "Oliver Scott",
role: "S. Manager",
team: "Security",
status: "active",
age: "37",
avatar: "https://i.pravatar.cc/150?img=12",
email: "oliver.scott@example.com",
},
{
id: 14,
name: "Grace Allen",
role: "M. Specialist",
team: "Marketing",
status: "active",
age: "30",
avatar: "https://i.pravatar.cc/150?img=16",
email: "grace.allen@example.com",
},
{
id: 15,
name: "Noah Carter",
role: "IT Specialist",
team: "I. Technology",
status: "paused",
age: "31",
avatar: "https://i.pravatar.cc/150?img=15",
email: "noah.carter@example.com",
},
{
id: 16,
name: "Ava Perez",
role: "Manager",
team: "Sales",
status: "active",
age: "29",
avatar: "https://i.pravatar.cc/150?img=20",
email: "ava.perez@example.com",
},
{
id: 17,
name: "Liam Johnson",
role: "Data Analyst",
team: "Analysis",
status: "active",
age: "28",
avatar: "https://i.pravatar.cc/150?img=33",
email: "liam.johnson@example.com",
},
{
id: 18,
name: "Sophia Taylor",
role: "QA Analyst",
team: "Testing",
status: "active",
age: "27",
avatar: "https://i.pravatar.cc/150?img=29",
email: "sophia.taylor@example.com",
},
{
id: 19,
name: "Lucas Harris",
role: "Administrator",
team: "Information Technology",
status: "paused",
age: "32",
avatar: "https://i.pravatar.cc/150?img=50",
email: "lucas.harris@example.com",
},
{
id: 20,
name: "Mia Robinson",
role: "Coordinator",
team: "Operations",
status: "active",
age: "26",
avatar: "https://i.pravatar.cc/150?img=45",
email: "mia.robinson@example.com",
},
];
type User = typeof users[0];
export default function Page() {
const [filterValue, setFilterValue] = useState("");
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
const [visibleColumns, setVisibleColumns] = useState<Selection>(new Set(INITIAL_VISIBLE_COLUMNS));
const [statusFilter, setStatusFilter] = useState<Selection>("all");
const [rowsPerPage, setRowsPerPage] = useState(5);
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
column: "age",
direction: "ascending",
});
const [page, setPage] = useState(1);
const pages = Math.ceil(users.length / rowsPerPage);
const hasSearchFilter = Boolean(filterValue);
const headerColumns = useMemo(() => {
if (visibleColumns === "all") return columns;
return columns.filter((column) => Array.from(visibleColumns).includes(column.uid));
}, [visibleColumns]);
const filteredItems = useMemo(() => {
let filteredUsers = [...users];
if (hasSearchFilter) {
filteredUsers = filteredUsers.filter((user) =>
user.name.toLowerCase().includes(filterValue.toLowerCase()),
);
}
if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) {
filteredUsers = filteredUsers.filter((user) =>
Array.from(statusFilter).includes(user.status),
);
}
return filteredUsers;
}, [users, filterValue, statusFilter]);
const items = useMemo(() => {
const start = (page - 1) * rowsPerPage;
const end = start + rowsPerPage;
return filteredItems.slice(start, end);
}, [page, filteredItems, rowsPerPage]);
const sortedItems = useMemo(() => {
return [...items].sort((a: User, b: User) => {
const first = a[sortDescriptor.column as keyof User] as number;
const second = b[sortDescriptor.column as keyof User] as number;
const cmp = first < second ? -1 : first > second ? 1 : 0;
return sortDescriptor.direction === "descending" ? -cmp : cmp;
});
}, [sortDescriptor, items]);
const renderCell = useCallback((user: User, columnKey: React.Key) => {
const cellValue = user[columnKey as keyof User];
switch (columnKey) {
case "name":
return (
<User
avatarProps={{radius: "full", size: "sm", src: user.avatar}}
classNames={{
description: "text-default-500",
}}
description={user.email}
name={cellValue}
>
{user.email}
</User>
);
case "role":
return (
<div className="flex flex-col">
<p className="text-bold text-small capitalize">{cellValue}</p>
<p className="text-bold text-tiny capitalize text-default-500">{user.team}</p>
</div>
);
case "status":
return (
<Chip
className="capitalize border-none gap-1 text-default-600"
color={statusColorMap[user.status]}
size="sm"
variant="dot"
>
{cellValue}
</Chip>
);
case "actions":
return (
<div className="relative flex justify-end items-center gap-2">
<Dropdown className="bg-background border-1 border-default-200">
<DropdownTrigger>
<Button isIconOnly radius="full" size="sm" variant="light">
<VerticalDotsIcon className="text-default-400" />
</Button>
</DropdownTrigger>
<DropdownMenu>
<DropdownItem>View</DropdownItem>
<DropdownItem>Edit</DropdownItem>
<DropdownItem>Delete</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
);
default:
return cellValue;
}
}, []);
const onRowsPerPageChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
setRowsPerPage(Number(e.target.value));
setPage(1);
}, []);
const onSearchChange = useCallback((value?: string) => {
if (value) {
setFilterValue(value);
setPage(1);
} else {
setFilterValue("");
}
}, []);
const topContent = useMemo(() => {
return (
<div className="flex flex-col gap-4">
<div className="flex justify-between gap-3 items-end">
<Input
isClearable
classNames={{
base: "w-full sm:max-w-[44%]",
inputWrapper: "border-1",
}}
placeholder="Search by name..."
size="sm"
startContent={<SearchIcon className="text-default-300" />}
value={filterValue}
variant="bordered"
onClear={() => setFilterValue("")}
onValueChange={onSearchChange}
/>
<div className="flex gap-3">
<Dropdown>
<DropdownTrigger className="hidden sm:flex">
<Button
endContent={<ChevronDownIcon className="text-small" />}
size="sm"
variant="flat"
>
Status
</Button>
</DropdownTrigger>
<DropdownMenu
disallowEmptySelection
aria-label="Table Columns"
closeOnSelect={false}
selectedKeys={statusFilter}
selectionMode="multiple"
onSelectionChange={setStatusFilter}
>
{statusOptions.map((status) => (
<DropdownItem key={status.uid} className="capitalize">
{capitalize(status.name)}
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
<Dropdown>
<DropdownTrigger className="hidden sm:flex">
<Button
endContent={<ChevronDownIcon className="text-small" />}
size="sm"
variant="flat"
>
Columns
</Button>
</DropdownTrigger>
<DropdownMenu
disallowEmptySelection
aria-label="Table Columns"
closeOnSelect={false}
selectedKeys={visibleColumns}
selectionMode="multiple"
onSelectionChange={setVisibleColumns}
>
{columns.map((column) => (
<DropdownItem key={column.uid} className="capitalize">
{capitalize(column.name)}
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
<Button
className="bg-foreground text-background"
endContent={<PlusLinearIcon />}
size="sm"
>
Add New
</Button>
</div>
</div>
<div className="flex justify-between items-center">
<span className="text-default-400 text-small">Total {users.length} users</span>
<label className="flex items-center text-default-400 text-small">
Rows per page:
<select
className="bg-transparent outline-none text-default-400 text-small"
onChange={onRowsPerPageChange}
>
<option value="5">5</option>
<option value="10">10</option>
<option value="15">15</option>
</select>
</label>
</div>
</div>
);
}, [
filterValue,
statusFilter,
visibleColumns,
onSearchChange,
onRowsPerPageChange,
users.length,
hasSearchFilter,
]);
const bottomContent = useMemo(() => {
return (
<div className="py-2 px-2 flex justify-between items-center">
<Pagination
showControls
classNames={{
cursor: "bg-foreground text-background",
}}
color="default"
isDisabled={hasSearchFilter}
page={page}
total={pages}
variant="light"
onChange={setPage}
/>
<span className="text-small text-default-400">
{selectedKeys === "all"
? "All items selected"
: `${selectedKeys.size} of ${items.length} selected`}
</span>
</div>
);
}, [selectedKeys, items.length, page, pages, hasSearchFilter]);
const classNames = useMemo(
() => ({
wrapper: ["max-h-[382px]", "max-w-3xl"],
th: ["bg-transparent", "text-default-500", "border-b", "border-divider"],
td: [
// changing the rows border radius
// first
"group-data-[first=true]:first:before:rounded-none",
"group-data-[first=true]:last:before:rounded-none",
// middle
"group-data-[middle=true]:before:rounded-none",
// last
"group-data-[last=true]:first:before:rounded-none",
"group-data-[last=true]:last:before:rounded-none",
],
}),
[],
);
return (
<div className="p-6">
<Table
isCompact
removeWrapper
aria-label="Example table with custom cells, pagination and sorting"
bottomContent={bottomContent}
bottomContentPlacement="outside"
checkboxesProps={{
classNames: {
wrapper: "after:bg-foreground after:text-background text-background",
},
}}
classNames={classNames}
selectedKeys={selectedKeys}
selectionMode="multiple"
sortDescriptor={sortDescriptor}
topContent={topContent}
topContentPlacement="outside"
onSelectionChange={setSelectedKeys}
onSortChange={setSortDescriptor}
>
<TableHeader columns={headerColumns}>
{(column) => (
<TableColumn
key={column.uid}
align={column.uid === "actions" ? "center" : "start"}
allowsSorting={column.sortable}
>
{column.name}
</TableColumn>
)}
</TableHeader>
<TableBody emptyContent={"No users found"} items={sortedItems}>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => <TableCell>{renderCell(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
@@ -0,0 +1,87 @@
"use client";
import {
Table,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
getKeyValue,
Spinner,
} from "@nextui-org/react";
import {useAsyncList} from "@react-stately/data";
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
import {useState} from "react";
type SWCharacter = {
name: string;
height: string;
mass: string;
birth_year: string;
};
export default function Page() {
const [isLoading, setIsLoading] = useState(true);
const [hasMore, setHasMore] = useState(false);
let list = useAsyncList<SWCharacter>({
async load({signal, cursor}) {
if (cursor) {
setIsLoading(false);
}
// If no cursor is available, then we're loading the first page.
// Otherwise, the cursor is the next URL to load, as returned from the previous page.
const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
let json = await res.json();
setHasMore(json.next !== null);
return {
items: json.results,
cursor: json.next,
};
},
});
const [loaderRef, scrollerRef] = useInfiniteScroll({hasMore, onLoadMore: list.loadMore});
return (
<div className="p-6">
<Table
isHeaderSticky
aria-label="Example table with infinite pagination"
baseRef={scrollerRef}
bottomContent={
hasMore ? (
<div className="flex w-full justify-center">
<Spinner ref={loaderRef} color="white" />
</div>
) : null
}
classNames={{
base: "max-h-[520px] overflow-scroll",
table: "min-h-[400px]",
}}
>
<TableHeader>
<TableColumn key="name">Name</TableColumn>
<TableColumn key="height">Height</TableColumn>
<TableColumn key="mass">Mass</TableColumn>
<TableColumn key="birth_year">Birth year</TableColumn>
</TableHeader>
<TableBody
isLoading={isLoading}
items={list.items}
loadingContent={<Spinner color="white" />}
>
{(item) => (
<TableRow key={item.name}>
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
@@ -0,0 +1,92 @@
"use client";
import {
Table,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
getKeyValue,
Spinner,
Button,
} from "@nextui-org/react";
import {useAsyncList} from "@react-stately/data";
import {useState} from "react";
type SWCharacter = {
name: string;
height: string;
mass: string;
birth_year: string;
};
export default function Page() {
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(true);
let list = useAsyncList<SWCharacter>({
async load({signal, cursor}) {
if (cursor) {
setPage((prev) => prev + 1);
}
// If no cursor is available, then we're loading the first page.
// Otherwise, the cursor is the next URL to load, as returned from the previous page.
const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
let json = await res.json();
if (!cursor) {
setIsLoading(false);
}
return {
items: json.results,
cursor: json.next,
};
},
});
const hasMore = page < 9;
return (
<div className="p-6">
<Table
isHeaderSticky
aria-label="Example table with client side sorting"
bottomContent={
hasMore && !isLoading ? (
<div className="flex w-full justify-center">
<Button isDisabled={list.isLoading} variant="flat" onPress={list.loadMore}>
{list.isLoading && <Spinner color="white" size="sm" />}
Load More
</Button>
</div>
) : null
}
classNames={{
base: "max-h-[520px] overflow-scroll",
table: "min-h-[420px]",
}}
>
<TableHeader>
<TableColumn key="name">Name</TableColumn>
<TableColumn key="height">Height</TableColumn>
<TableColumn key="mass">Mass</TableColumn>
<TableColumn key="birth_year">Birth year</TableColumn>
</TableHeader>
<TableBody
isLoading={isLoading}
items={list.items}
loadingContent={<Spinner label="Loading..." />}
>
{(item) => (
<TableRow key={item.name}>
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
@@ -0,0 +1,94 @@
"use client";
import {
Table,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
getKeyValue,
Spinner,
} from "@nextui-org/react";
import {useAsyncList} from "@react-stately/data";
import {useState} from "react";
type SWCharacter = {
name: string;
height: string;
mass: string;
birth_year: string;
};
export default function Page() {
const [isLoading, setIsLoading] = useState(true);
let list = useAsyncList<SWCharacter>({
async load({signal}) {
let res = await fetch(`https://swapi.py4e.com/api/people/?search`, {
signal,
});
let json = await res.json();
setIsLoading(false);
return {
items: json.results,
};
},
async sort({items, sortDescriptor}) {
return {
items: items.sort((a, b) => {
let first = a[sortDescriptor.column as keyof SWCharacter];
let second = b[sortDescriptor.column as keyof SWCharacter];
let cmp = (parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1;
if (sortDescriptor.direction === "descending") {
cmp *= -1;
}
return cmp;
}),
};
},
});
return (
<div className="p-6">
<Table
aria-label="Example table with client side sorting"
classNames={{
table: "min-h-[400px]",
}}
sortDescriptor={list.sortDescriptor}
onSortChange={list.sort}
>
<TableHeader>
<TableColumn key="name" allowsSorting>
Name
</TableColumn>
<TableColumn key="height" allowsSorting>
Height
</TableColumn>
<TableColumn key="mass" allowsSorting>
Mass
</TableColumn>
<TableColumn key="birth_year" allowsSorting>
Birth year
</TableColumn>
</TableHeader>
<TableBody
isLoading={isLoading}
items={list.items}
loadingContent={<Spinner label="Loading..." />}
>
{(item) => (
<TableRow key={item.name}>
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
@@ -0,0 +1,553 @@
/* eslint-disable jsx-a11y/no-onchange */
"use client";
import {
Table,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
Input,
Button,
DropdownTrigger,
Dropdown,
DropdownMenu,
DropdownItem,
Selection,
Chip,
User,
ChipProps,
Pagination,
SortDescriptor,
} from "@nextui-org/react";
import {ChevronDownIcon, SearchIcon} from "@nextui-org/shared-icons";
import {useCallback, useMemo, useState} from "react";
import {capitalize} from "lodash";
import {PlusLinearIcon} from "@/components/icons";
import {VerticalDotsIcon} from "@/components/icons/vertical-dots";
const statusColorMap: Record<string, ChipProps["color"]> = {
active: "success",
paused: "danger",
vacation: "warning",
};
const columns = [
{name: "ID", uid: "id", sortable: true},
{name: "NAME", uid: "name", sortable: true},
{name: "AGE", uid: "age", sortable: true},
{name: "ROLE", uid: "role", sortable: true},
{name: "TEAM", uid: "team"},
{name: "EMAIL", uid: "email"},
{name: "STATUS", uid: "status", sortable: true},
{name: "ACTIONS", uid: "actions"},
];
const statusOptions = [
{name: "Active", uid: "active"},
{name: "Paused", uid: "paused"},
{name: "Vacation", uid: "vacation"},
];
const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"];
const users = [
{
id: 1,
name: "Tony Reichert",
role: "CEO",
team: "Management",
status: "active",
age: "29",
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d",
email: "tony.reichert@example.com",
},
{
id: 2,
name: "Zoey Lang",
role: "Tech Lead",
team: "Development",
status: "paused",
age: "25",
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026704d",
email: "zoey.lang@example.com",
},
{
id: 3,
name: "Jane Fisher",
role: "Sr. Dev",
team: "Development",
status: "active",
age: "22",
avatar: "https://i.pravatar.cc/150?u=a04258114e29026702d",
email: "jane.fisher@example.com",
},
{
id: 4,
name: "William Howard",
role: "C.M.",
team: "Marketing",
status: "vacation",
age: "28",
avatar: "https://i.pravatar.cc/150?u=a048581f4e29026701d",
email: "william.howard@example.com",
},
{
id: 5,
name: "Kristen Copper",
role: "S. Manager",
team: "Sales",
status: "active",
age: "24",
avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d",
email: "kristen.cooper@example.com",
},
{
id: 6,
name: "Brian Kim",
role: "P. Manager",
team: "Management",
age: "29",
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d",
email: "brian.kim@example.com",
status: "Active",
},
{
id: 7,
name: "Michael Hunt",
role: "Designer",
team: "Design",
status: "paused",
age: "27",
avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d",
email: "michael.hunt@example.com",
},
{
id: 8,
name: "Samantha Brooks",
role: "HR Manager",
team: "HR",
status: "active",
age: "31",
avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d",
email: "samantha.brooks@example.com",
},
{
id: 9,
name: "Frank Harrison",
role: "F. Manager",
team: "Finance",
status: "vacation",
age: "33",
avatar: "https://i.pravatar.cc/150?img=4",
email: "frank.harrison@example.com",
},
{
id: 10,
name: "Emma Adams",
role: "Ops Manager",
team: "Operations",
status: "active",
age: "35",
avatar: "https://i.pravatar.cc/150?img=5",
email: "emma.adams@example.com",
},
{
id: 11,
name: "Brandon Stevens",
role: "Jr. Dev",
team: "Development",
status: "active",
age: "22",
avatar: "https://i.pravatar.cc/150?img=8",
email: "brandon.stevens@example.com",
},
{
id: 12,
name: "Megan Richards",
role: "P. Manager",
team: "Product",
status: "paused",
age: "28",
avatar: "https://i.pravatar.cc/150?img=10",
email: "megan.richards@example.com",
},
{
id: 13,
name: "Oliver Scott",
role: "S. Manager",
team: "Security",
status: "active",
age: "37",
avatar: "https://i.pravatar.cc/150?img=12",
email: "oliver.scott@example.com",
},
{
id: 14,
name: "Grace Allen",
role: "M. Specialist",
team: "Marketing",
status: "active",
age: "30",
avatar: "https://i.pravatar.cc/150?img=16",
email: "grace.allen@example.com",
},
{
id: 15,
name: "Noah Carter",
role: "IT Specialist",
team: "I. Technology",
status: "paused",
age: "31",
avatar: "https://i.pravatar.cc/150?img=15",
email: "noah.carter@example.com",
},
{
id: 16,
name: "Ava Perez",
role: "Manager",
team: "Sales",
status: "active",
age: "29",
avatar: "https://i.pravatar.cc/150?img=20",
email: "ava.perez@example.com",
},
{
id: 17,
name: "Liam Johnson",
role: "Data Analyst",
team: "Analysis",
status: "active",
age: "28",
avatar: "https://i.pravatar.cc/150?img=33",
email: "liam.johnson@example.com",
},
{
id: 18,
name: "Sophia Taylor",
role: "QA Analyst",
team: "Testing",
status: "active",
age: "27",
avatar: "https://i.pravatar.cc/150?img=29",
email: "sophia.taylor@example.com",
},
{
id: 19,
name: "Lucas Harris",
role: "Administrator",
team: "Information Technology",
status: "paused",
age: "32",
avatar: "https://i.pravatar.cc/150?img=50",
email: "lucas.harris@example.com",
},
{
id: 20,
name: "Mia Robinson",
role: "Coordinator",
team: "Operations",
status: "active",
age: "26",
avatar: "https://i.pravatar.cc/150?img=45",
email: "mia.robinson@example.com",
},
];
type User = typeof users[0];
export default function Page() {
const [filterValue, setFilterValue] = useState("");
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
const [visibleColumns, setVisibleColumns] = useState<Selection>(new Set(INITIAL_VISIBLE_COLUMNS));
const [statusFilter, setStatusFilter] = useState<Selection>("all");
const [rowsPerPage, setRowsPerPage] = useState(5);
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
column: "age",
direction: "ascending",
});
const [page, setPage] = useState(1);
const hasSearchFilter = Boolean(filterValue);
const headerColumns = useMemo(() => {
if (visibleColumns === "all") return columns;
return columns.filter((column) => Array.from(visibleColumns).includes(column.uid));
}, [visibleColumns]);
const filteredItems = useMemo(() => {
let filteredUsers = [...users];
if (hasSearchFilter) {
filteredUsers = filteredUsers.filter((user) =>
user.name.toLowerCase().includes(filterValue.toLowerCase()),
);
}
if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) {
filteredUsers = filteredUsers.filter((user) =>
Array.from(statusFilter).includes(user.status),
);
}
return filteredUsers;
}, [users, filterValue, statusFilter]);
const pages = Math.ceil(filteredItems.length / rowsPerPage);
const items = useMemo(() => {
const start = (page - 1) * rowsPerPage;
const end = start + rowsPerPage;
return filteredItems.slice(start, end);
}, [page, filteredItems, rowsPerPage]);
const sortedItems = useMemo(() => {
return [...items].sort((a: User, b: User) => {
const first = a[sortDescriptor.column as keyof User] as number;
const second = b[sortDescriptor.column as keyof User] as number;
const cmp = first < second ? -1 : first > second ? 1 : 0;
return sortDescriptor.direction === "descending" ? -cmp : cmp;
});
}, [sortDescriptor, items]);
const renderCell = useCallback((user: User, columnKey: React.Key) => {
const cellValue = user[columnKey as keyof User];
switch (columnKey) {
case "name":
return (
<User
avatarProps={{radius: "lg", src: user.avatar}}
description={user.email}
name={cellValue}
>
{user.email}
</User>
);
case "role":
return (
<div className="flex flex-col">
<p className="text-bold text-small capitalize">{cellValue}</p>
<p className="text-bold text-tiny capitalize text-default-400">{user.team}</p>
</div>
);
case "status":
return (
<Chip className="capitalize" color={statusColorMap[user.status]} size="sm" variant="flat">
{cellValue}
</Chip>
);
case "actions":
return (
<div className="relative flex justify-end items-center gap-2">
<Dropdown>
<DropdownTrigger>
<Button isIconOnly size="sm" variant="light">
<VerticalDotsIcon className="text-default-300" />
</Button>
</DropdownTrigger>
<DropdownMenu>
<DropdownItem>View</DropdownItem>
<DropdownItem>Edit</DropdownItem>
<DropdownItem>Delete</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
);
default:
return cellValue;
}
}, []);
const onNextPage = useCallback(() => {
if (page < pages) {
setPage(page + 1);
}
}, [page, pages]);
const onPreviousPage = useCallback(() => {
if (page > 1) {
setPage(page - 1);
}
}, [page]);
const onRowsPerPageChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
setRowsPerPage(Number(e.target.value));
setPage(1);
}, []);
const onSearchChange = useCallback((value?: string) => {
if (value) {
setFilterValue(value);
setPage(1);
} else {
setFilterValue("");
}
}, []);
const onClear = useCallback(() => {
setFilterValue("");
setPage(1);
}, []);
const topContent = useMemo(() => {
return (
<div className="flex flex-col gap-4">
<div className="flex justify-between gap-3 items-end">
<Input
isClearable
className="w-full sm:max-w-[44%]"
placeholder="Search by name..."
startContent={<SearchIcon />}
value={filterValue}
onClear={() => onClear()}
onValueChange={onSearchChange}
/>
<div className="flex gap-3">
<Dropdown>
<DropdownTrigger className="hidden sm:flex">
<Button endContent={<ChevronDownIcon className="text-small" />} variant="flat">
Status
</Button>
</DropdownTrigger>
<DropdownMenu
disallowEmptySelection
aria-label="Table Columns"
closeOnSelect={false}
selectedKeys={statusFilter}
selectionMode="multiple"
onSelectionChange={setStatusFilter}
>
{statusOptions.map((status) => (
<DropdownItem key={status.uid} className="capitalize">
{capitalize(status.name)}
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
<Dropdown>
<DropdownTrigger className="hidden sm:flex">
<Button endContent={<ChevronDownIcon className="text-small" />} variant="flat">
Columns
</Button>
</DropdownTrigger>
<DropdownMenu
disallowEmptySelection
aria-label="Table Columns"
closeOnSelect={false}
selectedKeys={visibleColumns}
selectionMode="multiple"
onSelectionChange={setVisibleColumns}
>
{columns.map((column) => (
<DropdownItem key={column.uid} className="capitalize">
{capitalize(column.name)}
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
<Button color="primary" endContent={<PlusLinearIcon />}>
Add New
</Button>
</div>
</div>
<div className="flex justify-between items-center">
<span className="text-default-400 text-small">Total {users.length} users</span>
<label className="flex items-center text-default-400 text-small">
Rows per page:
<select
className="bg-transparent outline-none text-default-400 text-small"
onChange={onRowsPerPageChange}
>
<option value="5">5</option>
<option value="10">10</option>
<option value="15">15</option>
</select>
</label>
</div>
</div>
);
}, [
filterValue,
statusFilter,
visibleColumns,
onSearchChange,
onRowsPerPageChange,
users.length,
hasSearchFilter,
]);
const bottomContent = useMemo(() => {
return (
<div className="py-2 px-2 flex justify-between items-center">
<span className="w-[30%] text-small text-default-400">
{selectedKeys === "all"
? "All items selected"
: `${selectedKeys.size} of ${filteredItems.length} selected`}
</span>
<Pagination
isCompact
showControls
showShadow
color="primary"
page={page}
total={pages}
onChange={setPage}
/>
<div className="hidden sm:flex w-[30%] justify-end gap-2">
<Button isDisabled={pages === 1} size="sm" variant="flat" onPress={onPreviousPage}>
Previous
</Button>
<Button isDisabled={pages === 1} size="sm" variant="flat" onPress={onNextPage}>
Next
</Button>
</div>
</div>
);
}, [selectedKeys, items.length, page, pages, hasSearchFilter]);
return (
<div className="p-6">
<Table
isHeaderSticky
aria-label="Example table with custom cells, pagination and sorting"
bottomContent={bottomContent}
bottomContentPlacement="outside"
classNames={{
wrapper: "max-h-[382px]",
}}
selectedKeys={selectedKeys}
selectionMode="multiple"
sortDescriptor={sortDescriptor}
topContent={topContent}
topContentPlacement="outside"
onSelectionChange={setSelectedKeys}
onSortChange={setSortDescriptor}
>
<TableHeader columns={headerColumns}>
{(column) => (
<TableColumn
key={column.uid}
align={column.uid === "actions" ? "center" : "start"}
allowsSorting={column.sortable}
>
{column.name}
</TableColumn>
)}
</TableHeader>
<TableBody emptyContent={"No users found"} items={sortedItems}>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => <TableCell>{renderCell(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
+37
View File
@@ -0,0 +1,37 @@
import Rss from "rss";
import {allBlogPosts} from "contentlayer/generated";
import {siteConfig} from "@/config/site";
import {allCoreContent} from "@/libs/contentlayer";
export async function GET() {
const feed = new Rss({
title: siteConfig.name,
description: siteConfig.description,
feed_url: `${siteConfig.siteUrl}/feed.xml`,
site_url: siteConfig.siteUrl,
webMaster: `${siteConfig.author} <${siteConfig.email}>`,
managingEditor: `${siteConfig.author} <${siteConfig.email}>`,
language: "en-US",
});
allCoreContent(allBlogPosts).forEach((post) => {
const author = post.author ? post.author : siteConfig.author;
feed.item({
title: post.title,
description: post.description ?? "",
url: `${siteConfig.siteUrl}/blog/${post.slug}`,
guid: `${siteConfig.siteUrl}/blog/${post.slug}`,
date: post.date,
author: `${author} <${siteConfig.email}>`,
categories: post.tags ?? [],
});
});
return new Response(feed.xml(), {
headers: {
"Content-Type": "application/xml",
},
});
}
+56
View File
@@ -0,0 +1,56 @@
import {Button, Image, Link} from "@nextui-org/react";
import {Blockquote} from "@/components/docs/components/blockquote";
export default function FigmaPage() {
return (
<>
<main className="relative container mx-auto max-w-7xl z-10 px-6 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
<section className="w-full flex flex-col items-center lg:px-16 mt-12 gap-6">
<div className="text-center max-w-xl">
<h1 className="mb-2 font-bold text-4xl">NextUI Figma Kit</h1>
<h5 className="text-default-500 text-lg">
A Figma file that contains the basis of the NextUI design system to help you design
your applications.
</h5>
</div>
<iframe
className="aspect-video w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg"
height="600"
src="https://embed.figma.com/file/1267584376234254760/hf_embed?community_viewer=true&embed_host=nextui"
title="NextUI Figma Kit"
width="800"
/>
<div className="text-center max-w-2xl m-auto">
<Button className="max-w-fit" color="default" variant="bordered">
<Link
isExternal
showAnchorIcon
className="text-current"
href="https://www.figma.com/community/file/1267584376234254760"
>
Open in Figma
</Link>
</Button>
<Blockquote color="warning">
This file is still in development and will be continuously updated.
</Blockquote>
</div>
</section>
</main>
<div
aria-hidden="true"
className="fixed hidden dark:md:block dark:opacity-70 -bottom-[40%] -left-[20%] z-0"
>
<Image removeWrapper alt="docs left background" src="/gradients/docs-left.png" />
</div>
<div
aria-hidden="true"
className="fixed hidden dark:md:block dark:opacity-70 -top-[80%] -right-[60%] 2xl:-top-[60%] 2xl:-right-[45%] z-0 rotate-12"
>
<Image removeWrapper alt="docs right background" src="/gradients/docs-right.png" />
</div>
</>
);
}
+77
View File
@@ -0,0 +1,77 @@
import "@/styles/globals.css";
import "@/styles/sandpack.css";
import {Metadata} from "next";
import {clsx} from "@nextui-org/shared-utils";
import {Analytics} from "@vercel/analytics/react";
import {Providers} from "./providers";
import {Cmdk} from "@/components/cmdk";
import manifest from "@/config/routes.json";
import {siteConfig} from "@/config/site";
import {fontSans} from "@/config/fonts";
import {Navbar} from "@/components/navbar";
import {Footer} from "@/components/footer";
import {__PROD__} from "@/utils";
export const metadata: Metadata = {
title: {
default: siteConfig.name,
template: `%s | ${siteConfig.name}`,
},
description: siteConfig.description,
keywords: [
"React",
"Next.js",
"Tailwind CSS",
"NextUI",
"React Aria",
"Server Components",
"React Components",
"UI Components",
"UI Kit",
"UI Library",
"UI Framework",
"UI Design System",
],
themeColor: [
{media: "(prefers-color-scheme: light)", color: "white"},
{media: "(prefers-color-scheme: dark)", color: "black"},
],
icons: {
icon: "/favicon.ico",
shortcut: "/favicon-32x32.png",
apple: "/apple-touch-icon.png",
},
manifest: "/manifest.json",
twitter: siteConfig.twitter,
openGraph: siteConfig.openGraph,
authors: [
{
name: "jrgarciadev",
url: "https://jrgarciadev.com",
},
],
creator: "jrgarciadev",
viewport:
"viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0",
};
export default function RootLayout({children}: {children: React.ReactNode}) {
return (
<html suppressHydrationWarning dir="ltr" lang="en">
<head />
<body className={clsx("min-h-screen bg-background font-sans antialiased", fontSans.variable)}>
<Providers themeProps={{attribute: "class", defaultTheme: "dark"}}>
<div className="relative flex flex-col" id="app-container">
<Navbar mobileRoutes={manifest.mobileRoutes} routes={manifest.routes} />
{children}
<Footer />
</div>
<Cmdk />
</Providers>
{__PROD__ && <Analytics />}
</body>
</html>
);
}
+49
View File
@@ -0,0 +1,49 @@
import {Spacer} from "@nextui-org/spacer";
import {Hero} from "@/components/marketing/hero";
import {FeaturesGrid} from "@/components/marketing/features-grid";
import {CustomThemes} from "@/components/marketing/custom-themes";
import {A11yOtb} from "@/components/marketing/a11y-otb";
import {DarkMode} from "@/components/marketing/dark-mode";
import {Customization} from "@/components/marketing/customization";
import {LastButNotLeast} from "@/components/marketing/last-but-not-least";
import {InstallBanner} from "@/components/marketing/install-banner";
import {Community} from "@/components/marketing/community";
import {Support} from "@/components/marketing/support";
import landingContent from "@/content/landing";
import {getAllSponsors} from "@/utils/get-all-sponsors";
async function getData() {
try {
const sponsors = await getAllSponsors();
return {
sponsors,
};
} catch (error) {
throw new Error("Failed to fetch data");
}
}
export default async function Home() {
const data = await getData();
return (
<main className="container mx-auto max-w-7xl px-6 flex-grow">
<section className="flex flex-col items-center justify-center">
<Hero />
<FeaturesGrid features={landingContent.topFeatures} />
<CustomThemes />
<A11yOtb />
<DarkMode />
<Customization />
<LastButNotLeast />
<Support sponsors={data.sponsors} />
<Spacer y={24} />
<InstallBanner />
<Community />
<Spacer y={24} />
</section>
</main>
);
}
+19
View File
@@ -0,0 +1,19 @@
"use client";
import * as React from "react";
import {NextUIProvider} from "@nextui-org/react";
import {ThemeProvider as NextThemesProvider} from "next-themes";
import {ThemeProviderProps} from "next-themes/dist/types";
export interface ProvidersProps {
children: React.ReactNode;
themeProps?: ThemeProviderProps;
}
export function Providers({children, themeProps}: ProvidersProps) {
return (
<NextUIProvider>
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
</NextUIProvider>
);
}
@@ -0,0 +1,116 @@
import React, {useCallback, useEffect} from "react";
import Script from "next/script";
import carbonOptimize from "./carbon-optimize";
import {loadScript} from "@/utils/scripts";
import {useIsMounted} from "@/hooks/use-is-mounted";
import {__PROD__} from "@/utils";
const EA_PROVIDER_RATIO = 0.85;
export const CarbonAd: React.FC<unknown> = () => {
const carbonRef = React.useRef(null);
const [showEthicalAds, setShowEthicalAds] = React.useState(false);
const isMounted = useIsMounted();
const loadEthicalAds = useCallback(() => {
return new Promise((resolve) => {
const script = document.createElement("script");
script.src = "https://media.ethicalads.io/media/client/ethicalads.min.js";
script.async = true;
script.onload = () => {
// @ts-ignore
resolve(window.ethicalads);
};
script.onerror = () => {
resolve(null);
};
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
});
}, []);
useEffect(() => {
const shouldShowEthicalAds = Math.random() < EA_PROVIDER_RATIO;
let loadCarbon: any = null;
const loadCarbonAds = () => {
setShowEthicalAds(false);
// The isolation logic of carbonads is flawed.
// Once the script starts loading, it will asynchronous resolve, with no way to stop it.
// This leads to duplication of the ad. To solve the issue, we debounce the load action.
loadCarbon =
isMounted &&
setTimeout(() => {
const script = loadScript(
"https://cdn.carbonads.com/carbon.js?serve=CESIC53Y&placement=nextuiorg",
carbonRef.current,
);
script.id = "_carbonads_js";
carbonOptimize.init();
});
};
const loadAdProvider = async () => {
if (shouldShowEthicalAds) {
try {
const ethicalads = await loadEthicalAds();
if (!ethicalads) {
loadCarbonAds();
return;
}
// @ts-ignore
ethicalads.wait.then((placements) => {
if (!placements.length) {
loadCarbonAds();
} else {
setShowEthicalAds(true);
}
});
} catch (error) {
loadCarbonAds();
}
} else {
loadCarbonAds();
}
};
loadAdProvider();
return () => {
loadCarbon && clearTimeout(loadCarbon);
};
}, [isMounted]);
if (!__PROD__) return null;
return (
<>
<>
<Script async src="https://media.ethicalads.io/media/client/ethicalads.min.js" />
<div
className="ea-container horizontal"
data-ea-campaign-types="paid|publisher-house|community"
data-ea-publisher="nextuiorg"
data-ea-type="image"
style={{display: showEthicalAds ? "block" : "none"}}
/>
</>
<div className="carbon-ad-container" style={{display: showEthicalAds ? "none" : "block"}}>
<span ref={carbonRef} id="carbon-ad" />
</div>
</>
);
};
@@ -0,0 +1,64 @@
export default {
isRefreshAble: function () {
return !(
typeof document.addEventListener === "undefined" || this.browserSupport().hidden === undefined
);
},
browserSupport: function () {
let hidden;
let visibilityChange;
if (typeof document.hidden !== "undefined") {
// Opera 12.10 and Firefox 18 and later support
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
return {
hidden: hidden,
visibilityChange: visibilityChange,
};
},
handleVisibilityChange: function () {
const isElementInViewport = function (el) {
let element = document.querySelector(el);
let bounding = element.getBoundingClientRect();
let isVisible;
if (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.right <= window.innerWidth &&
bounding.bottom <= window.innerHeight
) {
isVisible = true;
} else {
isVisible = false;
}
return isVisible;
};
if (!document.hidden) {
if (typeof _carbonads !== "undefined" && isElementInViewport("#carbonads")) {
// eslint-disable-next-line no-undef
_carbonads.refresh();
}
}
},
init: function () {
if (this.isRefreshAble()) {
document.addEventListener(
this.browserSupport().visibilityChange,
this.handleVisibilityChange,
false,
);
}
},
};
@@ -0,0 +1 @@
export * from "./carbon-ad";
+16 -18
View File
@@ -1,7 +1,5 @@
import {ReactNode, FC} from "react";
import {Image} from "@nextui-org/react";
import {clsx} from "@nextui-org/shared-utils";
export interface BgGridContainerProps {
showGradient?: boolean;
children?: ReactNode;
@@ -9,41 +7,41 @@ export interface BgGridContainerProps {
}
export const BgGridContainer: FC<BgGridContainerProps> = ({
showGradient = true,
// showGradient = true,
children,
className,
}) => {
return (
<div
className={clsx(
"relative overflow-y-hidden flex items-center border border-neutral-400/50 dark:border-neutral-100/50 px-2 py-4 rounded-lg",
"relative overflow-y-hidden flex items-center border border-default-200 dark:border-default-100 px-2 py-4 rounded-lg",
"overflow-hidden",
// blur effect
"bg-transparent",
"before:w-full",
"before:bg-background/10",
"before:content-['']",
"before:block",
"before:z-[-1]",
"before:absolute",
"before:inset-0",
"before:backdrop-blur-md",
"before:backdrop-saturate-200",
// "bg-transparent",
// "before:w-full",
// "before:bg-background/10",
// "before:content-['']",
// "before:block",
// "before:z-[-1]",
// "before:absolute",
// "before:inset-0",
// "before:backdrop-blur-md",
// "before:backdrop-saturate-200",
className,
)}
>
<div className="max-w-full py-4 px-2 w-full h-full scrollbar-hide overflow-x-scroll">
{children}
</div>
<div
{/* <div
className={clsx(
"absolute z-[-1] inset-0 bg-grid-zinc-300/25 [mask-image:linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.6))]",
"hidden md:block absolute z-[-1] inset-0 bg-grid-zinc-300/25 [mask-image:linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.6))]",
"dark:bg-grid-zinc-500/25 dark:[mask-image:linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.5))]",
)}
style={{backgroundPosition: "10px 10px"}}
/>
{showGradient && (
<div className="absolute h-full w-full z-[-1] opacity-40 -top-8 -right-28">
<div className="hidden md:block absolute h-full w-full z-[-1] opacity-40 -top-8 -right-28">
<Image
removeWrapper
alt="custom themes background"
@@ -51,7 +49,7 @@ export const BgGridContainer: FC<BgGridContainerProps> = ({
src="/gradients/blue-purple-1.svg"
/>
</div>
)}
)} */}
</div>
);
};
+67
View File
@@ -0,0 +1,67 @@
"use client";
import {BlogPost} from "contentlayer/generated";
import {Card, CardFooter, CardBody, CardHeader, Link, Avatar, Image} from "@nextui-org/react";
import Balancer from "react-wrap-balancer";
import {format, parseISO} from "date-fns";
import NextLink from "next/link";
import {AnimatePresence, motion} from "framer-motion";
import {useIsMounted} from "@/hooks/use-is-mounted";
const BlogPostCard = (post: BlogPost) => {
const isMounted = useIsMounted();
return (
<AnimatePresence>
{isMounted && (
<motion.article
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: 5}}
initial={{opacity: 0, y: 5}}
transition={{duration: 0.3}}
>
<Card
isBlurred
as={NextLink}
className="p-2 h-full border-transparent text-start bg-white/5 dark:bg-default-400/10 backdrop-blur-lg backdrop-saturate-[1.8]"
href={post.url}
isPressable={!!post.url}
>
<CardHeader>
<Link
as={NextLink}
className="font-semibold "
href={post.url}
size="lg"
underline="hover"
>
<Balancer>{post.title}</Balancer>
</Link>
</CardHeader>
<CardBody className="pt-0 px-2 pb-1">
<Image className="mb-4" src={post.image} />
<p className="font-normal w-full text-default-600">{post.description}</p>
</CardBody>
<CardFooter className="flex justify-between items-center">
<time className="block text-small text-default-500" dateTime={post.date}>
{format(parseISO(post.date), "LLLL d, yyyy")}
</time>
<Avatar size="sm" src={post.author?.avatar} />
</CardFooter>
</Card>
</motion.article>
)}
</AnimatePresence>
);
};
export const BlogPostList = ({posts}: {posts: BlogPost[]}) => {
return (
<div className="mt-10 grid gap-4 grid-cols-[repeat(auto-fill,minmax(300px,1fr))]">
{posts.map((post, idx) => (
<BlogPostCard key={idx} {...post} />
))}
</div>
);
};
+2
View File
@@ -0,0 +1,2 @@
export * from "./nextjs-templates";
export * from "./video-in-view";
@@ -0,0 +1,34 @@
import {NewNextJSIcon} from "@/components/icons";
import {FeaturesGrid} from "@/components/marketing/features-grid";
const frameworks = [
{
title: "Next.js 13 (App) Template",
isExternal: true,
description:
"A Next.js 13 with app directory template pre-configured with NextUI (v2) and Tailwind CSS.",
icon: <NewNextJSIcon height={40} width={40} />,
href: "https://github.com/nextui-org/next-app-template",
},
{
title: "Next.js 13 (Pages) Template",
isExternal: true,
description:
"A Next.js 13 with pages directory template pre-configured with NextUI (v2) and Tailwind CSS.",
icon: <NewNextJSIcon height={40} width={40} />,
href: "https://github.com/nextui-org/next-pages-template",
},
];
export const NextJsTemplates = () => {
return (
<FeaturesGrid
classNames={{
base: "mt-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4",
iconWrapper: "bg-default-300/20",
body: "py-0",
}}
features={frameworks}
/>
);
};
+151
View File
@@ -0,0 +1,151 @@
/* eslint-disable jsx-a11y/media-has-caption */
"use client";
import {useInView} from "framer-motion";
import {useRef, FC, useEffect, useState, useCallback} from "react";
import {Button, cn, Spinner, Tooltip} from "@nextui-org/react";
import {PlayBoldIcon, PauseBoldIcon} from "@/components/icons";
import {RotateLeftLinearIcon} from "@/components/icons";
interface VideoInViewProps {
src: string;
playMode?: "auto" | "manual";
autoPlay?: boolean;
poster?: string;
width?: number;
height?: number;
controls?: boolean;
className?: string;
}
export const VideoInView: FC<VideoInViewProps> = ({
src,
width,
height,
poster,
autoPlay = true,
playMode = "manual",
controls = false,
className,
}) => {
const [isLoading, setIsLoading] = useState(true);
const [isPlaying, setIsPlaying] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);
const isVisible = useInView(videoRef);
// play video when it is visible and playMode is auto
useEffect(() => {
if (playMode !== "auto") {
return;
}
if (isVisible) {
videoRef.current?.play();
} else {
videoRef.current?.pause();
}
}, [isVisible]);
const handleCanPlay = useCallback(() => {
setIsLoading(false);
}, []);
useEffect(() => {
const videoEl = videoRef.current;
if (videoEl) {
if (videoEl.readyState > 3) {
// HAVE_FUTURE_DATA: enough data to start playing
handleCanPlay();
} else {
videoEl.addEventListener("canplaythrough", handleCanPlay);
}
// Cleanup the event listener
return () => {
videoEl.removeEventListener("canplaythrough", handleCanPlay);
};
}
}, []);
const onRestart = useCallback(() => {
if (videoRef.current) {
videoRef.current.currentTime = 0;
videoRef.current.play();
setIsPlaying(true);
}
}, []);
const onTogglePlay = useCallback(() => {
if (videoRef.current) {
if (!isPlaying) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
setIsPlaying((v) => !v);
}
}, [isPlaying]);
return (
<div
className="relative data-[playing=true]:after:opacity-0 data-[playing=true]:after:z-[-1] after:content-[''] after:absolute after:inset-0 after:bg-black/30 after:z-20 after:transition-opacity"
data-playing={isPlaying}
>
{isLoading && (
<Spinner
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
color="secondary"
size="lg"
/>
)}
<Tooltip content={isPlaying ? "Pause" : "Play"} delay={1000}>
<Button
isIconOnly
className="absolute z-50 right-12 top-3 border-1 border-transparent bg-transparent before:bg-white/10 before:content-[''] before:block before:z-[-1] before:absolute before:inset-0 before:backdrop-blur-md before:backdrop-saturate-100 before:rounded-lg"
size="sm"
variant="bordered"
onPress={onTogglePlay}
>
{isPlaying ? (
<PauseBoldIcon className="text-white" size={16} />
) : (
<PlayBoldIcon className="text-white" size={16} />
)}
</Button>
</Tooltip>
<Tooltip content="Restart" delay={1000}>
<Button
isIconOnly
className="absolute z-50 right-3 top-3 border-1 border-transparent bg-transparent before:bg-white/10 before:content-[''] before:block before:z-[-1] before:absolute before:inset-0 before:backdrop-blur-md before:backdrop-saturate-100 before:rounded-lg"
size="sm"
variant="bordered"
onPress={onRestart}
>
<RotateLeftLinearIcon className="text-white" size={16} />
</Button>
</Tooltip>
<video
ref={videoRef}
loop
muted
autoPlay={autoPlay && playMode === "auto"}
className={cn(
"w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg",
className,
)}
controls={controls}
height={height}
poster={poster}
src={src}
width={width}
onCanPlay={handleCanPlay}
/>
</div>
);
};
+430
View File
@@ -0,0 +1,430 @@
/* eslint-disable jsx-a11y/no-autofocus */
"use client";
import {Command} from "cmdk";
import {useEffect, useState, FC, useMemo, useCallback, useRef} from "react";
import {matchSorter} from "match-sorter";
import {Button, ButtonProps, Kbd, Modal, ModalContent} from "@nextui-org/react";
import {CloseIcon} from "@nextui-org/shared-icons";
import {tv} from "tailwind-variants";
import {usePathname, useRouter} from "next/navigation";
import MultiRef from "react-multi-ref";
import {clsx} from "@nextui-org/shared-utils";
import scrollIntoView from "scroll-into-view-if-needed";
import {isAppleDevice, isWebKit} from "@react-aria/utils";
import {create} from "zustand";
import {intersectionBy, isEmpty} from "lodash";
import {writeStorage, useLocalStorage} from "@rehooks/local-storage";
import {
DocumentCodeBoldIcon,
HashBoldIcon,
ChevronRightLinearIcon,
SearchLinearIcon,
} from "./icons";
import searchData from "@/config/search-meta.json";
import {useUpdateEffect} from "@/hooks/use-update-effect";
const hideOnPaths = ["examples"];
export interface CmdkStore {
isOpen: boolean;
onClose: () => void;
onOpen: () => void;
}
export const useCmdkStore = create<CmdkStore>((set) => ({
isOpen: false,
onClose: () => set({isOpen: false}),
onOpen: () => set({isOpen: true}),
}));
const cmdk = tv({
slots: {
base: "max-h-full overflow-y-auto",
header: [
"flex",
"items-center",
"w-full",
"px-4",
"border-b",
"border-default-400/50",
"dark:border-default-100",
],
searchIcon: "text-default-400 text-lg",
input: [
"w-full",
"px-2",
"h-14",
"font-sans",
"text-lg",
"outline-none",
"rounded-none",
"bg-transparent",
"text-default-700",
"placeholder-default-500",
"dark:text-default-500",
"dark:placeholder:text-default-300",
],
list: ["px-4", "mt-2", "pb-4", "overflow-y-auto", "max-h-[50vh]"],
itemWrapper: [
"px-4",
"mt-2",
"group",
"flex",
"h-16",
"justify-between",
"items-center",
"rounded-lg",
"shadow",
"bg-content2/50",
"active:opacity-70",
"cursor-pointer",
"transition-opacity",
"data-[active=true]:bg-primary",
"data-[active=true]:text-primary-foreground",
],
leftWrapper: ["flex", "gap-3", "items-center", "w-full", "max-w-full"],
leftIcon: [
"text-default-500 dark:text-default-300",
"group-data-[active=true]:text-primary-foreground",
],
itemContent: ["flex", "flex-col", "gap-0", "justify-center", "max-w-[80%]"],
itemParentTitle: [
"text-default-400",
"text-xs",
"group-data-[active=true]:text-primary-foreground",
"select-none",
],
itemTitle: [
"truncate",
"text-default-500",
"group-data-[active=true]:text-primary-foreground",
"select-none",
],
emptyWrapper: ["flex", "flex-col", "text-center", "items-center", "justify-center", "h-32"],
},
});
interface SearchResultItem {
content: string;
objectID: string;
url: string;
type: "lvl1" | "lvl2" | "lvl3";
hierarchy: {
lvl1: string | null;
lvl2?: string | null;
lvl3?: string | null;
};
}
const MATCH_KEYS = ["hierarchy.lvl1", "hierarchy.lvl2", "hierarchy.lvl3", "content"];
const RECENT_SEARCHES_KEY = "recent-searches";
const MAX_RECENT_SEARCHES = 10;
const MAX_RESULTS = 20;
export const Cmdk: FC<{}> = () => {
const [query, setQuery] = useState("");
const [activeItem, setActiveItem] = useState(0);
const [menuNodes] = useState(() => new MultiRef<number, HTMLElement>());
const slots = useMemo(() => cmdk(), []);
const pathname = usePathname();
const eventRef = useRef<"mouse" | "keyboard">();
const listRef = useRef<HTMLDivElement>(null);
const router = useRouter();
const {isOpen, onClose, onOpen} = useCmdkStore();
const [recentSearches] = useLocalStorage<SearchResultItem[]>(RECENT_SEARCHES_KEY);
const addToRecentSearches = (item: SearchResultItem) => {
let searches = recentSearches ?? [];
// Avoid adding the same search again
if (!searches.find((i) => i.objectID === item.objectID)) {
writeStorage(RECENT_SEARCHES_KEY, [item, ...searches].slice(0, MAX_RECENT_SEARCHES));
} else {
// Move the search to the top
searches = searches.filter((i) => i.objectID !== item.objectID);
writeStorage(RECENT_SEARCHES_KEY, [item, ...searches].slice(0, MAX_RECENT_SEARCHES));
}
};
const results = useMemo<SearchResultItem[]>(
function getResults() {
if (query.length < 2) return [];
const data = searchData as SearchResultItem[];
const words = query.split(" ");
if (words.length === 1) {
return matchSorter(data, query, {
keys: MATCH_KEYS,
}).slice(0, MAX_RESULTS);
}
const matchesForEachWord = words.map((word) =>
matchSorter(data, word, {
keys: MATCH_KEYS,
}),
);
const matches = intersectionBy(...matchesForEachWord, "objectID").slice(0, MAX_RESULTS);
return matches;
},
[query],
);
const items = !isEmpty(results) ? results : recentSearches ?? [];
// Toggle the menu when ⌘K / CTRL K is pressed
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
const hotkey = isAppleDevice() ? "metaKey" : "ctrlKey";
if (e?.key?.toLowerCase() === "k" && e[hotkey]) {
e.preventDefault();
isOpen ? onClose() : onOpen();
}
};
document.addEventListener("keydown", onKeyDown);
}, [isOpen]);
const onItemSelect = useCallback(
(item: SearchResultItem) => {
onClose();
router.push(item.url);
addToRecentSearches(item);
},
[router, recentSearches],
);
const onInputKeyDown = useCallback(
(e: React.KeyboardEvent) => {
eventRef.current = "keyboard";
switch (e.key) {
case "ArrowDown": {
e.preventDefault();
if (activeItem + 1 < items.length) {
setActiveItem(activeItem + 1);
}
break;
}
case "ArrowUp": {
e.preventDefault();
if (activeItem - 1 >= 0) {
setActiveItem(activeItem - 1);
}
break;
}
case "Control":
case "Alt":
case "Shift": {
e.preventDefault();
break;
}
case "Enter": {
if (items?.length <= 0) {
break;
}
onItemSelect(items[activeItem]);
break;
}
}
},
[activeItem, items, router],
);
useUpdateEffect(() => {
setActiveItem(0);
}, [query]);
useUpdateEffect(() => {
if (!listRef.current || eventRef.current === "mouse") return;
const node = menuNodes.map.get(activeItem);
if (!node) return;
scrollIntoView(node, {
scrollMode: "if-needed",
behavior: "smooth",
block: "end",
inline: "end",
boundary: listRef.current,
});
}, [activeItem]);
const CloseButton = useCallback(
({
onPress,
className,
}: {
onPress?: ButtonProps["onPress"];
className?: ButtonProps["className"];
}) => {
return (
<Button
isIconOnly
className={clsx(
"border data-[hover=true]:bg-content2 border-default-400 dark:border-default-100",
className,
)}
radius="full"
size="sm"
variant="bordered"
onPress={onPress}
>
<CloseIcon />
</Button>
);
},
[],
);
const renderItem = useCallback(
(item: SearchResultItem, index: number, isRecent = false) => {
const isLvl1 = item.type === "lvl1";
const mainIcon = isRecent ? (
<SearchLinearIcon className={slots.leftIcon()} size={20} strokeWidth={2} />
) : isLvl1 ? (
<DocumentCodeBoldIcon className={slots.leftIcon()} />
) : (
<HashBoldIcon className={slots.leftIcon()} />
);
return (
<Command.Item
key={item.objectID}
ref={menuNodes.ref(index)}
className={slots.itemWrapper()}
data-active={index === activeItem}
value={item.content}
onMouseEnter={() => {
eventRef.current = "mouse";
setActiveItem(index);
}}
onSelect={() => {
if (eventRef.current === "keyboard") {
return;
}
onItemSelect(item);
}}
>
<div className={slots.leftWrapper()}>
{mainIcon}
<div className={slots.itemContent()}>
{!isLvl1 && <span className={slots.itemParentTitle()}>{item.hierarchy.lvl1}</span>}
<p className={slots.itemTitle()}>{item.content}</p>
</div>
</div>
<ChevronRightLinearIcon size={14} />
</Command.Item>
);
},
[activeItem, onItemSelect, CloseButton, slots],
);
const shouldOpen = !hideOnPaths.some((path) => pathname.includes(path));
return (
<Modal
hideCloseButton
backdrop="opaque"
classNames={{
base: [
"mt-[20vh]",
"border-small",
"dark:border-default-100",
"supports-[backdrop-filter]:bg-background/80",
"dark:supports-[backdrop-filter]:bg-background/30",
"supports-[backdrop-filter]:backdrop-blur-md",
"supports-[backdrop-filter]:backdrop-saturate-150",
],
backdrop: ["bg-black/80"],
}}
isOpen={isOpen && shouldOpen}
motionProps={{
onAnimationComplete: () => {
if (!isOpen) {
setQuery("");
}
},
}}
placement="top-center"
scrollBehavior="inside"
size="xl"
onClose={() => onClose()}
>
<ModalContent>
<Command className={slots.base()} label="Quick search command" shouldFilter={false}>
<div className={slots.header()}>
<SearchLinearIcon className={slots.searchIcon()} strokeWidth={2} />
<Command.Input
autoFocus={!isWebKit()}
className={slots.input()}
placeholder="Search documentation"
value={query}
onKeyDown={onInputKeyDown}
onValueChange={setQuery}
/>
{query.length > 0 && <CloseButton onPress={() => setQuery("")} />}
<Kbd className="hidden md:block border-none px-2 py-1 ml-2 font-medium text-[0.6rem]">
ESC
</Kbd>
</div>
<Command.List ref={listRef} className={slots.list()} role="listbox">
{query.length > 0 && (
<Command.Empty>
<div className={slots.emptyWrapper()}>
<div>
<p>No results for &quot;{query}&quot;</p>
{query.length === 1 ? (
<p className="text-default-400">
Try adding more characters to your search term.
</p>
) : (
<p className="text-default-400">Try searching for something else.</p>
)}
</div>
</div>
</Command.Empty>
)}
{isEmpty(query) &&
(isEmpty(recentSearches) ? (
<div className={slots.emptyWrapper()}>
<p className="text-default-400">No recent searches</p>
</div>
) : (
recentSearches &&
recentSearches.length > 0 && (
<Command.Group
heading={
<div className="flex items-center justify-between">
<p className="text-default-600">Recent</p>
</div>
}
>
{recentSearches.map((item, index) => renderItem(item, index, true))}
</Command.Group>
)
))}
{results.map((item, index) => renderItem(item, index))}
</Command.List>
</Command>
</ModalContent>
</Modal>
);
};
@@ -1,3 +1,4 @@
"use client";
// Inspired by https://github.dev/modulz/stitches-site code demo
import React from "react";
import rangeParser from "parse-numeric-range";
+16 -5
View File
@@ -1,10 +1,14 @@
"use client";
import {FC, useState} from "react";
import {
Modal,
Button,
ModalContent,
ModalHeader,
Link as NextUILink,
ModalBody,
ModalFooter,
Skeleton,
} from "@nextui-org/react";
import Link from "next/link";
@@ -41,7 +45,7 @@ export const DemoCodeModal: FC<DemoCodeModalProps> = ({isOpen, code, title, subt
setIsCodeVisible(isOpen);
},
}}
radius={isMobile ? "none" : "2xl"}
radius={isMobile ? "none" : "lg"}
size={isMobile ? "full" : "2xl"}
onClose={onClose}
>
@@ -61,18 +65,25 @@ export const DemoCodeModal: FC<DemoCodeModalProps> = ({isOpen, code, title, subt
)}
</p>
</ModalHeader>
<ModalBody>
<Skeleton className="h-[320px] md:h-[60vh] rounded-xl" isLoaded={isCodeVisible}>
<ModalBody className="flex-initial md:pb-6">
{isCodeVisible ? (
<CodeWindow
showCopy
showWindowIcons
className="min-h-[320px] max-h-[80vh] md:max-h-[60vh] h-screen"
className="min-h-[320px] !h-[60vh] max-h-full"
language="jsx"
title={fileName}
value={code}
/>
</Skeleton>
) : (
<Skeleton className="h-[60vh] rounded-xl" />
)}
</ModalBody>
<ModalFooter className="md:hidden">
<Button fullWidth onPress={onClose}>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
+4 -2
View File
@@ -1,11 +1,12 @@
"use client";
import {useRef} from "react";
import {Button} from "@nextui-org/react";
import confetti from "canvas-confetti";
export const CustomButton = () => {
const buttonRef = useRef<HTMLButtonElement | null>(null);
const handleConfetti = () => {
const handleConfetti = async () => {
const {clientWidth, clientHeight} = document.documentElement;
const boundingBox = buttonRef.current?.getBoundingClientRect?.();
@@ -14,6 +15,7 @@ export const CustomButton = () => {
const targetWidth = boundingBox?.width ?? 0;
const targetCenterX = targetX + targetWidth / 2;
const confetti = (await import("canvas-confetti")).default;
confetti({
zIndex: 999,
+21 -15
View File
@@ -1,6 +1,9 @@
"use client";
import {Card, CardBody, Button, Image, Progress, CardProps} from "@nextui-org/react";
import {useState, FC} from "react";
import {clsx} from "@nextui-org/shared-utils";
import NextImage from "next/image";
import {
PauseCircleBoldIcon,
@@ -19,9 +22,8 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
return (
<Card
isBlurred
className={clsx("border-none dark:bg-black/40 bg-background/60", className)}
radius="2xl"
shadow="2xl"
className={clsx("border-none bg-background/60 dark:bg-default-100/50", className)}
shadow="sm"
{...otherProps}
>
<CardBody>
@@ -29,27 +31,26 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
<div className="relative col-span-6 md:col-span-4">
<Image
alt="Album cover"
as={NextImage}
className="object-cover"
classNames={{
base: "shadow-black/20",
}}
height={200}
shadow="lg"
height={300}
shadow="md"
src="/images/album-cover.png"
width="100%"
width={240}
/>
</div>
<div className="flex flex-col col-span-6 md:col-span-8">
<div className="flex justify-between items-start">
<div className="flex flex-col gap-0">
<h3 className="font-semibold text-foreground/90">Daily Mix</h3>
<p className="font-semibold text-foreground/90">Daily Mix</p>
<p className="text-sm text-foreground/80">12 Tracks</p>
<h1 className="text-lg font-medium mt-2">Frontend Radio</h1>
<p className="text-lg font-medium mt-2">Frontend Radio</p>
</div>
<Button
isIconOnly
className="text-neutral-900/60 data-[hover]:bg-foreground/10 -translate-y-2 translate-x-2"
aria-label="Like"
className="text-default-900/60 data-[hover]:bg-foreground/10 -translate-y-2 translate-x-2"
radius="full"
variant="light"
onPress={() => setLiked((v) => !v)}
@@ -65,10 +66,10 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
<Progress
aria-label="Music progress"
classNames={{
filler: "bg-neutral-800 dark:bg-white",
track: "bg-neutral-500/30",
indicator: "bg-default-800 dark:bg-white",
track: "bg-default-500/30",
}}
color="neutral"
color="default"
size="sm"
value={33}
/>
@@ -81,6 +82,7 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
<div className="flex w-full items-center justify-center">
<Button
isIconOnly
aria-label="Repeat"
className="data-[hover]:bg-foreground/10"
radius="full"
variant="light"
@@ -89,6 +91,7 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
</Button>
<Button
isIconOnly
aria-label="Previous"
className="data-[hover]:bg-foreground/10"
radius="full"
variant="light"
@@ -97,6 +100,7 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
</Button>
<Button
isIconOnly
aria-label="Play"
className="w-auto h-auto data-[hover]:bg-foreground/10"
radius="full"
variant="light"
@@ -105,6 +109,7 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
</Button>
<Button
isIconOnly
aria-label="Next"
className="data-[hover]:bg-foreground/10"
radius="full"
variant="light"
@@ -113,6 +118,7 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
</Button>
<Button
isIconOnly
aria-label="Shuffle"
className="data-[hover]:bg-foreground/10"
radius="full"
variant="light"
@@ -0,0 +1,89 @@
import {
tv,
VariantProps,
CircularProgress,
CircularProgressProps,
circularProgress,
} from "@nextui-org/react";
import {FC} from "react";
const speedProgress = tv({
extend: circularProgress,
slots: {
svg: "",
label: "",
value: "",
},
variants: {
color: {
olive: {
svg: "text-[#84cc16]",
},
orange: {
svg: "text-[#ff8c00]",
},
violet: {
svg: "text-[#8b5cf6]",
},
},
size: {
sm: {
svg: "w-10 h-10",
label: "text-small",
value: "text-[0.5rem]",
},
md: {
svg: "w-12 h-12",
label: "text-medium",
value: "text-small",
},
lg: {
svg: "w-14 h-14",
label: "text-medium",
value: "text-[0.6rem]",
},
xl: {
svg: "w-16 h-16",
label: "text-large",
value: "text-small",
},
},
},
defaultVariants: {
color: "olive",
size: "sm",
},
});
type SpeedProgressVariants = VariantProps<typeof speedProgress>;
export interface SpeedProgressProps extends Omit<CircularProgressProps, "color" | "size"> {
color?: SpeedProgressVariants["color"];
size?: SpeedProgressVariants["size"];
}
export const SpeedProgress: FC<SpeedProgressProps> = ({color, size, ...otherProps}) => {
const slots = speedProgress({size, color});
return (
<CircularProgress
classNames={{
svg: slots.svg(),
label: slots.label(),
value: slots.value(),
}}
// color={color} // not needed because is being passed from slots
formatOptions={{style: "unit", unit: "kilometer"}}
label="Speed"
showValueLabel={true}
// size={size}
{...otherProps}
/>
);
};
// const MyApp = () => {
// return (
// <SpeedProgress color="orange" />
// );
// }
+25
View File
@@ -0,0 +1,25 @@
/* eslint-disable react/display-name */
import React from "react";
type Variants = {[K: string]: {[P: string]: string}};
type StringToBoolean<T> = T extends "true" | "false" ? boolean : T;
type StylesProps<V extends Variants> = {
[K in keyof V]?: StringToBoolean<keyof V[K]>;
} & {
variants: V;
[x: string]: any;
};
type ComponentType<P = {}> = React.ComponentType<P> | React.ForwardRefExoticComponent<P>;
export function extendStyles<P extends Variants>(
Component: ComponentType<StylesProps<P>>,
styles: {variants: P},
) {
return React.forwardRef<HTMLButtonElement, StylesProps<P>>((props, ref) => {
return <Component ref={ref} {...styles} {...props} />;
});
}
@@ -1,6 +1,9 @@
"use client";
import {useState, FC} from "react";
import {Card, CardHeader, Button, Avatar, CardBody, CardFooter} from "@nextui-org/react";
import {clsx} from "@nextui-org/shared-utils";
import NextImage from "next/image";
interface UserTwitterCardProps {
className?: string;
@@ -13,14 +16,25 @@ export const UserTwitterCard: FC<UserTwitterCardProps> = ({className}) => {
<Card className={clsx("max-w-[300px]", className)}>
<CardHeader className="justify-between">
<div className="flex gap-5">
<Avatar isBordered radius="full" size="md" src="/avatars/avatar-1.png" />
<Avatar
isBordered
ImgComponent={NextImage}
alt="Zoey Lang"
imgProps={{
width: 40,
height: 40,
}}
radius="full"
size="md"
src="/avatars/avatar-1.webp"
/>
<div className="flex flex-col items-start justify-center">
<h4 className="text-sm font-semibold leading-none text-neutral-600">Zoey Lang</h4>
<h5 className="text-sm tracking-tight text-neutral-400">@zoeylang</h5>
<h4 className="text-sm font-semibold leading-none text-default-600">Zoey Lang</h4>
<h5 className="text-sm tracking-tight text-default-400">@zoeylang</h5>
</div>
</div>
<Button
className={isFollowed ? "bg-transparent text-foreground border-neutral-200" : ""}
className={isFollowed ? "bg-transparent text-foreground border-default-200" : ""}
color="primary"
radius="full"
size="sm"
@@ -31,7 +45,7 @@ export const UserTwitterCard: FC<UserTwitterCardProps> = ({className}) => {
</Button>
</CardHeader>
<CardBody className="px-3 py-0">
<p className="text-sm pl-px text-neutral-400">
<p className="text-sm pl-px text-default-400">
Full-stack developer, @getnextui lover she/her&nbsp;
<span aria-label="confetti" role="img">
🎉
@@ -40,12 +54,12 @@ export const UserTwitterCard: FC<UserTwitterCardProps> = ({className}) => {
</CardBody>
<CardFooter className="gap-3">
<div className="flex gap-1">
<p className="font-semibold text-neutral-400 text-sm">4</p>
<p className=" text-neutral-400 text-sm">Following</p>
<p className="font-semibold text-default-400 text-sm">4</p>
<p className=" text-default-400 text-sm">Following</p>
</div>
<div className="flex gap-1">
<p className="font-semibold text-neutral-400 text-sm">97.1K</p>
<p className="text-neutral-400 text-sm">Followers</p>
<p className="font-semibold text-default-400 text-sm">97.1K</p>
<p className="text-default-400 text-sm">Followers</p>
</div>
</CardFooter>
</Card>
@@ -1,12 +1,13 @@
"use client";
import {FC} from "react";
import {tv, VariantProps} from "tailwind-variants";
const blockquote = tv({
base: "border pl-4 bg-neutral-50 my-6 py-3 rounded-xl [&>p]:m-0",
const blockquoteStyles = tv({
base: "border px-4 bg-default-50 my-6 py-3 rounded-xl [&>p]:m-0",
variants: {
color: {
neutral: "border-neutral-100 bg-neutral-50/20",
default: "border-default-200 dark:border-default-100 bg-default-200/20",
primary: "border-primary-100 bg-primary-50/20",
secondary: "border-secondary-100 bg-secondary-50/20",
success: "border-success-100 bg-success-50/20",
@@ -15,11 +16,11 @@ const blockquote = tv({
},
},
defaultVariants: {
color: "neutral",
color: "default",
},
});
type BlockquoteVariantProps = VariantProps<typeof blockquote>;
type BlockquoteVariantProps = VariantProps<typeof blockquoteStyles>;
export interface BlockquoteProps extends BlockquoteVariantProps {
children?: React.ReactNode;
@@ -27,7 +28,7 @@ export interface BlockquoteProps extends BlockquoteVariantProps {
}
export const Blockquote: FC<BlockquoteProps> = ({children, color, className, ...props}) => {
const styles = blockquote({color, className});
const styles = blockquoteStyles({color, className});
return (
<blockquote className={styles} {...props}>
@@ -1,87 +1,199 @@
import React from "react";
"use client";
import React, {useCallback, useMemo, useRef} from "react";
import dynamic from "next/dynamic";
import {Spinner} from "@nextui-org/react";
import {Skeleton, Tab, Tabs} from "@nextui-org/react";
import {useInView} from "framer-motion";
import {useCodeDemo, UseCodeDemoProps} from "./use-code-demo";
import WindowResizer, {WindowResizerProps} from "./window-resizer";
import {GradientBoxProps} from "@/components/gradient-box";
const DynamicReactLiveDemo = dynamic(
() => import("./react-live-demo").then((m) => m.ReactLiveDemo),
{
ssr: false,
// eslint-disable-next-line react/display-name
loading: () => (
<div>
<Spinner />
</div>
),
loading: () => <Skeleton className="w-full h-24 rounded-xl" />,
},
);
const DynamicSandpack = dynamic(() => import("../../../sandpack").then((m) => m.Sandpack), {
ssr: false,
// eslint-disable-next-line react/display-name
loading: () => (
<div>
<Spinner />
</div>
),
loading: () => <Skeleton className="w-full h-32 rounded-xl" />,
});
interface CodeDemoProps extends UseCodeDemoProps {
component?: string;
interface CodeDemoProps extends UseCodeDemoProps, WindowResizerProps {
title?: string;
asIframe?: boolean;
showSandpackPreview?: boolean;
initialEditorOpen?: boolean;
enableResize?: boolean;
showTabs?: boolean;
showPreview?: boolean;
showWindowActions?: boolean;
iframeSrc?: string;
asIframe?: boolean;
iframeInitialWidth?: number;
hideWindowActions?: boolean;
showOpenInCodeSandbox?: boolean;
isPreviewCentered?: boolean;
resizeEnabled?: boolean;
typescriptStrict?: boolean;
displayMode?: "always" | "visible";
isGradientBox?: boolean;
gradientColor?: GradientBoxProps["color"];
defaultExpanded?: boolean;
previewHeight?: string | number;
overflow?: "auto" | "visible" | "hidden";
className?: string;
}
export const CodeDemo: React.FC<CodeDemoProps> = ({
files = {},
component,
title,
showEditor = true,
showPreview = true,
asIframe = false,
resizeEnabled = true,
hideWindowActions = false,
showSandpackPreview = false,
showWindowActions = false,
enableResize = false,
isPreviewCentered = false,
// when false .js files will be used
typescriptStrict = false,
showOpenInCodeSandbox,
isGradientBox = false,
defaultExpanded = false,
previewHeight = "auto",
overflow = "visible",
displayMode = "always",
showTabs = true,
gradientColor,
highlightedLines,
iframeInitialWidth,
iframeSrc,
className,
}) => {
const ref = useRef(null);
const isInView = useInView(ref, {
once: true,
margin: "600px",
});
const {noInline, code} = useCodeDemo({
files,
});
return (
<div className="flex flex-col gap-4">
{showPreview && (
<DynamicReactLiveDemo
code={code}
enableResize={enableResize || asIframe}
height={previewHeight}
iframeInitialWidth={iframeInitialWidth}
iframeSrc={iframeSrc}
iframeTitle={component}
noInline={noInline}
overflow={overflow}
showWindowActions={showWindowActions || asIframe}
/>
)}
const renderContent = useCallback(
(content: React.ReactNode) => {
if (displayMode === "always") return content;
if (displayMode === "visible") {
if (!isInView) {
return <div style={{height: previewHeight}} />;
}
return content;
}
},
[displayMode, previewHeight, isInView],
);
const previewContent = useMemo(() => {
if (!showPreview) return null;
const content = asIframe ? (
<WindowResizer
hideWindowActions={hideWindowActions}
iframeHeight={previewHeight}
iframeInitialWidth={iframeInitialWidth}
iframeSrc={iframeSrc}
iframeTitle={title}
resizeEnabled={resizeEnabled}
/>
) : (
<DynamicReactLiveDemo
className={className}
code={code}
gradientColor={gradientColor}
height={previewHeight}
isCentered={isPreviewCentered}
isGradientBox={isGradientBox}
noInline={noInline}
overflow={overflow}
/>
);
return renderContent(content);
}, [
displayMode,
isGradientBox,
gradientColor,
previewHeight,
hideWindowActions,
asIframe,
showPreview,
isInView,
className,
]);
const editorContent = useMemo(() => {
if (!showEditor) return null;
const content = (
<DynamicSandpack
defaultExpanded={defaultExpanded}
files={files}
highlightedLines={highlightedLines}
showEditor={showEditor}
showOpenInCodeSandbox={showPreview}
showOpenInCodeSandbox={showOpenInCodeSandbox || showPreview}
showPreview={showSandpackPreview}
typescriptStrict={typescriptStrict}
/>
);
return renderContent(content);
}, [
displayMode,
showEditor,
isInView,
files,
highlightedLines,
defaultExpanded,
showPreview,
showSandpackPreview,
showOpenInCodeSandbox,
]);
const shouldRenderTabs = useMemo(() => {
if (!showTabs) return false;
if (!showPreview) return false;
if (!showEditor) return false;
return true;
}, [showTabs, showPreview, showEditor]);
return (
<div ref={ref} className="flex flex-col gap-2">
{shouldRenderTabs ? (
<Tabs
disableAnimation
aria-label="Code demo tabs"
classNames={{
panel: "pt-0",
}}
variant="underlined"
>
<Tab key="preview" title="Preview">
{previewContent}
</Tab>
<Tab key="code" title="Code">
{editorContent}
</Tab>
</Tabs>
) : (
<>
{previewContent}
{editorContent}
</>
)}
</div>
);
};
@@ -1,18 +1,19 @@
import React from "react";
import {LivePreview, LiveProvider, LiveError} from "react-live";
import {clsx} from "@nextui-org/shared-utils";
import * as Components from "@nextui-org/react";
import {BgGridContainer} from "@/components/bg-grid-container";
import {GradientBox, GradientBoxProps} from "@/components/gradient-box";
export interface ReactLiveDemoProps {
code: string;
height?: string | number;
noInline?: boolean;
showWindowActions?: boolean;
iframeSrc?: string;
iframeTitle?: string;
iframeInitialWidth?: number;
enableResize?: boolean;
height?: string | number;
isCentered?: boolean;
isGradientBox?: boolean;
className?: string;
gradientColor?: GradientBoxProps["color"];
overflow?: "auto" | "visible" | "hidden";
}
@@ -20,13 +21,46 @@ export const scope = {
...Components,
} as Record<string, unknown>;
export const ReactLiveDemo: React.FC<ReactLiveDemoProps> = ({code, noInline}) => {
export const ReactLiveDemo: React.FC<ReactLiveDemoProps> = ({
code,
isGradientBox,
gradientColor = "orange",
isCentered = false,
height,
className,
noInline,
}) => {
const content = (
<>
<LivePreview
className={clsx("live-preview flex h-full w-full not-prose", {
"justify-center items-center": isCentered,
})}
style={{height}}
/>
<LiveError />
</>
);
return (
<LiveProvider code={code} noInline={noInline} scope={scope}>
<BgGridContainer>
<LivePreview className="live-preview flex w-full not-prose" />
<LiveError />
</BgGridContainer>
{isGradientBox ? (
<GradientBox
isCentered
className={clsx(
className,
"relative overflow-y-hidden flex items-center border border-default-200 dark:border-default-100 px-2 py-4 rounded-lg overflow-hidden",
)}
color={gradientColor}
to="top-right"
>
<div className="max-w-full py-4 px-2 w-full h-full scrollbar-hide overflow-x-scroll">
{content}
</div>
</GradientBox>
) : (
<BgGridContainer className={className}>{content}</BgGridContainer>
)}
</LiveProvider>
);
};
@@ -0,0 +1,168 @@
import React from "react";
import {motion, useMotionValue, useTransform} from "framer-motion";
import {tv} from "tailwind-variants";
import {useIsMobile} from "@/hooks/use-media-query";
import {useIsomorphicLayoutEffect} from "@/hooks/use-isomorphic-layout-effect";
import {WindowActions} from "@/components/code-window/window-actions";
const resizer = tv({
slots: {
base: "flex items-center justify-end absolute right-[5px] z-10 w-auto xs:hidden",
main: "relative w-full",
barWrapper:
"cursor-ew-resize select-none absolute d-flex justify-center flex items-center w-[10px] h-auto active:opacity-80",
barInner: "relative z-10",
bar: "w-[6px] h-[40px] rounded-full bg-default-400",
iframeWrapper:
"relative z-10 w-full h-full border border-default-200 dark:border-default-100 rounded-lg overflow-hidden",
iframe: "w-full h-[calc(100%_-_2rem)] border-none z-10 overflow-scroll",
},
variants: {
hasInitialWidth: {
true: {
base: "justify-start",
},
},
isMobile: {
true: {
barInner: "hidden",
},
},
enablePointerEvents: {
true: {
iframe: "pointer-events-auto",
iframeWrapper: "pointer-events-auto",
},
false: {
iframe: "pointer-events-none select-none",
iframeWrapper: "pointer-events-none select-none",
},
},
},
defaultVariants: {
hasInitialWidth: false,
isMobile: false,
enablePointerEvents: true,
},
});
export interface WindowResizerProps {
resizeEnabled?: boolean;
hideWindowActions?: boolean;
iframeHeight?: string | number;
iframeMinWidth?: number;
iframeSrc?: string;
iframeInitialWidth?: number;
iframeTitle?: string;
}
const MIN_WIDTH = 320;
const WindowResizer: React.FC<WindowResizerProps> = (props) => {
let constraintsResizerRef = React.useRef<HTMLDivElement>(null);
let resizerRef = React.useRef<HTMLDivElement>(null);
let iframeRef = React.useRef<HTMLIFrameElement>(null);
const [enablePointerEvents, setEnablePointerEvents] = React.useState(true);
const isMobile = useIsMobile();
const {
iframeSrc,
iframeTitle,
resizeEnabled,
hideWindowActions = false,
iframeHeight: height = "420px",
iframeInitialWidth,
iframeMinWidth: minWidth = MIN_WIDTH,
} = props;
const hasInitialWidth = iframeInitialWidth !== undefined;
const {main, base, barInner, barWrapper, bar, iframe, iframeWrapper} = resizer({
hasInitialWidth,
isMobile,
enablePointerEvents,
});
const resizerX = useMotionValue(0);
const browserWidth = useTransform(resizerX, (x) =>
hasInitialWidth ? iframeInitialWidth + x + 14 : `calc(100% + ${x}px - 14px)`,
);
useIsomorphicLayoutEffect(() => {
let observer = new window.ResizeObserver(() => {
if (constraintsResizerRef.current && resizerRef.current) {
let width = constraintsResizerRef.current.offsetWidth - resizerRef.current.offsetWidth;
if (resizerX.get() > width) {
resizerX.set(width);
}
}
});
constraintsResizerRef.current && observer.observe(constraintsResizerRef.current);
return () => {
observer.disconnect();
};
}, []);
React.useEffect(() => {
if (!resizerRef.current) {
return;
}
resizerRef.current.onselectstart = () => false;
}, []);
return (
<div className={main()} style={{height}}>
<motion.div
className={iframeWrapper()}
style={{
width: isMobile ? "100%" : browserWidth,
}}
>
{!hideWindowActions && <WindowActions className="bg-default-100 dark:bg-default-50" />}
<motion.iframe ref={iframeRef} className={iframe()} src={iframeSrc} title={iframeTitle} />
</motion.div>
{resizeEnabled && (
<div
ref={constraintsResizerRef}
className={base({
className: "z-1 top-0 bottom-0 right-0 xs:w-mw-xs",
})}
style={{
width: `calc(100% - ${hasInitialWidth ? iframeInitialWidth : minWidth}px - 20px)`,
}}
>
<motion.div
ref={resizerRef}
_dragX={resizerX}
className={barWrapper()}
drag="x"
dragConstraints={constraintsResizerRef}
dragElastic={0}
dragMomentum={false}
style={{x: resizerX}}
onDragEnd={() => {
document.documentElement.classList.remove("dragging-ew");
iframeRef.current?.classList.remove("dragging-ew");
setEnablePointerEvents(true);
}}
onDragStart={() => {
document.documentElement.classList.add("dragging-ew");
iframeRef.current?.classList.add("dragging-ew");
setEnablePointerEvents(false);
}}
>
<div className={barInner()}>
<div className={bar()} />
</div>
</motion.div>
</div>
)}
</div>
);
};
export default WindowResizer;
@@ -1,5 +1,5 @@
import React from "react";
import {clsx} from "@nextui-org/shared-utils";
import React, {forwardRef} from "react";
import {clsx, getUniqueID} from "@nextui-org/shared-utils";
import BaseHighlight, {Language, PrismTheme, defaultProps} from "prism-react-renderer";
import defaultTheme from "@/libs/prism-theme";
@@ -10,6 +10,8 @@ interface CodeblockProps {
metastring?: string;
theme?: PrismTheme;
showLines?: boolean;
removeIndent?: boolean;
hideScrollBar?: boolean;
className?: string;
children?: (props: any) => React.ReactNode;
}
@@ -39,61 +41,84 @@ const calculateLinesToHighlight = (meta?: string) => {
};
};
export function Codeblock({
codeString,
language,
showLines,
theme: themeProp,
metastring,
className: classNameProp,
...props
}: CodeblockProps) {
const theme = themeProp || defaultTheme;
const shouldHighlightLine = calculateLinesToHighlight(metastring);
const isMultiLine = codeString.split("\n").length > 2;
const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
(
{
codeString,
language,
showLines,
theme: themeProp,
metastring,
hideScrollBar,
removeIndent,
className: classNameProp,
...props
},
ref,
) => {
const theme = themeProp || defaultTheme;
const shouldHighlightLine = calculateLinesToHighlight(metastring);
const isMultiLine = codeString.split("\n").length > 2;
return (
<BaseHighlight {...defaultProps} code={codeString} language={language} theme={theme} {...props}>
{({className, style, tokens, getLineProps, getTokenProps}) => (
<div className="w-full" data-language={language}>
<pre
className={clsx(className, classNameProp, "flex max-w-full", {
"flex-col": isMultiLine,
})}
style={style}
>
{tokens.map((line, i) => {
const lineProps = getLineProps({line, key: i});
return (
<BaseHighlight
{...defaultProps}
code={codeString}
language={language}
theme={theme}
{...props}
>
{({className, style, tokens, getLineProps, getTokenProps}) => (
<div className="w-full" data-language={language}>
<pre
ref={ref}
className={clsx(className, classNameProp, "flex max-w-full", {
"flex-col": isMultiLine,
"scrollbar-hide overflow-x-scroll": hideScrollBar,
})}
style={style}
>
{tokens.map((line, i) => {
const lineProps = getLineProps({line, key: i});
return (
<div
key={i}
{...lineProps}
className={clsx(
lineProps.className,
"px-4 relative [&>span]:relative [&>span]:z-10",
{
"px-2": showLines,
},
{
"before:content-[''] before:w-full before:h-full before:absolute before:z-0 before:left-0 before:bg-gradient-to-r before:from-white/10 before:to-code-background before:border-l-2 border-l-white/80 dark:before:border-l-white/50": shouldHighlightLine(
i,
),
},
)}
>
{showLines && (
<span className="select-none text-xs mr-6 opacity-30">{i + 1}</span>
)}
{line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} className={className} />
))}
</div>
);
})}
</pre>
</div>
)}
</BaseHighlight>
);
}
return (
<div
key={`${i}-${getUniqueID("line-wrapper")}`}
{...lineProps}
className={clsx(
lineProps.className,
removeIndent ? "pr-4" : "px-4",
"relative [&>span]:relative [&>span]:z-10",
{
"px-2": showLines,
},
{
"before:content-[''] before:w-full before:h-full before:absolute before:z-0 before:left-0 before:bg-gradient-to-r before:from-white/10 before:to-code-background":
shouldHighlightLine(i),
},
)}
>
{showLines && (
<span className="select-none text-xs mr-6 opacity-30">{i + 1}</span>
)}
{line.map((token, key) => (
<span
key={`${key}-${getUniqueID("line")}`}
{...getTokenProps({token, key})}
className={className}
/>
))}
</div>
);
})}
</pre>
</div>
)}
</BaseHighlight>
);
},
);
Codeblock.displayName = "CodeBlock";
export default Codeblock;
@@ -1,4 +1,4 @@
import {FeaturesGrid} from "@/components/marketing";
import {FeaturesGrid} from "@/components/marketing/features-grid";
import {communityAccounts} from "@/libs/constants";
export const Community = () => {
@@ -1,83 +1,113 @@
import {Button, Link} from "@nextui-org/react";
import {Button, ButtonProps, Code, Link, Tooltip} from "@nextui-org/react";
import {ReactNode} from "react";
import Balancer from "react-wrap-balancer";
import {GithubIcon, NpmIcon, AdobeIcon, StorybookIcon} from "@/components/icons";
import {GithubIcon, NpmIcon, AdobeIcon, StorybookIcon, NextJsIcon} from "@/components/icons";
import {COMPONENT_PATH, COMPONENT_THEME_PATH} from "@/libs/github/constants";
export interface ComponentLinksProps {
component: string;
styles?: string;
storybook?: string;
rscCompatible?: boolean;
reactAriaHook?: string;
}
export const ComponentLinks = ({component, storybook, reactAriaHook}: ComponentLinksProps) => {
const ButtonLink = ({
children,
href,
startContent,
tooltip,
...props
}: ButtonProps & {
href: string;
tooltip?: string | ReactNode;
}) => {
const button = (
<Button
isExternal
as={Link}
className="!text-small py-4 bg-default-100 dark:bg-default-50 text-default-700"
href={href}
size="sm"
startContent={startContent}
{...props}
>
{children}
</Button>
);
return tooltip ? (
<Tooltip className="max-w-[230px]" content={tooltip}>
{button}
</Tooltip>
) : (
button
);
};
export const ComponentLinks = ({
component,
storybook,
styles,
rscCompatible,
reactAriaHook,
}: ComponentLinksProps) => {
if (!component) {
return null;
}
return (
<div className="flex flex-wrap gap-3 mt-6">
<Button
isExternal
as={Link}
className="text-neutral-700"
href={`https://storiesv2.nextui.org/?path=/story/components-${storybook || component}`}
radius="md"
size="sm"
startIcon={<StorybookIcon className="text-lg text-[#ff4785]" />}
variant="flat"
<ButtonLink
href={`https://storybook.nextui.org/?path=/story/components-${storybook || component}`}
startContent={<StorybookIcon className="text-lg text-[#ff4785]" />}
>
Storybook
</Button>
<Button
isExternal
as={Link}
className="text-neutral-700"
</ButtonLink>
<ButtonLink
href={`https://www.npmjs.com/package/@nextui-org/${component}`}
radius="md"
size="sm"
startIcon={<NpmIcon className="text-2xl text-[#E53E3E]" />}
variant="flat"
startContent={<NpmIcon className="text-2xl text-[#E53E3E]" />}
>
{`@nextui-org/${component}`}
</Button>
</ButtonLink>
{reactAriaHook && (
<Button
isExternal
as={Link}
className="text-neutral-700"
<ButtonLink
href={`https://react-spectrum.adobe.com/react-aria/${reactAriaHook}.html`}
radius="md"
size="sm"
startIcon={<AdobeIcon className="text-lg text-[#E1251B]" />}
variant="flat"
startContent={<AdobeIcon className="text-lg text-[#E1251B]" />}
>
React Aria
</Button>
</ButtonLink>
)}
<Button
isExternal
as={Link}
className="text-neutral-700"
href={`${COMPONENT_PATH}/${component}`}
radius="md"
size="sm"
startIcon={<GithubIcon />}
variant="flat"
>
{rscCompatible && (
<ButtonLink
href="https://nextjs.org/docs/getting-started/react-essentials#server-components"
startContent={<NextJsIcon size={18} />}
tooltip={
<p>
<Balancer>
This component doesn&apos;t use the
<Code className="font-normal bg-transparent px-0 py-0 text-code-mdx">
`use client;`
</Code>
directive making it compatible with RSC.
</Balancer>
</p>
}
>
Server component
</ButtonLink>
)}
<ButtonLink href={`${COMPONENT_PATH}/${component}`} startContent={<GithubIcon size={20} />}>
Source
</Button>
<Button
isExternal
as={Link}
className="text-neutral-700"
href={`${COMPONENT_THEME_PATH}/${component}.ts`}
radius="md"
size="sm"
startIcon={<GithubIcon />}
variant="flat"
</ButtonLink>
<ButtonLink
href={`${COMPONENT_THEME_PATH}/${styles || component}.ts`}
startContent={<GithubIcon size={20} />}
>
Styles source
</Button>
</ButtonLink>
</div>
);
};
@@ -1,5 +1,5 @@
import {NewNextJSIcon, ViteIcon, RemixIcon, AstroIcon} from "@/components/icons";
import {FeaturesGrid} from "@/components/marketing";
import {FeaturesGrid} from "@/components/marketing/features-grid";
const frameworks = [
{
@@ -33,7 +33,7 @@ export const Frameworks = () => {
<FeaturesGrid
classNames={{
base: "mt-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4",
iconWrapper: "bg-neutral-300/20",
iconWrapper: "bg-default-300/20",
body: "py-0",
}}
features={frameworks}
@@ -0,0 +1,63 @@
import {Tabs, Tab, Snippet} from "@nextui-org/react";
import Codeblock from "./codeblock";
type PackageManager = {
key: string;
name: string;
};
const importTabs: PackageManager[] = [
{
key: "main",
name: "Main",
},
{
key: "individual",
name: "Individual",
},
];
export interface ImportTabsProps {
commands: Record<string, string>;
}
export const ImportTabs = ({commands}: ImportTabsProps) => {
return (
<Tabs
disableAnimation
aria-label="NextUI import commands"
classNames={{
base: "group mt-4",
tabList: "relative h-10",
}}
variant="underlined"
>
{importTabs.map(({key, name}) => {
if (!commands[key]) return null;
return (
<Tab key={key} title={name}>
<Snippet
disableTooltip
fullWidth
hideSymbol
classNames={{
base: "bg-code-background text-code-foreground",
pre: "font-light text-base",
copyButton: "text-lg text-default-400",
}}
>
<Codeblock
hideScrollBar
removeIndent
codeString={commands[key] as string}
language="jsx"
/>
</Snippet>
</Tab>
);
})}
</Tabs>
);
};
@@ -4,3 +4,6 @@ export * from "./blockquote";
export * from "./community";
export * from "./frameworks";
export * from "./code-demo";
export * from "./import-tabs";
export * from "./swatch-colors-set";
export {default as Codeblock} from "./codeblock";
@@ -1,6 +1,6 @@
import {Tabs, TabItem, Snippet} from "@nextui-org/react";
import {Tabs, Tab, Snippet} from "@nextui-org/react";
import {Codeblock} from "./codeblock";
import Codeblock from "./codeblock";
import {YarnIcon, NpmSmallIcon, PnpmIcon} from "@/components/icons";
@@ -44,7 +44,7 @@ export const PackageManagers = ({commands}: PackageManagersProps) => {
if (!commands[name]) return null;
return (
<TabItem
<Tab
key={name}
title={
<div className="flex items-center space-x-2">
@@ -60,12 +60,12 @@ export const PackageManagers = ({commands}: PackageManagersProps) => {
classNames={{
base: "bg-code-background text-code-foreground",
pre: "font-light text-base",
copyButton: "text-lg text-code-foreground/50",
copyButton: "text-lg text-zinc-500 mr-2",
}}
>
<Codeblock codeString={commands[name] as string} language="bash" />
<Codeblock removeIndent codeString={commands[name] as string} language="bash" />
</Snippet>
</TabItem>
</Tab>
);
})}
</Tabs>
@@ -0,0 +1,641 @@
import {parseToRgba, readableColor} from "color2k";
import {Button, Tooltip} from "@nextui-org/react";
import {commonColors, semanticColors} from "@nextui-org/theme";
import {useClipboard} from "@nextui-org/use-clipboard";
import {useState} from "react";
import {useTheme} from "next-themes";
import {get, isEmpty} from "lodash";
type ColorsItem = {
color: string;
scale?: string;
className?: string;
textClassName?: string;
};
type SwatchColors = {
title: string;
items: ColorsItem[];
};
type SwatchSetProps = {
colors: SwatchColors[];
isSematic?: boolean;
};
const scaleIndexMap: Record<number, string> = {
0: "50",
1: "100",
2: "200",
3: "300",
4: "400",
5: "500",
6: "600",
7: "700",
8: "800",
9: "900",
};
const Swatch = ({color, scale}: {color: string; scale?: string}) => {
const [copied, setCopied] = useState(false);
const {copy} = useClipboard();
const colorText = color
? `#${parseToRgba(color)
.slice(0, 3)
.map((x) => x.toString(16).padStart(2, "0"))
.join("")
.toUpperCase()}`
: "N/A";
const handleCopy = () => {
copy(colorText);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1000);
};
return (
<Tooltip content="Copied" isOpen={copied}>
<Button
className="flex flex-col gap-1 items-center justify-center w-24 h-24 m-2 rounded-medium shadow-small"
style={{
backgroundColor: color,
}}
onPress={handleCopy}
>
<span
className="text-small font-semibold"
style={{
color: readableColor(color),
}}
>
{colorText}
</span>
{scale && (
<span
className="text-tiny font-semibold"
style={{
color: readableColor(color),
}}
>
{scale}
</span>
)}
</Button>
</Tooltip>
);
};
const SematicSwatch = ({
color,
className,
textClassName,
}: {
color: string;
className?: string;
textClassName?: string;
}) => {
const [copied, setCopied] = useState(false);
const {copy} = useClipboard();
const {theme} = useTheme();
let value: string = "";
const [colorName, colorScale] = color.split("-");
let currentPalette = get(semanticColors, theme ?? "", {});
if (!colorScale) {
value = get(currentPalette, `${colorName}.DEFAULT`, "");
} else {
value = get(currentPalette, `${colorName}.${colorScale}`, "");
}
const handleCopy = () => {
copy(value);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1000);
};
return (
<Tooltip content="Copied" isOpen={copied}>
<Button
className={`${className} gap-1 flex flex-col items-center justify-center w-24 h-24 m-2 rounded-medium shadow-small`}
onPress={handleCopy}
>
<span className={`${textClassName} text-tiny font-semibold`}>{color}</span>
{!isEmpty(value) && (
<span className={`${textClassName} text-tiny font-semibold`}>{value}</span>
)}
</Button>
</Tooltip>
);
};
const SwatchSet = ({colors, isSematic = false}: SwatchSetProps) => (
<div className="flex flex-row flex-wrap items-center justify-center">
{colors.map(({title, items}) => (
<div key={title} className="flex flex-col items-start w-full h-full">
<h2 className="text-xl font-bold text-foreground">{title}</h2>
<div className="flex flex-row flex-wrap items-center justify-start w-full h-full px-4 py-1">
{items.map((c, index) =>
isSematic ? (
<SematicSwatch
key={`${c.color}-${index}`}
className={c.className}
color={c.color}
textClassName={c.textClassName}
/>
) : (
<Swatch key={`${c.color}-${index}`} color={c.color} scale={c.scale} />
),
)}
</div>
</div>
))}
</div>
);
const getCommonItems = (colors: string[]) => {
return colors.map((color, index, array) => ({
color,
scale: array.length > 2 ? scaleIndexMap[index] : undefined,
}));
};
export const CommonColors = () => {
return (
<SwatchSet
colors={[
{
title: "White & Black",
items: getCommonItems([commonColors.white, commonColors.black]),
},
{
title: "Blue",
items: getCommonItems([...Object.values(commonColors.blue)]),
},
{
title: "Purple",
items: getCommonItems([...Object.values(commonColors.purple)]),
},
{
title: "Green",
items: getCommonItems([...Object.values(commonColors.green)]),
},
{
title: "Red",
items: getCommonItems([...Object.values(commonColors.red)]),
},
{
title: "Pink",
items: getCommonItems([...Object.values(commonColors.pink)]),
},
{
title: "Yellow",
items: getCommonItems([...Object.values(commonColors.yellow)]),
},
{
title: "Cyan",
items: getCommonItems([...Object.values(commonColors.cyan)]),
},
{
title: "Zinc",
items: getCommonItems([...Object.values(commonColors.zinc)]),
},
]}
/>
);
};
export const SemanticColors = () => {
return (
<SwatchSet
isSematic
colors={[
{
title: "Layout",
items: [
{
color: "background",
className: "bg-background",
textClassName: "text-foreground",
},
{
color: "foreground",
className: "bg-foreground",
textClassName: "text-background",
},
{
color: "divider",
className: "bg-divider",
textClassName: "text-foreground",
},
{
color: "focus",
className: "bg-focus",
textClassName: "text-primary-foreground",
},
],
},
{
title: "Content",
items: [
{
color: "content1",
className: "bg-content1",
textClassName: "text-content1-foreground",
},
{
color: "content2",
className: "bg-content2",
textClassName: "text-content2-foreground",
},
{
color: "content3",
className: "bg-content3",
textClassName: "text-content3-foreground",
},
{
color: "content4",
className: "bg-content4",
textClassName: "text-content4-foreground",
},
],
},
{
title: "Base",
items: [
{
color: "default",
className: "bg-default",
textClassName: "text-default-foreground",
},
{
color: "primary",
className: "bg-primary",
textClassName: "text-primary-foreground",
},
{
color: "secondary",
className: "bg-secondary",
textClassName: "text-secondary-foreground",
},
{
color: "success",
className: "bg-success",
textClassName: "text-success-foreground",
},
{
color: "warning",
className: "bg-warning",
textClassName: "text-warning-foreground",
},
{
color: "danger",
className: "bg-danger",
textClassName: "text-danger-foreground",
},
],
},
{
title: "Default",
items: [
{
color: "default-50",
className: "bg-default-50",
textClassName: "text-default-900",
},
{
color: "default-100",
className: "bg-default-100",
textClassName: "text-default-900",
},
{
color: "default-200",
className: "bg-default-200",
textClassName: "text-default-800",
},
{
color: "default-300",
className: "bg-default-300",
textClassName: "text-default-800",
},
{
color: "default-400",
className: "bg-default-400",
textClassName: "text-default-800",
},
{
color: "default-500",
className: "bg-default-500",
textClassName: "text-default-foreground",
},
{
color: "default-600",
className: "bg-default-600",
textClassName: "text-default-50",
},
{
color: "default-700",
className: "bg-default-700",
textClassName: "text-default-100",
},
{
color: "default-800",
className: "bg-default-800",
textClassName: "text-default-100",
},
{
color: "default-900",
className: "bg-default-900",
textClassName: "text-default-100",
},
],
},
{
title: "Primary",
items: [
{
color: "primary-50",
className: "bg-primary-50",
textClassName: "text-primary-900",
},
{
color: "primary-100",
className: "bg-primary-100",
textClassName: "text-primary-900",
},
{
color: "primary-200",
className: "bg-primary-200",
textClassName: "text-primary-800",
},
{
color: "primary-300",
className: "bg-primary-300",
textClassName: "text-primary-800",
},
{
color: "primary-400",
className: "bg-primary-400",
textClassName: "text-primary-800",
},
{
color: "primary-500",
className: "bg-primary-500",
textClassName: "text-primary-foreground",
},
{
color: "primary-600",
className: "bg-primary-600",
textClassName: "text-primary-50",
},
{
color: "primary-700",
className: "bg-primary-700",
textClassName: "text-primary-100",
},
{
color: "primary-800",
className: "bg-primary-800",
textClassName: "text-primary-100",
},
{
color: "primary-900",
className: "bg-primary-900",
textClassName: "text-primary-100",
},
],
},
{
title: "Secondary",
items: [
{
color: "secondary-50",
className: "bg-secondary-50",
textClassName: "text-secondary-900",
},
{
color: "secondary-100",
className: "bg-secondary-100",
textClassName: "text-secondary-900",
},
{
color: "secondary-200",
className: "bg-secondary-200",
textClassName: "text-secondary-800",
},
{
color: "secondary-300",
className: "bg-secondary-300",
textClassName: "text-secondary-800",
},
{
color: "secondary-400",
className: "bg-secondary-400",
textClassName: "text-secondary-800",
},
{
color: "secondary-500",
className: "bg-secondary-500",
textClassName: "text-secondary-foreground",
},
{
color: "secondary-600",
className: "bg-secondary-600",
textClassName: "text-secondary-50",
},
{
color: "secondary-700",
className: "bg-secondary-700",
textClassName: "text-secondary-100",
},
{
color: "secondary-800",
className: "bg-secondary-800",
textClassName: "text-secondary-100",
},
{
color: "secondary-900",
className: "bg-secondary-900",
textClassName: "text-secondary-100",
},
],
},
{
title: "Success",
items: [
{
color: "success-50",
className: "bg-success-50",
textClassName: "text-success-900",
},
{
color: "success-100",
className: "bg-success-100",
textClassName: "text-success-900",
},
{
color: "success-200",
className: "bg-success-200",
textClassName: "text-success-800",
},
{
color: "success-300",
className: "bg-success-300",
textClassName: "text-success-800",
},
{
color: "success-400",
className: "bg-success-400",
textClassName: "text-success-800",
},
{
color: "success-500",
className: "bg-success-500",
textClassName: "text-success-foreground",
},
{
color: "success-600",
className: "bg-success-600",
textClassName: "text-success-50",
},
{
color: "success-700",
className: "bg-success-700",
textClassName: "text-success-100",
},
{
color: "success-800",
className: "bg-success-800",
textClassName: "text-success-100",
},
{
color: "success-900",
className: "bg-success-900",
textClassName: "text-success-100",
},
],
},
{
title: "Warning",
items: [
{
color: "warning-50",
className: "bg-warning-50",
textClassName: "text-warning-900",
},
{
color: "warning-100",
className: "bg-warning-100",
textClassName: "text-warning-900",
},
{
color: "warning-200",
className: "bg-warning-200",
textClassName: "text-warning-800",
},
{
color: "warning-300",
className: "bg-warning-300",
textClassName: "text-warning-800",
},
{
color: "warning-400",
className: "bg-warning-400",
textClassName: "text-warning-800",
},
{
color: "warning-500",
className: "bg-warning-500",
textClassName: "text-warning-foreground",
},
{
color: "warning-600",
className: "bg-warning-600",
textClassName: "text-warning-50",
},
{
color: "warning-700",
className: "bg-warning-700",
textClassName: "text-warning-100",
},
{
color: "warning-800",
className: "bg-warning-800",
textClassName: "text-warning-100",
},
{
color: "warning-900",
className: "bg-warning-900",
textClassName: "text-warning-100",
},
],
},
{
title: "Danger",
items: [
{
color: "danger-50",
className: "bg-danger-50",
textClassName: "text-danger-900",
},
{
color: "danger-100",
className: "bg-danger-100",
textClassName: "text-danger-900",
},
{
color: "danger-200",
className: "bg-danger-200",
textClassName: "text-danger-800",
},
{
color: "danger-300",
className: "bg-danger-300",
textClassName: "text-danger-800",
},
{
color: "danger-400",
className: "bg-danger-400",
textClassName: "text-danger-800",
},
{
color: "danger-500",
className: "bg-danger-500",
textClassName: "text-danger-foreground",
},
{
color: "danger-600",
className: "bg-danger-600",
textClassName: "text-danger-50",
},
{
color: "danger-700",
className: "bg-danger-700",
textClassName: "text-danger-100",
},
{
color: "danger-800",
className: "bg-danger-800",
textClassName: "text-danger-100",
},
{
color: "danger-900",
className: "bg-danger-900",
textClassName: "text-danger-100",
},
],
},
]}
/>
);
};
+1 -1
View File
@@ -1,4 +1,4 @@
export * from "./sidebar";
export * from "./toc";
export * from "./footer-nav";
export * from "./pager";
export * from "./components/code-demo";
@@ -1,18 +1,22 @@
"use client";
import * as React from "react";
import NextLink from "next/link";
import {Link} from "@nextui-org/react";
import {ChevronIcon} from "@nextui-org/shared-icons";
import manifest from "@/config/routes.json";
import {removeFromLast} from "@/utils";
import {Route, addTagToSlug} from "@/libs/docs/page";
import {Route} from "@/libs/docs/page";
import {useDocsRoute} from "@/hooks/use-docs-route";
export interface FooterNavProps {
tag?: string;
prevRoute?: Route;
nextRoute?: Route;
currentRoute?: Route;
}
export const FooterNav: React.FC<FooterNavProps> = ({tag, prevRoute, nextRoute}) => {
export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
const {prevRoute, nextRoute} = useDocsRoute(manifest.routes, currentRoute);
return (
<div className="flex w-full justify-between py-20">
{prevRoute ? (
@@ -21,7 +25,7 @@ export const FooterNav: React.FC<FooterNavProps> = ({tag, prevRoute, nextRoute})
as={NextLink}
className="flex gap-2"
color="foreground"
href={addTagToSlug(removeFromLast(prevRoute.path || "", "."), tag)}
href={removeFromLast(prevRoute.path || "", ".")}
>
<ChevronIcon className="text-primary" height={20} width={20} />
{prevRoute.title}
@@ -35,7 +39,7 @@ export const FooterNav: React.FC<FooterNavProps> = ({tag, prevRoute, nextRoute})
as={NextLink}
className="flex gap-1 items-center"
color="foreground"
href={addTagToSlug(removeFromLast(nextRoute.path || "", "."), tag)}
href={removeFromLast(nextRoute.path || "", ".")}
>
{nextRoute.title}
<ChevronIcon className="rotate-180 text-primary" height={20} width={20} />
+59 -24
View File
@@ -1,3 +1,5 @@
"use client";
import {FC} from "react";
import {ChevronIcon} from "@nextui-org/shared-icons";
import {CollectionBase, Expandable, MultipleSelection, Node, ItemProps} from "@react-types/shared";
@@ -8,15 +10,22 @@ import {TreeState, useTreeState} from "@react-stately/tree";
import {useSelectableCollection} from "@react-aria/selection";
import {usePress} from "@react-aria/interactions";
import {clsx, dataAttr} from "@nextui-org/shared-utils";
import {SpacerProps, Spacer, Link as NextUILink} from "@nextui-org/react";
import {
SpacerProps,
Spacer,
Link as NextUILink,
Chip,
dataFocusVisibleClasses,
} from "@nextui-org/react";
import Link from "next/link";
import {isEmpty} from "lodash";
import {useRouter} from "next/router";
import {usePathname, useRouter} from "next/navigation";
import {getRoutePaths} from "./utils";
import {Route} from "@/libs/docs/page";
import {TreeKeyboardDelegate} from "@/utils/tree-keyboard-delegate";
import {useScrollPosition} from "@/hooks/use-scroll-position";
export interface Props<T> extends Omit<ItemProps<T>, "title">, Route {
slug?: string;
@@ -49,6 +58,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
const {key, rendered, childNodes} = item;
const router = useRouter();
const pathname = usePathname();
const paths = item.props.path
? getRoutePaths(item.props.path, item.props?.tag)
@@ -57,8 +67,14 @@ function TreeItem<T>(props: TreeItemProps<T>) {
pathname: "",
};
const isNew = item.props?.newPost;
const isExpanded = state.expandedKeys.has(key);
const isSelected = state.selectionManager.isSelected(key) || paths.pathname === item.props.slug;
const isSelected =
state.selectionManager.isSelected(key) ||
paths.pathname === item.props.slug ||
paths.pathname === pathname ||
paths.pagePath === pathname;
const ref = useRef<any>(null);
@@ -85,14 +101,9 @@ function TreeItem<T>(props: TreeItemProps<T>) {
aria-expanded={dataAttr(hasChildNodes ? isExpanded : undefined)}
aria-selected={dataAttr(isSelected)}
className={clsx(
"flex flex-col gap-3 outline-none w-full",
"flex flex-col gap-3 outline-none w-full tap-highlight-transparent",
// focus ring
"data-[focus-visible=true]:outline-none",
"data-[focus-visible=true]:ring-2",
"data-[focus-visible=true]:ring-primary",
"data-[focus-visible=true]:ring-offset-2",
"data-[focus-visible=true]:ring-offset-background",
"data-[focus-visible=true]:dark:ring-offset-background-dark",
...dataFocusVisibleClasses,
)}
data-focus-visible={isFocusVisible}
data-focused={isFocused}
@@ -114,16 +125,11 @@ function TreeItem<T>(props: TreeItemProps<T>) {
as={Link}
className={clsx(
"w-full",
"opacity-60",
"dark:font-light",
{
"opacity-100": isSelected,
"pointer-events-none": item.props?.comingSoon,
},
"font-normal",
"before:mr-4",
"before:content-['']",
"before:block",
"before:bg-neutral-300",
"before:bg-default-300",
"before:w-1",
"before:h-1",
"before:rounded-full",
@@ -131,7 +137,28 @@ function TreeItem<T>(props: TreeItemProps<T>) {
color="foreground"
href={paths.pathname}
>
{rendered}
<span
className={clsx(
isSelected
? "text-primary font-medium dark:text-foreground"
: "opacity-80 dark:opacity-60",
{
"pointer-events-none": item.props?.comingSoon,
},
)}
>
{rendered}
</span>
{isNew && (
<Chip className="ml-2" color="primary" size="sm" variant="flat">
New
</Chip>
)}
{item.props?.comingSoon && (
<Chip className="ml-2" color="default" size="sm" variant="flat">
Coming soon
</Chip>
)}
</NextUILink>
)}
</div>
@@ -162,14 +189,16 @@ function TreeHeading({item}: {item: any}) {
function Tree<T extends object>(props: CollectionBase<T> & Expandable & MultipleSelection) {
let state = useTreeState(props);
let ref = useRef<HTMLUListElement>(null);
const scrollPosition = useScrollPosition(ref);
let keyboardDelegate = useMemo(
// @ts-expect-error
() => new TreeKeyboardDelegate(state.collection, state.disabledKeys),
[state.collection, state.disabledKeys],
);
let ref = useRef(null);
let {collectionProps} = useSelectableCollection({
ref,
selectionManager: state.selectionManager,
@@ -180,8 +209,13 @@ function Tree<T extends object>(props: CollectionBase<T> & Expandable & Multiple
<ul
{...collectionProps}
ref={ref}
className="flex flex-col gap-4 scrollbar-hide overflow-y-scroll max-h-[calc(100vh_-_64px)] pb-28"
className="flex flex-col gap-4 scrollbar-hide lg:overflow-y-scroll lg:max-h-[calc(100vh_-_64px)] pb-28"
role="tree"
style={{
WebkitMaskImage: `linear-gradient(to top, transparent 0%, #000 100px, #000 ${
scrollPosition > 30 ? "90%" : "100%"
}, transparent 100%)`,
}}
>
{[...state.collection].map((item) => {
if (item.type === "section") {
@@ -198,19 +232,20 @@ export interface DocsSidebarProps {
routes?: Route[];
tag?: string;
slug?: string;
className?: string;
}
export const DocsSidebar: FC<DocsSidebarProps> = ({routes, slug, tag}) => {
export const DocsSidebar: FC<DocsSidebarProps> = ({routes, slug, tag, className}) => {
const expandedKeys = routes?.reduce((keys, route) => {
if (route.defaultOpen) {
keys.push(route.key);
keys.push(route.key as string);
}
return keys;
}, [] as string[]);
return (
<div className="sticky top-20 mt-2 z-0 h-[calc(100vh-121px)]">
<div className={clsx("lg:fixed lg:top-20 mt-2 z-0 lg:h-[calc(100vh-121px)]", className)}>
<Tree defaultExpandedKeys={expandedKeys} items={routes || []}>
{(route) => (
<Item
+66 -32
View File
@@ -1,23 +1,31 @@
import {FC} from "react";
"use client";
import {FC, useRef, useEffect} from "react";
import {clsx} from "@nextui-org/shared-utils";
import {Divider, Spacer} from "@nextui-org/react";
import {ChevronCircleTopLinearIcon} from "@nextui-org/shared-icons";
import scrollIntoView from "scroll-into-view-if-needed";
import {Heading} from "@/utils/docs-utils";
import {Heading} from "@/libs/docs/utils";
import {useScrollSpy} from "@/hooks/use-scroll-spy";
import {useScrollPosition} from "@/hooks/use-scroll-position";
export interface DocsTocProps {
headings: Heading[];
}
const paddingLeftByLevel: Record<string, string> = {
const paddingLeftByLevel: Record<number, string> = {
1: "pl-0",
2: "pl-3",
2: "pl-0",
3: "pl-3",
4: "pl-3",
};
export const DocsToc: FC<DocsTocProps> = ({headings}) => {
const tocRef = useRef<HTMLDivElement>(null);
const scrollPosition = useScrollPosition(tocRef);
const activeId = useScrollSpy(
headings.map(({id}) => `[id="${id}"]`),
{
@@ -25,39 +33,65 @@ export const DocsToc: FC<DocsTocProps> = ({headings}) => {
},
);
if (headings.length <= 0) return null;
const activeIndex = headings.findIndex(({id}) => id == activeId);
const firstId = headings[0].id;
useEffect(() => {
if (!activeId || activeIndex < 2) return;
const anchor = tocRef.current?.querySelector(`li > a[href="#${activeId}"]`);
if (anchor) {
scrollIntoView(anchor, {
behavior: "smooth",
block: "center",
inline: "center",
scrollMode: "always",
boundary: tocRef.current,
});
}
}, [activeId, activeIndex]);
return (
<div className="sticky w-full flex flex-col gap-4 text-left top-20 h-[calc(100vh-121px)] overflow-y-scroll scrollbar-hide">
<div
ref={tocRef}
className="fixed w-full max-w-[12rem] flex flex-col gap-4 text-left top-20 pb-20 h-[calc(100vh-121px)] scrollbar-hide overflow-y-scroll"
style={{
WebkitMaskImage: `linear-gradient(to top, transparent 0%, #000 100px, #000 ${
scrollPosition > 30 ? "90%" : "100%"
}, transparent 100%)`,
}}
>
<p className="text-sm">On this page</p>
<ul className="scrollbar-hide flex flex-col gap-2">
{headings.map((heading, i) => (
<li
key={i}
className={clsx(
"flex items-center text-sm font-normal text-foreground/30",
"data-[active=true]:text-foreground/80",
"data-[active=true]:font-medium",
"before:content-['']",
"before:opacity-0",
"data-[active=true]:before:opacity-100",
"before:transition-opacity",
"before:mr-3",
"before:block",
"before:bg-neutral-300",
"before:w-1",
"before:h-1",
"before:rounded-full",
paddingLeftByLevel[heading.level],
)}
data-active={activeId == heading.id}
>
<a href={`#${heading.id}`}>{heading.text}</a>
</li>
))}
{headings.map(
(heading, i) =>
heading.level > 1 && (
<li
key={i}
className={clsx(
"transition-colors",
"font-normal",
"flex items-center text-sm font-normal text-default-500 dark:text-default-300",
"data-[active=true]:text-foreground",
"dark:data-[active=true]:text-foreground",
"before:content-['']",
"before:opacity-0",
"data-[active=true]:before:opacity-100",
"before:transition-opacity",
"before:-ml-3",
"before:absolute",
"before:bg-default-400",
"before:w-1",
"before:h-1",
"before:rounded-full",
paddingLeftByLevel[heading.level],
)}
data-active={activeId == heading.id}
>
<a href={`#${heading.id}`}>{heading.text}</a>
</li>
),
)}
<li
className="mt-2 opacity-0 data-[visible=true]:opacity-100 transition-opacity"
data-visible={activeIndex >= 2}
@@ -65,7 +99,7 @@ export const DocsToc: FC<DocsTocProps> = ({headings}) => {
<Divider />
<Spacer y={2} />
<a
className="flex gap-2 items-center text-sm text-foreground/30 hover:text-foreground/80 pl-4 transition-opacity"
className="flex gap-2 items-center text-sm text-default-500 dark:text-foreground/30 hover:text-foreground/80 pl-4 transition-opacity"
href={`#${firstId}`}
>
Back to top
+31
View File
@@ -0,0 +1,31 @@
"use client";
import dynamic from "next/dynamic";
import {usePathname} from "next/navigation";
import {siteConfig} from "@/config/site";
const Link = dynamic(() => import("@nextui-org/react").then((mod) => mod.Link));
const VercelCallout = dynamic(() => import("./vercel-callout").then((mod) => mod.VercelCallout));
export const Footer = () => {
const pathname = usePathname();
if (pathname.includes("/examples")) {
return null;
}
return (
<footer className="container mx-auto max-w-7xl pb-12 px-12">
<div className="flex flex-col justify-center items-center gap-1">
<p className="text-sm text-default-400">
Created&nbsp;by&nbsp;
<Link isExternal className="text-sm" href={siteConfig.links.portfolio}>
Junior Garcia
</Link>
</p>
<VercelCallout />
</div>
</footer>
);
};
@@ -0,0 +1,19 @@
import {IconSvgProps} from "@/types";
export const DocumentCodeBoldIcon = ({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
d="M16 2H8C4.5 2 3 4 3 7V17C3 20 4.5 22 8 22H16C19.5 22 21 20 21 17V7C21 4 19.5 2 16 2ZM10.53 16.47C10.82 16.76 10.82 17.24 10.53 17.53C10.38 17.68 10.19 17.75 10 17.75C9.81 17.75 9.62 17.68 9.47 17.53L7.47 15.53C7.18 15.24 7.18 14.76 7.47 14.47L9.47 12.47C9.76 12.18 10.24 12.18 10.53 12.47C10.82 12.76 10.82 13.24 10.53 13.53L9.06 15L10.53 16.47ZM16.53 15.53L14.53 17.53C14.38 17.68 14.19 17.75 14 17.75C13.81 17.75 13.62 17.68 13.47 17.53C13.18 17.24 13.18 16.76 13.47 16.47L14.94 15L13.47 13.53C13.18 13.24 13.18 12.76 13.47 12.47C13.76 12.18 14.24 12.18 14.53 12.47L16.53 14.47C16.82 14.76 16.82 15.24 16.53 15.53ZM18.5 9.25H16.5C14.98 9.25 13.75 8.02 13.75 6.5V4.5C13.75 4.09 14.09 3.75 14.5 3.75C14.91 3.75 15.25 4.09 15.25 4.5V6.5C15.25 7.19 15.81 7.75 16.5 7.75H18.5C18.91 7.75 19.25 8.09 19.25 8.5C19.25 8.91 18.91 9.25 18.5 9.25Z"
fill="currentColor"
/>
</svg>
);
+23
View File
@@ -0,0 +1,23 @@
import {IconSvgProps} from "@/types";
export const HashBoldIcon = ({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
d="M10.4199 13.4181H13.2599L13.5799 10.5781H10.7399L10.4199 13.4181Z"
fill="currentColor"
/>
<path
d="M16.19 2H7.81C4.17 2 2 4.17 2 7.81V16.18C2 19.83 4.17 22 7.81 22H16.18C19.82 22 21.99 19.83 21.99 16.19V7.81C22 4.17 19.83 2 16.19 2ZM18.82 10.58H15.05L14.73 13.43H18.1C18.5 13.43 18.83 13.76 18.83 14.16C18.83 14.56 18.5 14.89 18.1 14.89H14.57L14.16 18.55C14.12 18.92 13.8 19.2 13.43 19.2C13.4 19.2 13.38 19.2 13.35 19.2C12.95 19.16 12.66 18.79 12.7 18.39L13.09 14.89H10.25L9.84 18.55C9.8 18.92 9.48 19.2 9.11 19.2C9.08 19.2 9.06 19.2 9.03 19.2C8.63 19.16 8.34 18.79 8.38 18.39L8.77 14.89H5.18C4.78 14.89 4.45 14.56 4.45 14.16C4.45 13.76 4.78 13.43 5.18 13.43H8.95L9.27 10.58H5.9C5.5 10.58 5.17 10.25 5.17 9.85C5.17 9.45 5.5 9.12 5.9 9.12H9.43L9.84 5.46C9.88 5.06 10.25 4.77 10.65 4.81C11.05 4.85 11.34 5.22 11.3 5.62L10.91 9.12H13.75L14.16 5.46C14.21 5.06 14.57 4.77 14.97 4.81C15.37 4.85 15.66 5.22 15.62 5.62L15.23 9.12H18.84C19.24 9.12 19.57 9.45 19.57 9.85C19.57 10.25 19.22 10.58 18.82 10.58Z"
fill="currentColor"
/>
</svg>
);
+5
View File
@@ -11,3 +11,8 @@ export * from "./previous";
export * from "./repeat-one";
export * from "./shuffle";
export * from "./info";
export * from "./document-code";
export * from "./hash";
export * from "./more-square";
export * from "./play";
export * from "./pause";
@@ -0,0 +1,19 @@
import {IconSvgProps} from "@/types";
export const MoreSquareBoldIcon = ({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
d="M16.19 2H7.81C4.17 2 2 4.17 2 7.81V16.18C2 19.83 4.17 22 7.81 22H16.18C19.82 22 21.99 19.83 21.99 16.19V7.81C22 4.17 19.83 2 16.19 2ZM8 13C7.44 13 7 12.55 7 12C7 11.45 7.45 11 8 11C8.55 11 9 11.45 9 12C9 12.55 8.56 13 8 13ZM12 13C11.44 13 11 12.55 11 12C11 11.45 11.45 11 12 11C12.55 11 13 11.45 13 12C13 12.55 12.56 13 12 13ZM16 13C15.44 13 15 12.55 15 12C15 11.45 15.45 11 16 11C16.55 11 17 11.45 17 12C17 12.55 16.56 13 16 13Z"
fill="currentColor"
/>
</svg>
);
+23
View File
@@ -0,0 +1,23 @@
import {IconSvgProps} from "@/types";
export const PauseBoldIcon = ({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
d="M10.65 19.11V4.89C10.65 3.54 10.08 3 8.64 3H5.01C3.57 3 3 3.54 3 4.89V19.11C3 20.46 3.57 21 5.01 21H8.64C10.08 21 10.65 20.46 10.65 19.11Z"
fill="currentColor"
/>
<path
d="M21 19.11V4.89C21 3.54 20.43 3 18.99 3H15.36C13.93 3 13.35 3.54 13.35 4.89V19.11C13.35 20.46 13.92 21 15.36 21H18.99C20.43 21 21 20.46 21 19.11Z"
fill="currentColor"
/>
</svg>
);
+19
View File
@@ -0,0 +1,19 @@
import {IconSvgProps} from "@/types";
export const PlayBoldIcon = ({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
d="M4 11.9999V8.43989C4 4.01989 7.13 2.2099 10.96 4.4199L14.05 6.1999L17.14 7.9799C20.97 10.1899 20.97 13.8099 17.14 16.0199L14.05 17.7999L10.96 19.5799C7.13 21.7899 4 19.9799 4 15.5599V11.9999Z"
fill="currentColor"
/>
</svg>
);
@@ -0,0 +1,23 @@
import {IconSvgProps} from "@/types";
export const ChevronRightLinearIcon = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height={height || size}
role="presentation"
viewBox="0 0 24 24"
width={width || size}
{...props}
>
<path
d="M8.91003 19.9201L15.43 13.4001C16.2 12.6301 16.2 11.3701 15.43 10.6001L8.91003 4.08008"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="1.5"
/>
</svg>
);
@@ -0,0 +1,43 @@
import {IconSvgProps} from "@/types";
export const HashLinearIcon = ({size = 24, width, height, ...props}: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height={height || size}
role="presentation"
viewBox="0 0 24 24"
width={width || size}
{...props}
>
<path
d="M10 3L8 21"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
<path
d="M16 3L14 21"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
<path
d="M3.5 9H21.5"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
<path
d="M2.5 15H20.5"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
</svg>
);
@@ -15,3 +15,8 @@ export * from "./paperclip";
export * from "./link-squared";
export * from "./link";
export * from "./rotate-right";
export * from "./hash";
export * from "./chevron-right";
export * from "./search";
export * from "./simple-grid";
export * from "./rotate-left";
@@ -0,0 +1,36 @@
import {IconSvgProps} from "@/types";
export const RotateLeftLinearIcon = ({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
d="M9.11008 5.0799C9.98008 4.8199 10.9401 4.6499 12.0001 4.6499C16.7901 4.6499 20.6701 8.5299 20.6701 13.3199C20.6701 18.1099 16.7901 21.9899 12.0001 21.9899C7.21008 21.9899 3.33008 18.1099 3.33008 13.3199C3.33008 11.5399 3.87008 9.8799 4.79008 8.4999"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
<path
d="M7.87012 5.32L10.7601 2"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
<path
d="M7.87012 5.32007L11.2401 7.78007"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
</svg>
);
@@ -0,0 +1,35 @@
import {IconSvgProps} from "@/types";
export const SearchLinearIcon = ({
size = 24,
strokeWidth = 1.5,
width,
height,
...props
}: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height={height || size}
role="presentation"
viewBox="0 0 24 24"
width={width || size}
{...props}
>
<path
d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={strokeWidth}
/>
<path
d="M22 22L20 20"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={strokeWidth}
/>
</svg>
);
@@ -0,0 +1,29 @@
import {IconSvgProps} from "@/types";
export const SimpleGridIcon = ({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
d="M9 22H15C20 22 22 20 22 15V9C22 4 20 2 15 2H9C4 2 2 4 2 9V15C2 20 4 22 9 22Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
<path
d="M9 2V22"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
</svg>
);
@@ -0,0 +1,19 @@
import {IconSvgProps} from "@/types";
export const VerticalDotsIcon = ({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
d="M12 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 12c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
fill="currentColor"
/>
</svg>
);
+19 -9
View File
@@ -1,3 +1,5 @@
"use client";
/* eslint-disable react/display-name */
import {
Button,
@@ -19,12 +21,13 @@ import {
DeleteDocumentBulkIcon,
} from "@nextui-org/shared-icons";
import Link from "next/link";
import dynamic from "next/dynamic";
import {useEffect, useRef, useState} from "react";
import {FeaturesGrid} from "./features-grid";
import landingContent from "@/content/landing";
import {GradientBox, DemoCodeModal} from "@/components";
import {GradientBox} from "@/components";
import {
KeyboardBoldIcon,
MouseCircleBoldIcon,
@@ -37,6 +40,10 @@ import {
import {title, subtitle, titleWrapper, sectionWrapper} from "@/components/primitives";
import {useIsMobile} from "@/hooks/use-media-query";
const DemoCodeModal = dynamic(() => import("../demo-code-modal").then((mod) => mod.DemoCodeModal), {
ssr: false,
});
const a11yItems = [
{
title: "Keyboard navigation",
@@ -64,7 +71,7 @@ const a11yItems = [
},
];
const iconClasses = "text-2xl text-neutral-500 pointer-events-none flex-shrink-0";
const iconClasses = "text-2xl text-default-500 pointer-events-none flex-shrink-0";
export const A11yOtb = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -97,7 +104,7 @@ export const A11yOtb = () => {
NextUI components are built on top of&nbsp;
<NextUILink
isExternal
className="text-xl text-neutral-500 font-light [&>svg]:ml-1"
className="text-xl text-default-500 font-light [&>svg]:ml-1"
href="https://react-spectrum.adobe.com/react-aria/"
underline="always"
>
@@ -112,15 +119,17 @@ export const A11yOtb = () => {
classNames={{
base: "grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4",
header: "pb-3",
iconWrapper: "bg-neutral-100 dark:bg-transparent text-neutral-500/50",
card: "bg-white dark:bg-default-400/10",
iconWrapper: "bg-default-100 dark:bg-transparent text-default-500/50",
}}
features={a11yItems}
/>
<Button
aria-label="Learn more about accessibility"
as={Link}
className="max-w-fit"
color="success"
href="/docs/theme/customize-theme"
href="/docs/customization/customize-theme"
radius="full"
size="sm"
variant="flat"
@@ -137,6 +146,7 @@ export const A11yOtb = () => {
<Tooltip className="text-xs px-2" content="Show code" placement="top">
<Button
isIconOnly
aria-label="Show code"
className="absolute top-1 right-1 text-success-50 data-[hover]:bg-foreground/10"
radius="full"
variant="light"
@@ -152,22 +162,22 @@ export const A11yOtb = () => {
isOpen={isDropdownOpen}
placement="bottom"
portalContainer={ref.current}
shouldBlockScroll={isMobile}
shouldBlockScroll={false}
shouldFlip={isMobile}
onOpenChange={(open) => setIsDropdownOpen(open)}
>
<DropdownTrigger>
<Button color="success" variant="flat">
<Button className="bg-success-50" color="success" variant="flat">
{isMobile ? "Click me" : "Actions"}
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label="Actions"
closeOnSelect={true}
color="neutral"
color="default"
variant="flat"
>
<DropdownSection title="Actions">
<DropdownSection showDivider title="Actions">
<DropdownItem
key="new"
description="Create a new file"
+1 -1
View File
@@ -2,7 +2,7 @@ import {Spacer} from "@nextui-org/react";
import {sectionWrapper, titleWrapper, title, subtitle} from "../primitives";
import {FeaturesGrid} from "@/components/marketing";
import {FeaturesGrid} from "@/components/marketing/features-grid";
import {communityAccounts} from "@/libs/constants";
export const Community = () => {
@@ -1,8 +1,11 @@
"use client";
/* eslint-disable react/display-name */
import {useMemo, useState} from "react";
import {Tabs, TabItem, Card, CardBody, Image, Button, RadioGroup, Radio} from "@nextui-org/react";
import {Tabs, Tab, Card, CardBody, Image, Button, RadioGroup, Radio} from "@nextui-org/react";
import get from "lodash/get";
import NextLink from "next/link";
import NextImage from "next/image";
import {shopCartStyles} from "./styles";
@@ -19,7 +22,7 @@ const themesTabs = (isMobile: boolean) => [
icon: () => (
<NextUILogo
small
className="text-neutral-400 group-data-[selected=true]:text-primary"
className="text-default-400 group-data-[selected=true]:text-primary"
size={isMobile ? 34 : 44}
/>
),
@@ -57,10 +60,10 @@ type Tab = {id: string; title: () => JSX.Element; icon: () => JSX.Element};
const itemSizes = ["xs", "s", "m", "l", "xl"];
const codeHighlights = {
nextui: "6-15",
modern: "22-31",
elegant: "38-47",
retro: "54-67",
nextui: "6-19",
modern: "26-39",
elegant: "46-59",
retro: "66-84",
};
const CustomThemesExample = ({
@@ -90,20 +93,20 @@ const CustomThemesExample = ({
<div className="flex flex-col gap-6 ">
<Tabs
disableAnimation
disableCursor
disableCursorAnimation
aria-label="Custom themes tabs"
classNames={{
base: "w-full md:max-w-[50%]",
tab: "px-0 md:px-3 h-auto data-[selected=true]:bg-transparent",
tabList: "w-full md:max-w-1/2 justify-start gap-0 md:gap-4",
tabContent: "text-neutral-400 text-base",
base: "w-full",
tab: "px-0 w-fit h-auto data-[selected=true]:bg-transparent",
tabList: "w-full justify-start gap-8",
tabContent: "text-default-400 text-base",
}}
items={tabs}
variant="light"
onSelectionChange={onSelectionChange}
>
{(item) => (
<TabItem
<Tab
key={item.id}
title={
<div className="flex flex-col justify-center items-center gap-2">
@@ -114,13 +117,16 @@ const CustomThemesExample = ({
/>
)}
</Tabs>
<Card className={slots.wrapper()} radius="2xl">
<Card className={slots.wrapper()} radius="lg">
<CardBody className="relative flex-col md:flex-row md:items-center gap-4 md:gap-9 overflow-visible">
<div className={slots.imageWrapper()}>
<Image
fill
removeWrapper
alt="Shoes theme example"
as={NextImage}
className={slots.img()}
sizes="100vw"
src="/images/shoes-1.png"
/>
</div>
@@ -146,7 +152,7 @@ const CustomThemesExample = ({
classNames={{
wrapper: "hidden",
labelWrapper: slots.sizeOption(),
label: "text-sm font-semibold text-inherit",
label: slots.sizeOptionLabel(),
}}
value={itemSize}
>
@@ -158,7 +164,6 @@ const CustomThemesExample = ({
<Button
className={slots.buyButton()}
color="primary"
radius="xl"
variant={selectedTheme === "nextui" ? "shadow" : "solid"}
>
Buy now
@@ -166,7 +171,7 @@ const CustomThemesExample = ({
<Button
className={slots.addToBagButton()}
color="primary"
radius="xl"
radius="full"
variant="bordered"
>
Add to bag
@@ -175,6 +180,7 @@ const CustomThemesExample = ({
</div>
<Button
isIconOnly
aria-label="like"
className={slots.starButton()}
data-liked={liked}
radius="full"
@@ -186,10 +192,11 @@ const CustomThemesExample = ({
</CardBody>
</Card>
<Button
aria-label="Learn more about theme customization"
as={NextLink}
className="max-w-fit"
className={slots.learnMoreButton()}
color="primary"
href="/docs/theme/customize-theme"
href="/docs/customization/customize-theme"
radius="full"
size="sm"
variant="flat"
@@ -25,7 +25,7 @@ export const shopCartStyles = tv({
"before:transition-all",
],
img: [
"overflow-visible",
"object-[45%_50%]",
"sm:scale-125",
"absolute",
"z-10",
@@ -43,9 +43,9 @@ export const shopCartStyles = tv({
],
contentWrapper: "flex flex-col justify-center transition-all h-full h-[200px]",
title: ["relative", "w-full", "flex-none", "text-xl", "font-semibold", "text-foreground"],
description: ["my-2", "w-full", "text-base", "text-neutral-500"],
description: ["my-2", "w-full", "text-base", "text-default-500"],
price: "relative text-lg font-semibold text-foreground",
previousPrice: "relative line-through font-semibold text-neutral-400 ml-3",
previousPrice: "relative line-through font-semibold text-default-400 ml-3",
percentOff: "relative font-normal text-success ml-3",
sizeOption: [
"w-8",
@@ -56,10 +56,18 @@ export const shopCartStyles = tv({
"items-center",
"text-sm",
"rounded-full",
// focus ring
"group-data-[focus-visible=true]:outline-none",
"group-data-[focus-visible=true]:ring-2",
"group-data-[focus-visible=true]:ring-focus",
"group-data-[focus-visible=true]:ring-offset-2",
"group-data-[focus-visible=true]:ring-offset-background",
],
sizeOptionLabel: "text-sm font-semibold text-inherit",
buyButton: ["text-sm", "font-normal"],
addToBagButton: ["text-sm", "font-normal"],
starButton: "absolute top-3 right-3 text-neutral-400 data-[liked=true]:text-warning",
starButton: "absolute top-3 right-3 text-default-400 data-[liked=true]:text-warning",
learnMoreButton: "max-w-fit",
},
variants: {
theme: {
@@ -67,9 +75,10 @@ export const shopCartStyles = tv({
card: "py-5",
imageWrapper: ["before:rounded-2xl", "before:from-[#010187] before:to-[#18000E]"],
sizeOption: [
"group-data-[checked=true]:bg-primary",
"group-data-[checked=true]:text-primary-foreground",
"group-data-[selected=true]:bg-primary",
"group-data-[selected=true]:text-primary-foreground",
],
learnMoreButton: "bg-primary/20 text-primary",
},
modern: {
wrapper: "rounded-3xl",
@@ -84,25 +93,34 @@ export const shopCartStyles = tv({
title: "text-2xl",
description: "text-sm",
sizeOption: [
"group-data-[checked=true]:bg-secondary",
"group-data-[checked=true]:text-secondary-foreground",
"group-data-[selected=true]:bg-secondary",
"group-data-[selected=true]:text-secondary-foreground",
"group-data-[focus-visible=true]:ring-secondary",
],
percentOff: "text-pink-500",
buyButton: ["bg-secondary", "text-sm", "font-normal", "rounded-full"],
buyButton: [
"bg-secondary",
"text-sm",
"font-normal",
"rounded-full",
"data-[focus-visible=true]:ring-secondary",
],
addToBagButton: [
"border-secondary",
"text-secondary",
"text-sm",
"font-normal",
"rounded-full",
"data-[focus-visible=true]:ring-secondary",
],
learnMoreButton: "bg-secondary/20 text-secondary",
},
elegant: {
wrapper: "rounded-md",
wrapper: "rounded-sm",
contentWrapper: "ml-3",
imageWrapper: [
"lg:scale-[1.3]",
"before:rounded-md",
"before:rounded-sm",
"before:from-[#323232] before:to-[#000000]",
"shadow-xl",
],
@@ -110,20 +128,32 @@ export const shopCartStyles = tv({
title: "text-xl font-mono font-thin",
description: "text-sm font-mono font-light",
sizeOption: [
"group-data-[checked=true]:bg-foreground",
"group-data-[checked=true]:text-background",
"rounded-sm w-7 h-7",
"group-data-[selected=true]:bg-foreground",
"group-data-[selected=true]:text-background",
"group-data-[focus-visible=true]:ring-foreground",
],
sizeOptionLabel: "font-normal",
price: "font-mono font-thin",
previousPrice: "font-mono font-light ml-2",
percentOff: "text-neutral-500 font-mono text-sm",
buyButton: ["bg-foreground", "text-background", "text-sm", "font-normal", "rounded-sm"],
percentOff: "text-default-500 font-mono text-sm",
buyButton: [
"bg-foreground",
"text-background",
"text-sm",
"font-normal",
"rounded-sm",
"data-[focus-visible=true]:ring-foreground",
],
addToBagButton: [
"border-foreground",
"text-foreground",
"text-sm",
"font-normal",
"rounded-sm",
"data-[focus-visible=true]:ring-foreground",
],
learnMoreButton: "bg-foreground/10 text-foreground",
},
retro: {
wrapper: "bg-[#F4E8D1] dark:bg-[#E1CA9E] rounded-sm",
@@ -145,8 +175,11 @@ export const shopCartStyles = tv({
description: "text-[0.7rem] uppercase text-black",
sizeOption: [
"text-black/80",
"group-data-[checked=true]:bg-[#EE457E]",
"group-data-[checked=true]:text-white",
"group-data-[selected=true]:bg-[#EE457E]",
"group-data-[selected=true]:text-white",
"group-data-[focus-visible=true]:ring-[#EE457E]",
"group-data-[focus-visible=true]:ring-offset-[#F4E8D1]",
"group-data-[focus-visible=true]:dark:ring-offset-[#F4E8D1]",
],
price: "text-black",
previousPrice: "text-black/60 ml-2",
@@ -158,6 +191,9 @@ export const shopCartStyles = tv({
"uppercase",
"text-sm",
"rounded-sm",
"data-[focus-visible=true]:ring-[#EE457E]",
"data-[focus-visible=true]:ring-offset-[#F4E8D1]",
"data-[focus-visible=true]:dark:ring-offset-[#F4E8D1]",
],
addToBagButton: [
"border-[#FFD34E]",
@@ -166,7 +202,11 @@ export const shopCartStyles = tv({
"font-medium",
"text-sm",
"rounded-sm",
"data-[focus-visible=true]:ring-[#EE457E]",
"data-[focus-visible=true]:ring-offset-[#F4E8D1]",
"data-[focus-visible=true]:dark:ring-offset-[#F4E8D1]",
],
learnMoreButton: "bg-warning/20 text-warning",
},
},
},
@@ -1,3 +1,5 @@
"use client";
/* eslint-disable react/display-name */
import {Button, Link} from "@nextui-org/react";
import NextLink from "next/link";
@@ -5,7 +7,7 @@ import NextLink from "next/link";
import {title, subtitle, titleWrapper, sectionWrapper} from "@/components/primitives";
import {CodeWindow} from "@/components/code-window";
import landingContent from "@/content/landing";
import {GradientBox} from "@/components";
import {GradientBox} from "@/components/gradient-box";
import {CustomButton} from "@/components/demos";
export const Customization = () => {
@@ -23,7 +25,7 @@ export const Customization = () => {
NextUI is based on{" "}
<Link
isExternal
className="text-xl text-neutral-500 font-light [&>svg]:ml-1"
className="text-xl text-default-500 font-light [&>svg]:ml-1"
href="https://tailwind-variants.org"
underline="always"
>
@@ -53,9 +55,10 @@ export const Customization = () => {
</div>
<div className="flex w-1/2 justify-start">
<Button
aria-label="Learn more about customization"
as={NextLink}
className="max-w-fit bg-pink-100 text-pink-500 dark:bg-pink-900 dark:text-pink-300"
href="/docs/theme/customize-theme"
href="/docs/customization/customize-theme"
radius="full"
size="sm"
variant="flat"
+12 -3
View File
@@ -1,16 +1,23 @@
"use client";
/* eslint-disable react/display-name */
import {Code, Button, Tooltip} from "@nextui-org/react";
import {useState} from "react";
import NextLink from "next/link";
import dynamic from "next/dynamic";
import {MusicPlayer} from "@/components/demos";
import {title, subtitle, titleWrapper, sectionWrapper} from "@/components/primitives";
import {ThemeSwitch} from "@/components/theme-switch";
import {CodeWindow} from "@/components/code-window";
import landingContent from "@/content/landing";
import {GradientBox, DemoCodeModal} from "@/components";
import {GradientBox} from "@/components";
import {InfoBoldIcon} from "@/components/icons";
const DemoCodeModal = dynamic(() => import("../demo-code-modal").then((mod) => mod.DemoCodeModal), {
ssr: false,
});
export const DarkMode = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -38,6 +45,7 @@ export const DarkMode = () => {
<Tooltip className="text-xs px-2" content="Show code" placement="top">
<Button
isIconOnly
aria-label="Show code"
className="text-white/70 dark:text-black/70 data-[hover]:bg-foreground/10"
radius="full"
variant="light"
@@ -48,7 +56,7 @@ export const DarkMode = () => {
</Tooltip>
<ThemeSwitch
classNames={{
base: "p-1.5 bg-transparent rounded-xl",
base: "mt-1 bg-transparent rounded-xl",
wrapper: "!text-white/70 dark:!text-black/70",
}}
/>
@@ -66,10 +74,11 @@ export const DarkMode = () => {
</div>
<div className="flex w-1/2 justify-start">
<Button
aria-label="Learn more about dark mode"
as={NextLink}
className="max-w-fit"
color="warning"
href="/docs/theme/dark-mode"
href="/docs/customization/dark-mode"
radius="full"
size="sm"
variant="flat"
@@ -1,20 +1,21 @@
import React, {ReactNode, useMemo} from "react";
"use client";
import React, {ReactNode} from "react";
import {tv} from "tailwind-variants";
import {Card, CardHeader, CardBody, LinkProps, SlotsToClasses} from "@nextui-org/react";
import {useRouter} from "next/router";
import {useRouter} from "next/navigation";
import {LinkIcon} from "@nextui-org/shared-icons";
const styles = tv({
slots: {
base: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4",
card:
"dark:border-transparent bg-white/5 dark:bg-neutral-400/10 backdrop-blur-lg backdrop-saturate-[1.8]",
card: "border-transparent bg-white/5 dark:bg-default-400/10 backdrop-blur-lg backdrop-saturate-[1.8]",
header: "gap-2 pb-0",
body: "",
iconWrapper:
"flex justify-center p-2 rounded-full items-center bg-secondary-100/80 text-pink-500",
title: "text-base font-semibold",
description: "font-light text-base text-neutral-500",
description: "font-normal text-base text-default-500",
},
});
@@ -34,7 +35,7 @@ interface FeaturesGridProps {
export const FeaturesGrid: React.FC<FeaturesGridProps> = ({features, classNames, ...props}) => {
const router = useRouter();
const slots = useMemo(() => styles(), []);
const slots = styles();
const handleClick = (feat: Feature) => {
if (!feat.href) {
@@ -64,13 +65,13 @@ export const FeaturesGrid: React.FC<FeaturesGridProps> = ({features, classNames,
<p className={slots.title({class: classNames?.title})}>{feat.title}</p>
{feat.isExternal && <LinkIcon className="text-white" height={18} width={18} />}
</CardHeader>
{feat.description && (
{feat.description ? (
<CardBody className={slots.body({class: classNames?.body})}>
<p className={slots.description({class: classNames?.description})}>
{feat.description}
</p>
</CardBody>
)}
) : null}
</Card>
))}
</div>
-235
View File
@@ -1,235 +0,0 @@
import {
Button,
Input,
Tooltip,
Image,
CardFooter,
Card,
CardBody,
Switch,
Spinner,
Pagination,
Tabs,
TabItem,
Link,
} from "@nextui-org/react";
import {clsx} from "@nextui-org/shared-utils";
import {ArrowRightIcon, MoonFilledIcon, SunFilledIcon} from "@nextui-org/shared-icons";
import {useTheme} from "next-themes";
import NextLink from "next/link";
import {title, subtitle} from "@/components/primitives";
import {NextUILogo} from "@/components";
import {GithubIcon} from "@/components/icons";
import {UserTwitterCard} from "@/components/demos";
import {useIsMounted} from "@/hooks/use-is-mounted";
import {useMediaQuery} from "@/hooks/use-media-query";
const FloatingComponents: React.FC<{mounted: boolean}> = ({mounted}) => {
const {theme, setTheme} = useTheme();
const isSelected = theme === "dark" && mounted;
const isTablet = useMediaQuery(1024);
const onChange = () => {
theme === "light" ? setTheme("dark") : setTheme("light");
};
return (
<div className="hidden lg:flex flex-col relative w-1/2">
<>
<Switch
classNames={{
base:
"absolute -top-[220px] -right-[40px] animate-[levitate_13s_ease_infinite_1s_reverse]",
wrapper: "shadow-sm",
startIcon: "text-white",
}}
endIcon={<MoonFilledIcon />}
isSelected={isSelected}
size="xl"
startIcon={<SunFilledIcon />}
onChange={onChange}
/>
<Input
className="absolute -top-[130px] -right-[120px] animate-[levitate_10s_ease_infinite] w-[200px]"
color="secondary"
defaultValue="NextUI"
label="Input"
labelPosition="outside"
radius="xl"
variant="bordered"
onClear={() => {}}
/>
<Card
isFooterBlurred
className="absolute -top-[260px] right-[100px] h-[120px] animate-[levitate_12s_ease_infinite_1s] z-0 max-w-fit"
radius="2xl"
>
<Image
alt="Professional camera"
className="object-cover -translate-y-12 h-[100%]"
src="/images/card-example-6.jpeg"
width={120}
/>
<CardFooter className="before:bg-black/10 before:border before:border-white/20 overflow-hidden justify-between py-2 absolute before:rounded-xl rounded-xl bottom-1 w-[calc(100%_-_8px)] shadow-lg ml-1 z-10">
<p className="text-xs font-semibold text-white/80">Camera</p>
<p className="text-xs font-semibold text-white/80">$525</p>
</CardFooter>
</Card>
<Tabs
aria-label="Floating tabs example"
className=""
classNames={{
base: "absolute left-[170px] -top-[160px] h-10 animate-[levitate_17s_ease_infinite_1s]",
tabList: "max-w-[200px] bg-content1 shadow-sm",
}}
radius="full"
size="xs"
>
<TabItem key="notes" title="Notes" />
<TabItem key="tasks" title="Tasks" />
<TabItem key="files" title="Files" />
</Tabs>
<UserTwitterCard className="absolute left-[80px] -top-[80px] animate-[levitate_16s_ease_infinite] border-none" />
<Card className="absolute right-[110px] -top-[60px] animate-[levitate_18s_ease_infinite] shadow-lg z-10 max-w-fit border-none">
<CardBody>
<NextUILogo small size={60} />
</CardBody>
</Card>
<div className="absolute z-10 -top-[40px] -right-[230px] animate-[levitate_14s_ease_infinite_1s]">
<Pagination
isCompact
showControls
showShadow
classNames={{
base: "shadow-sm rounded-xl",
item: "bg-background dark:bg-content1",
prev: "bg-background dark:bg-content1",
next: "bg-background dark:bg-content1",
}}
initialPage={6}
total={10}
/>
</div>
{mounted && (
<Tooltip
showArrow
className="text-sm animate-[levitate_14s_ease_infinite]"
color="secondary"
content="Developers love Next.js"
isOpen={!isTablet}
placement="top"
>
<Button
className="absolute left-[200px] top-[160px] max-w-fit animate-[levitate_14s_ease_infinite_0.5s]"
color="secondary"
size="sm"
variant="flat"
>
Tooltip
</Button>
</Tooltip>
)}
<Card className="absolute right-[10px] top-[30px] animate-[levitate_16s_ease_infinite] shadow-lg z-10 max-w-fit border-none">
<CardBody>
<Spinner color="secondary" size="xl" />
</CardBody>
</Card>
<Card
isFooterBlurred
className="absolute right-[60px] top-[100px] animate-[levitate_12s_ease_infinite_1s] z-0 max-w-fit"
radius="2xl"
>
<Image
alt="Woman listing to music"
className="object-cover"
height={200}
src="/images/hero-card.jpeg"
width={200}
/>
<CardFooter className="before:bg-white/10 overflow-hidden justify-between py-2 absolute before:rounded-xl rounded-xl bottom-1 w-[calc(100%_-_8px)] shadow-lg ml-1 z-10">
<p className="text-xs text-white/80">Available soon.</p>
<Button color="secondary" radius="full" size="xs" variant="flat">
Notify me
</Button>
</CardFooter>
</Card>
</>
</div>
);
};
export const Hero = () => {
const isMounted = useIsMounted();
return (
<section className="flex relative w-full flex-nowrap justify-between items-center h-[calc(100vh_-_64px)] 2xl:h-[calc(84vh_-_64px)]">
<div className="flex flex-col gap-6 w-full lg:w-1/2 xl:mt-10">
<div className="text-center leading-8 md:leading-10 md:text-left">
<div className="inline-block">
<h1 className={title()}>Make&nbsp;</h1>
<h1 className={title({color: "violet"})}>beautiful&nbsp;</h1>
</div>
<h1 className={title()}>websites regardless of your design experience.</h1>
</div>
<h4 className={subtitle({fullWidth: true, class: "text-center md:text-left"})}>
Beautiful, fast and modern React UI library.
</h4>
<div className="flex flex-col md:flex-row items-center gap-4">
<Button
as={NextLink}
className="w-full md:w-auto"
color="primary"
endIcon={
<ArrowRightIcon
className="group-data-[hover=true]:translate-x-0.5 transition-transform"
strokeWidth={2}
/>
}
href="/docs/guide/introduction"
radius="full"
size="lg"
>
Get Started
</Button>
<Button
isExternal
as={Link}
className="w-full md:w-auto"
href="https://github.com/nextui-org/nextui"
radius="full"
size="lg"
startIcon={<GithubIcon />}
variant="bordered"
>
Github
</Button>
</div>
</div>
<FloatingComponents mounted={isMounted} />
<div
className={clsx(
"absolute -top-20 lg:top-10 w-screen h-screen -z-50 opacity-0",
"data-[mounted=true]:opacity-100 transition-opacity",
"bg-left bg-no-repeat bg-[url('/gradients/looper-pattern.svg')]",
"after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:z-[-1]",
"after:bg-gradient-to-r after:from-transparent after:to-white dark:after:to-black after:z-[-1]",
)}
data-mounted={isMounted}
/>
</section>
);
};
@@ -0,0 +1,22 @@
"use client";
import {clsx} from "@nextui-org/shared-utils";
import {useIsMounted} from "@/hooks/use-is-mounted";
export const BgLooper = () => {
const isMounted = useIsMounted();
return (
<div
className={clsx(
"absolute -top-20 lg:top-10 w-screen h-screen z-0 opacity-0 overflow-hidden",
"data-[mounted=true]:opacity-100 transition-opacity",
"bg-left bg-no-repeat bg-[url('/gradients/looper-pattern.svg')]",
"after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:z-[-1]",
"after:bg-gradient-to-r after:from-transparent after:to-background dark:after:to-black after:z-[-1]",
)}
data-mounted={isMounted}
/>
);
};

Some files were not shown because too many files have changed in this diff Show More