Compare commits

...

255 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
975 changed files with 58394 additions and 78796 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": [],
+1 -1
View File
@@ -1,7 +1,7 @@
.now/*
.next/*
*.css
examples/*
.changeset
dist
esm/*
public/*
+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 }}
+4
View File
@@ -20,6 +20,7 @@ coverage
dist/
storybook-static
/packages/storybook/public/tailwind.css
# misc
.DS_Store
.env
@@ -59,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
+6 -9
View File
@@ -77,7 +77,6 @@
"company": null,
"description": "Create custom web apps in minutes",
"image": "/sponsors/327844.jpg",
"email": null,
"twitter": "https://twitter.com/lowdefy",
"github": "https://github.com/lowdefy",
"website": "https://lowdefy.com"
@@ -110,16 +109,15 @@
"role": "BACKER",
"tier": "bronze sponsor 🥉",
"isActive": true,
"totalAmountDonated": 60,
"totalAmountDonated": 70,
"currency": "USD",
"lastTransactionAt": "2023-06-02 06:09",
"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",
"email": null,
"twitter": null,
"github": null,
"website": "https://chartbrew.com"
@@ -160,7 +158,6 @@
"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",
"email": null,
"twitter": "https://twitter.com/oxlikn",
"github": "https://github.com/0xLIKN",
"website": "https://www.likn.co/?ref=opencollective"
@@ -302,14 +299,14 @@
"currency": "USD",
"lastTransactionAt": "2023-06-13 13:51",
"lastTransactionAmount": 5,
"profile": "https://opencollective.com/jefferson-fonseca",
"name": "Jefferson Fonseca",
"company": null,
"profile": "https://opencollective.com/encurme",
"name": "EncurMe",
"company": "EncurMe",
"description": null,
"image": "/sponsors/442878.jpg",
"email": null,
"twitter": null,
"github": null,
"website": 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>
);
}
+35 -95
View File
@@ -1,27 +1,16 @@
import type {Metadata} from "next";
import type {MetaProps} from "@/libs/docs/meta";
import fs from "fs";
import path from "path";
import {notFound} from "next/navigation";
import {Spinner} from "@nextui-org/react";
import rehypeSlugPlugin from "rehype-slug";
import {serialize} from "next-mdx-remote/serialize";
import matter from "gray-matter";
import NextLink from "next/link";
import {allDocs} from "contentlayer/generated";
import {Link} from "@nextui-org/react";
import {Suspense} from "react";
import {MDXRemote} from "@/components/mdx-remote";
import manifest from "@/content/docs/manifest.json";
import {findRouteByPath} from "@/libs/docs/page";
import {extractHeadings, getAppSlug} from "@/libs/docs/utils";
import {FooterNav} from "@/components/docs/footer-nav";
import {DocsToc} from "@/components/docs/toc";
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: {
@@ -29,54 +18,40 @@ interface DocPageProps {
};
}
const cache = new Map();
async function getDocFromParams({params}: DocPageProps) {
const slug = params.slug?.join("/") || "";
const doc = allDocs.find((doc) => doc.slugAsParams === slug);
export function getDocFromParams(params: {slug: string[]}) {
let meta, doc;
const {slug} = getAppSlug(params);
if (!cache.has(slug)) {
const folderPath = path.join(process.cwd(), "content");
const filePath = path.join(folderPath, `${slug}.mdx`);
const rawFileSource = fs.readFileSync(filePath);
const {content, data} = matter(rawFileSource);
doc = content.toString();
meta = data as MetaProps;
cache.set(slug, {doc, meta});
} else {
const cached = cache.get(slug);
doc = cached.doc;
meta = cached.meta;
if (!doc) {
null;
}
const currentRoute = findRouteByPath(slug, manifest.routes);
const headings = getHeadings(doc?.body.raw);
return {
doc,
meta,
currentRoute,
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, meta} = await getDocFromParams(params);
const {doc} = await getDocFromParams({params});
if (!doc) {
return {};
}
return {
title: meta.title,
description: meta.description,
title: doc.title,
description: doc.description,
openGraph: {
title: meta.title,
description: meta.description,
title: doc.title,
description: doc.description,
type: "article",
url: meta.url,
url: doc.url,
images: [
{
url: siteConfig.ogImage,
@@ -88,74 +63,39 @@ export async function generateMetadata({params}: DocPageProps): Promise<Metadata
},
twitter: {
card: "summary_large_image",
title: meta.title,
description: meta.description,
title: doc.title,
description: doc.description,
images: [siteConfig.ogImage],
creator: siteConfig.creator,
},
};
}
async function getDocData({params}: DocPageProps) {
let meta, doc;
const {tag, slug} = getAppSlug(params);
const folderPath = path.join(process.cwd(), "content");
const filePath = path.join(folderPath, `${slug}.mdx`);
const rawFileSource = fs.readFileSync(filePath);
const {content, data} = matter(rawFileSource);
doc = content.toString();
meta = data;
const currentRoute = findRouteByPath(slug, manifest.routes);
const mdxSource = await serialize(doc, {
mdxOptions: {
rehypePlugins: [rehypeSlugPlugin as any],
},
});
return {
doc,
tag,
meta,
slug,
currentRoute,
headings: extractHeadings(mdxSource.compiledSource),
source: mdxSource,
};
export async function generateStaticParams(): Promise<DocPageProps["params"][]> {
return allDocs.map((doc) => ({
slug: doc.slugAsParams.split("/"),
}));
}
export default async function DocPage({params}: DocPageProps) {
const {doc, source, tag, slug, currentRoute, headings} = await getDocData({params});
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">
<Suspense fallback={<Spinner />}>
<MDXRemote source={source} />
</Suspense>
<MDXContent code={doc.body.code} />
</div>
{currentRoute && <FooterNav currentRoute={currentRoute} tag={tag} />}
{currentRoute && <DocsPager currentRoute={currentRoute} />}
<footer>
{tag ? (
<Link as={NextLink} href={slug || ""} size="sm">
Go to the live version of this page
</Link>
) : (
<Link isExternal showAnchorIcon href={editUrl} size="sm">
Edit this page on GitHub
</Link>
)}
<Link isExternal showAnchorIcon href={editUrl} size="sm">
Edit this page on GitHub
</Link>
</footer>
</div>
{headings && headings.length > 0 && (
+2 -2
View File
@@ -1,6 +1,6 @@
import {Image} from "@nextui-org/react";
import manifest from "@/content/docs/manifest.json";
import manifest from "@/config/routes.json";
import {DocsSidebar} from "@/components/docs/sidebar";
interface DocsLayoutProps {
@@ -10,7 +10,7 @@ interface DocsLayoutProps {
export default function DocsLayout({children}: DocsLayoutProps) {
return (
<>
<main className="relative container mx-auto max-w-7xl z-10 px-6 pt-16 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
<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} />
+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>
</>
);
}
+7 -4
View File
@@ -2,15 +2,17 @@ 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 "@/content/docs/manifest.json";
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: {
@@ -38,7 +40,7 @@ export const metadata: Metadata = {
],
icons: {
icon: "/favicon.ico",
shortcut: "/favicon-16x16.png",
shortcut: "/favicon-32x32.png",
apple: "/apple-touch-icon.png",
},
manifest: "/manifest.json",
@@ -57,17 +59,18 @@ export const metadata: Metadata = {
export default function RootLayout({children}: {children: React.ReactNode}) {
return (
<html suppressHydrationWarning lang="en">
<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 routes={manifest.routes} />
<Navbar mobileRoutes={manifest.mobileRoutes} routes={manifest.routes} />
{children}
<Footer />
</div>
<Cmdk />
</Providers>
{__PROD__ && <Analytics />}
</body>
</html>
);
+2 -2
View File
@@ -1,4 +1,4 @@
import {Spacer} from "@nextui-org/react";
import {Spacer} from "@nextui-org/spacer";
import {Hero} from "@/components/marketing/hero";
import {FeaturesGrid} from "@/components/marketing/features-grid";
@@ -29,7 +29,7 @@ export default async function Home() {
const data = await getData();
return (
<main className="container mx-auto max-w-7xl px-6 pt-16 flex-grow">
<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} />
@@ -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";
+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>
);
};
+47 -26
View File
@@ -7,13 +7,14 @@ 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 {useRouter} from "next/navigation";
import {usePathname, useRouter} from "next/navigation";
import MultiRef from "react-multi-ref";
import {cn} from "@nextui-org/theme";
import {clsx} from "@nextui-org/shared-utils";
import scrollIntoView from "scroll-into-view-if-needed";
import {isAppleDevice} from "@react-aria/utils";
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,
@@ -22,9 +23,10 @@ import {
SearchLinearIcon,
} from "./icons";
import searchData from "@/content/docs/search-meta.json";
import searchData from "@/config/search-meta.json";
import {useUpdateEffect} from "@/hooks/use-update-effect";
import {useLocalStorage} from "@/hooks/use-local-storage";
const hideOnPaths = ["examples"];
export interface CmdkStore {
isOpen: boolean;
@@ -83,12 +85,12 @@ const cmdk = tv({
"data-[active=true]:bg-primary",
"data-[active=true]:text-primary-foreground",
],
leftWrapper: ["flex", "gap-3", "items-center"],
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"],
itemContent: ["flex", "flex-col", "gap-0", "justify-center", "max-w-[80%]"],
itemParentTitle: [
"text-default-400",
"text-xs",
@@ -96,6 +98,7 @@ const cmdk = tv({
"select-none",
],
itemTitle: [
"truncate",
"text-default-500",
"group-data-[active=true]:text-primary-foreground",
"select-none",
@@ -117,6 +120,9 @@ interface SearchResultItem {
}
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("");
@@ -124,21 +130,26 @@ export const Cmdk: FC<{}> = () => {
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, setRecentSearches] = useLocalStorage<SearchResultItem[]>(
"recent-searches",
[],
);
const [recentSearches] = useLocalStorage<SearchResultItem[]>(RECENT_SEARCHES_KEY);
const addToRecentSearches = (item: SearchResultItem) => {
let searches = recentSearches ?? [];
// Avoid adding the same search again
if (!recentSearches.find((i) => i.objectID === item.objectID)) {
setRecentSearches((recentSearches) => [item, ...recentSearches].slice(0, 6));
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));
}
};
@@ -153,7 +164,7 @@ export const Cmdk: FC<{}> = () => {
if (words.length === 1) {
return matchSorter(data, query, {
keys: MATCH_KEYS,
}).slice(0, 20);
}).slice(0, MAX_RESULTS);
}
const matchesForEachWord = words.map((word) =>
@@ -162,14 +173,14 @@ export const Cmdk: FC<{}> = () => {
}),
);
const matches = intersectionBy(...matchesForEachWord, "objectID").slice(0, 20);
const matches = intersectionBy(...matchesForEachWord, "objectID").slice(0, MAX_RESULTS);
return matches;
},
[query],
);
const items = !isEmpty(results) ? results : recentSearches;
const items = !isEmpty(results) ? results : recentSearches ?? [];
// Toggle the menu when ⌘K / CTRL K is pressed
useEffect(() => {
@@ -183,8 +194,6 @@ export const Cmdk: FC<{}> = () => {
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [isOpen]);
const onItemSelect = useCallback(
@@ -193,7 +202,7 @@ export const Cmdk: FC<{}> = () => {
router.push(item.url);
addToRecentSearches(item);
},
[router],
[router, recentSearches],
);
const onInputKeyDown = useCallback(
@@ -263,11 +272,12 @@ export const Cmdk: FC<{}> = () => {
return (
<Button
isIconOnly
className={cn(
className={clsx(
"border data-[hover=true]:bg-content2 border-default-400 dark:border-default-100",
className,
)}
size="xs"
radius="full"
size="sm"
variant="bordered"
onPress={onPress}
>
@@ -325,14 +335,17 @@ export const Cmdk: FC<{}> = () => {
[activeItem, onItemSelect, CloseButton, slots],
);
const shouldOpen = !hideOnPaths.some((path) => pathname.includes(path));
return (
<Modal
hideCloseButton
backdrop="opaque"
classNames={{
wrapper: "md:items-start",
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",
@@ -340,7 +353,7 @@ export const Cmdk: FC<{}> = () => {
],
backdrop: ["bg-black/80"],
}}
isOpen={isOpen}
isOpen={isOpen && shouldOpen}
motionProps={{
onAnimationComplete: () => {
if (!isOpen) {
@@ -348,16 +361,17 @@ export const Cmdk: FC<{}> = () => {
}
},
}}
placement="top-center"
scrollBehavior="inside"
size="xl"
onClose={onClose}
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
autoFocus={!isWebKit()}
className={slots.input()}
placeholder="Search documentation"
value={query}
@@ -375,7 +389,13 @@ export const Cmdk: FC<{}> = () => {
<div className={slots.emptyWrapper()}>
<div>
<p>No results for &quot;{query}&quot;</p>
<p className="text-default-400">Try searching for something else.</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>
@@ -387,6 +407,7 @@ export const Cmdk: FC<{}> = () => {
<p className="text-default-400">No recent searches</p>
</div>
) : (
recentSearches &&
recentSearches.length > 0 && (
<Command.Group
heading={
+1 -1
View File
@@ -45,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}
>
+3 -7
View File
@@ -22,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>
@@ -34,11 +33,8 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
alt="Album cover"
as={NextImage}
className="object-cover"
classNames={{
wrapper: "shadow-black/20",
}}
height={300}
shadow="lg"
shadow="md"
src="/images/album-cover.png"
width={240}
/>
@@ -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} />;
});
}
@@ -19,6 +19,7 @@ export const UserTwitterCard: FC<UserTwitterCardProps> = ({className}) => {
<Avatar
isBordered
ImgComponent={NextImage}
alt="Zoey Lang"
imgProps={{
width: 40,
height: 40,
@@ -1,11 +1,13 @@
"use client";
import {FC} from "react";
import {tv, VariantProps} from "tailwind-variants";
const blockquoteStyles = tv({
base: "border pl-4 bg-default-50 my-6 py-3 rounded-xl [&>p]:m-0",
base: "border px-4 bg-default-50 my-6 py-3 rounded-xl [&>p]:m-0",
variants: {
color: {
default: "border-default-100 bg-default-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",
@@ -3,9 +3,12 @@
import React, {useCallback, useMemo, useRef} from "react";
import dynamic from "next/dynamic";
import {Skeleton, Tab, Tabs} from "@nextui-org/react";
import {motion, useInView} from "framer-motion";
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),
@@ -22,47 +25,57 @@ const DynamicSandpack = dynamic(() => import("../../../sandpack").then((m) => m.
loading: () => <Skeleton className="w-full h-32 rounded-xl" />,
});
interface CodeDemoProps extends UseCodeDemoProps {
interface CodeDemoProps extends UseCodeDemoProps, WindowResizerProps {
title?: string;
component?: string;
asIframe?: boolean;
showSandpackPreview?: boolean;
initialEditorOpen?: boolean;
enableResize?: boolean;
showTabs?: boolean;
showPreview?: boolean;
hideWindowActions?: boolean;
showOpenInCodeSandbox?: boolean;
isPreviewCentered?: boolean;
resizeEnabled?: boolean;
typescriptStrict?: boolean;
displayMode?: "always" | "visible";
isGradientBox?: boolean;
gradientColor?: GradientBoxProps["color"];
defaultExpanded?: boolean;
showWindowActions?: boolean;
iframeSrc?: string;
asIframe?: boolean;
iframeInitialWidth?: number;
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,
isPreviewCentered = false,
// when false .js files will be used
typescriptStrict = false,
showOpenInCodeSandbox,
showWindowActions = false,
enableResize = false,
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, {
margin: "100px",
once: true,
margin: "600px",
});
const {noInline, code} = useCodeDemo({
@@ -72,44 +85,54 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
const renderContent = useCallback(
(content: React.ReactNode) => {
if (displayMode === "always") return content;
if (displayMode === "visible") {
return (
<motion.div
animate={isInView ? "visible" : "hidden"}
initial={false}
variants={{
visible: {opacity: 1},
hidden: {opacity: 0},
}}
>
{content}
</motion.div>
);
if (!isInView) {
return <div style={{height: previewHeight}} />;
}
return content;
}
},
[displayMode, isInView],
[displayMode, previewHeight, isInView],
);
const previewContent = useMemo(() => {
if (!showPreview) return null;
const content = (
<DynamicReactLiveDemo
code={code}
enableResize={enableResize || asIframe}
height={previewHeight}
const content = asIframe ? (
<WindowResizer
hideWindowActions={hideWindowActions}
iframeHeight={previewHeight}
iframeInitialWidth={iframeInitialWidth}
iframeSrc={iframeSrc}
iframeTitle={component}
iframeTitle={title}
resizeEnabled={resizeEnabled}
/>
) : (
<DynamicReactLiveDemo
className={className}
code={code}
gradientColor={gradientColor}
height={previewHeight}
isCentered={isPreviewCentered}
isGradientBox={isGradientBox}
noInline={noInline}
overflow={overflow}
showWindowActions={showWindowActions || asIframe}
/>
);
return renderContent(content);
}, [displayMode, showPreview, isInView]);
}, [
displayMode,
isGradientBox,
gradientColor,
previewHeight,
hideWindowActions,
asIframe,
showPreview,
isInView,
className,
]);
const editorContent = useMemo(() => {
if (!showEditor) return null;
@@ -122,6 +145,7 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
showEditor={showEditor}
showOpenInCodeSandbox={showOpenInCodeSandbox || showPreview}
showPreview={showSandpackPreview}
typescriptStrict={typescriptStrict}
/>
);
@@ -138,9 +162,17 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
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">
{showTabs && showPreview ? (
{shouldRenderTabs ? (
<Tabs
disableAnimation
aria-label="Code demo tabs"
@@ -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 h-full 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;
@@ -87,15 +87,14 @@ const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
{...lineProps}
className={clsx(
lineProps.className,
language === "bash" || removeIndent ? "pr-4" : "px-4",
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,
),
"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),
},
)}
>
@@ -1,19 +1,56 @@
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;
}
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) {
@@ -22,68 +59,55 @@ export const ComponentLinks = ({
return (
<div className="flex flex-wrap gap-3 mt-6">
<Button
isExternal
as={Link}
className="text-default-700"
href={`https://storiesv2.nextui.org/?path=/story/components-${storybook || component}`}
radius="md"
size="sm"
<ButtonLink
href={`https://storybook.nextui.org/?path=/story/components-${storybook || component}`}
startContent={<StorybookIcon className="text-lg text-[#ff4785]" />}
variant="flat"
>
Storybook
</Button>
<Button
isExternal
as={Link}
className="text-default-700"
</ButtonLink>
<ButtonLink
href={`https://www.npmjs.com/package/@nextui-org/${component}`}
radius="md"
size="sm"
startContent={<NpmIcon className="text-2xl text-[#E53E3E]" />}
variant="flat"
>
{`@nextui-org/${component}`}
</Button>
</ButtonLink>
{reactAriaHook && (
<Button
isExternal
as={Link}
className="text-default-700"
<ButtonLink
href={`https://react-spectrum.adobe.com/react-aria/${reactAriaHook}.html`}
radius="md"
size="sm"
startContent={<AdobeIcon className="text-lg text-[#E1251B]" />}
variant="flat"
>
React Aria
</Button>
</ButtonLink>
)}
<Button
isExternal
as={Link}
className="text-default-700"
href={`${COMPONENT_PATH}/${component}`}
radius="md"
size="sm"
startContent={<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-default-700"
</ButtonLink>
<ButtonLink
href={`${COMPONENT_THEME_PATH}/${styles || component}.ts`}
radius="md"
size="sm"
startContent={<GithubIcon />}
variant="flat"
startContent={<GithubIcon size={20} />}
>
Styles source
</Button>
</ButtonLink>
</div>
);
};
@@ -5,4 +5,5 @@ 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";
@@ -60,10 +60,10 @@ 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>
</Tab>
);
@@ -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";
@@ -5,17 +5,16 @@ import NextLink from "next/link";
import {Link} from "@nextui-org/react";
import {ChevronIcon} from "@nextui-org/shared-icons";
import manifest from "@/content/docs/manifest.json";
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;
currentRoute?: Route;
}
export const FooterNav: React.FC<FooterNavProps> = ({currentRoute, tag}) => {
export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
const {prevRoute, nextRoute} = useDocsRoute(manifest.routes, currentRoute);
return (
@@ -26,7 +25,7 @@ export const FooterNav: React.FC<FooterNavProps> = ({currentRoute, tag}) => {
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}
@@ -40,7 +39,7 @@ export const FooterNav: React.FC<FooterNavProps> = ({currentRoute, tag}) => {
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} />
+27 -18
View File
@@ -10,10 +10,16 @@ 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, cn, Chip} 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/navigation";
import {usePathname, useRouter} from "next/navigation";
import {getRoutePaths} from "./utils";
@@ -52,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)
@@ -63,7 +70,11 @@ function TreeItem<T>(props: TreeItemProps<T>) {
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);
@@ -92,12 +103,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
className={clsx(
"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}
@@ -132,21 +138,24 @@ function TreeItem<T>(props: TreeItemProps<T>) {
href={paths.pathname}
>
<span
className={clsx({
"opacity-100": isSelected,
"opacity-60": !isSelected,
"pointer-events-none": item.props?.comingSoon,
})}
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="xs" variant="flat">
<Chip className="ml-2" color="primary" size="sm" variant="flat">
New
</Chip>
)}
{item.props?.comingSoon && (
<Chip className="ml-2" color="default" size="xs" variant="flat">
<Chip className="ml-2" color="default" size="sm" variant="flat">
Coming soon
</Chip>
)}
@@ -229,14 +238,14 @@ export interface DocsSidebarProps {
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={cn("lg:fixed lg:top-20 mt-2 z-0 lg:h-[calc(100vh-121px)]", className)}>
<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
+3 -2
View File
@@ -71,8 +71,9 @@ export const DocsToc: FC<DocsTocProps> = ({headings}) => {
className={clsx(
"transition-colors",
"font-normal",
"flex items-center text-sm font-normal text-default-300",
"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",
@@ -98,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
+7
View File
@@ -1,6 +1,7 @@
"use client";
import dynamic from "next/dynamic";
import {usePathname} from "next/navigation";
import {siteConfig} from "@/config/site";
@@ -8,6 +9,12 @@ 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">
+3
View File
@@ -13,3 +13,6 @@ 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>
);
@@ -18,3 +18,5 @@ 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,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>
);
+4 -2
View File
@@ -119,6 +119,7 @@ export const A11yOtb = () => {
classNames={{
base: "grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4",
header: "pb-3",
card: "bg-white dark:bg-default-400/10",
iconWrapper: "bg-default-100 dark:bg-transparent text-default-500/50",
}}
features={a11yItems}
@@ -128,7 +129,7 @@ export const A11yOtb = () => {
as={Link}
className="max-w-fit"
color="success"
href="/docs/theme/customize-theme"
href="/docs/customization/customize-theme"
radius="full"
size="sm"
variant="flat"
@@ -161,6 +162,7 @@ export const A11yOtb = () => {
isOpen={isDropdownOpen}
placement="bottom"
portalContainer={ref.current}
shouldBlockScroll={false}
shouldFlip={isMobile}
onOpenChange={(open) => setIsDropdownOpen(open)}
>
@@ -175,7 +177,7 @@ export const A11yOtb = () => {
color="default"
variant="flat"
>
<DropdownSection title="Actions">
<DropdownSection showDivider title="Actions">
<DropdownItem
key="new"
description="Create a new file"
@@ -60,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 = ({
@@ -96,9 +96,9 @@ const CustomThemesExample = ({
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",
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}
@@ -117,7 +117,7 @@ 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
@@ -152,7 +152,7 @@ const CustomThemesExample = ({
classNames={{
wrapper: "hidden",
labelWrapper: slots.sizeOption(),
label: "text-sm font-semibold text-inherit",
label: slots.sizeOptionLabel(),
}}
value={itemSize}
>
@@ -164,7 +164,6 @@ const CustomThemesExample = ({
<Button
className={slots.buyButton()}
color="primary"
radius="xl"
variant={selectedTheme === "nextui" ? "shadow" : "solid"}
>
Buy now
@@ -172,7 +171,7 @@ const CustomThemesExample = ({
<Button
className={slots.addToBagButton()}
color="primary"
radius="xl"
radius="full"
variant="bordered"
>
Add to bag
@@ -195,9 +194,9 @@ const CustomThemesExample = ({
<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"
@@ -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-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-default-500 font-mono text-sm",
buyButton: ["bg-foreground", "text-background", "text-sm", "font-normal", "rounded-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",
},
},
},
@@ -58,7 +58,7 @@ export const Customization = () => {
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"
+2 -2
View File
@@ -56,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",
}}
/>
@@ -78,7 +78,7 @@ export const DarkMode = () => {
as={NextLink}
className="max-w-fit"
color="warning"
href="/docs/theme/dark-mode"
href="/docs/customization/dark-mode"
radius="full"
size="sm"
variant="flat"
@@ -9,7 +9,7 @@ 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-default-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:
@@ -65,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>
@@ -14,7 +14,7 @@ export const BgLooper = () => {
"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]",
"after:bg-gradient-to-r after:from-transparent after:to-background dark:after:to-black after:z-[-1]",
)}
data-mounted={isMounted}
/>
@@ -38,14 +38,13 @@ export const FloatingComponents: React.FC<{}> = () => {
<>
<Switch
classNames={{
base:
"absolute -top-[220px] -right-[40px] animate-[levitate_13s_ease_infinite_1s_reverse]",
base: "absolute -top-[220px] -right-[40px] animate-[levitate_13s_ease_infinite_1s_reverse]",
wrapper: "shadow-sm",
startContent: "text-white",
}}
endContent={<MoonFilledIcon />}
isSelected={isSelected}
size="xl"
size="lg"
startContent={<SunFilledIcon />}
onChange={onChange}
/>
@@ -56,7 +55,6 @@ export const FloatingComponents: React.FC<{}> = () => {
defaultValue="NextUI"
label="Input"
labelPlacement="outside"
radius="xl"
variant="bordered"
onClear={() => {}}
/>
@@ -64,7 +62,6 @@ export const FloatingComponents: React.FC<{}> = () => {
<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"
@@ -74,7 +71,7 @@ export const FloatingComponents: React.FC<{}> = () => {
src="/images/card-example-6.webp"
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">
<CardFooter className="before:bg-black/10 border-white/20 border-1 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>
@@ -84,7 +81,10 @@ export const FloatingComponents: React.FC<{}> = () => {
<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">
<Card
className="absolute right-[110px] -top-[60px] animate-[levitate_18s_ease_infinite] z-10 max-w-fit border-none"
shadow="lg"
>
<CardBody>
<NextUILogo small size={60} />
</CardBody>
@@ -96,10 +96,7 @@ export const FloatingComponents: React.FC<{}> = () => {
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",
base: "rounded-xl",
}}
initialPage={6}
total={10}
@@ -114,6 +111,9 @@ export const FloatingComponents: React.FC<{}> = () => {
content="Developers love Next.js"
isOpen={!isTablet}
placement="top"
style={{
zIndex: 39,
}}
>
<Button
className="absolute left-[200px] top-[160px] max-w-fit animate-[levitate_14s_ease_infinite_0.5s]"
@@ -126,16 +126,18 @@ export const FloatingComponents: React.FC<{}> = () => {
</Tooltip>
)}
<Card className="absolute right-[10px] top-[30px] animate-[levitate_16s_ease_infinite] shadow-lg z-10 max-w-fit border-none">
<Card
className="absolute right-[10px] top-[30px] animate-[levitate_16s_ease_infinite] z-10 max-w-fit border-none"
shadow="lg"
>
<CardBody>
<Spinner color="secondary" size="xl" />
<Spinner color="secondary" size="lg" />
</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"
@@ -145,9 +147,15 @@ export const FloatingComponents: React.FC<{}> = () => {
src="/images/hero-card.webp"
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">
<CardFooter className="justify-between before:bg-white/10 border-white/20 border-1 overflow-hidden py-1 absolute before:rounded-xl rounded-large bottom-1 w-[calc(100%_-_8px)] shadow-small ml-1 z-10">
<p className="text-tiny text-white/80">Available soon.</p>
<Button
className="text-tiny text-white bg-black/20"
color="default"
radius="lg"
size="sm"
variant="flat"
>
Notify me
</Button>
</CardFooter>
@@ -9,10 +9,10 @@ export const FloatingTabs: React.FC<{}> = () => {
className=""
classNames={{
base: "absolute left-[170px] -top-[160px] h-10 animate-[levitate_17s_ease_infinite_1s]",
tabList: "max-w-[200px] bg-content1 shadow-sm",
tabList: "max-w-[200px] shadow-sm",
}}
radius="full"
size="xs"
size="sm"
>
<Tab key="notes" title="Notes" />
<Tab key="tasks" title="Tasks" />
+1 -1
View File
@@ -35,7 +35,7 @@ export const Hero = () => {
color="primary"
endContent={
<ArrowRightIcon
className="group-data-[hover=true]:translate-x-0.5 transition-transform"
className="group-data-[hover=true]:translate-x-0.5 outline-none transition-transform"
strokeWidth={2}
/>
}
@@ -4,6 +4,7 @@ import {Button, Link} from "@nextui-org/react";
import {ArrowRightIcon} from "@nextui-org/shared-icons";
import {clsx} from "@nextui-org/shared-utils";
import NextLink from "next/link";
import {Code} from "@nextui-org/react";
import {FeaturesGrid} from "./features-grid";
@@ -21,8 +22,11 @@ const bannerSuggestions = [
},
{
title: "NextUI + Next.js",
description:
"NextUI is totally compatible with Next.js you just need to customize the _app.jsx entry file to load the provider.",
description: (
<>
NextUI is fully compatible with the new Next.js 13 <Code>app/</Code> directory structure.
</>
),
icon: <NextJsIcon className="text-pink-500" />,
href: "/docs/frameworks/nextjs",
},
@@ -58,7 +62,7 @@ export const InstallBanner = () => {
color="secondary"
endContent={
<ArrowRightIcon
className="group-data-[hover=true]:translate-x-0.5 transition-transform"
className="group-data-[hover=true]:translate-x-0.5 outline-none transition-transform"
strokeWidth={2}
/>
}
+3 -3
View File
@@ -58,10 +58,10 @@ const getSponsorSize = (sponsor: Sponsor, isMobile: boolean) => {
size = isMobile ? "sm" : "md";
break;
case SPONSOR_TIERS.GOLD:
size = isMobile ? "lg" : "xl";
size = isMobile ? "md" : "lg";
break;
case SPONSOR_TIERS.PLATINUM:
size = isMobile ? "lg" : "xl";
size = isMobile ? "md" : "lg";
break;
default:
size = isMobile ? "sm" : "md";
@@ -174,7 +174,7 @@ export const Support: FC<SupportProps> = ({sponsors = []}) => {
color="secondary"
content={"Become a sponsor"}
offset={10}
radius="xl"
radius="full"
>
<Button
isIconOnly
+26 -16
View File
@@ -4,8 +4,12 @@ import * as Components from "@nextui-org/react";
import {Language} from "prism-react-renderer";
import NextImage from "next/image";
import {ThemeSwitch} from "./theme-switch";
import {Sandpack} from "@/components/sandpack";
import {CarbonAd} from "@/components/ads/carbon-ad";
import * as DocsComponents from "@/components/docs/components";
import * as BlogComponents from "@/components/blog/components";
import {Codeblock} from "@/components/docs/components";
import {VirtualAnchor, virtualAnchorEncode} from "@/components";
@@ -98,19 +102,31 @@ const List: React.FC<{children?: React.ReactNode}> = ({children}) => {
);
};
const InlineCode = ({children}: {children?: React.ReactNode}) => {
return (
<Components.Code className="font-normal bg-transparent px-0 py-0 text-code-mdx">
{children}
</Components.Code>
);
};
const Code = ({
className,
children,
metastring,
meta,
}: {
children?: React.ReactNode;
metastring?: string;
className?: string;
meta?: string;
}) => {
const isMultiLine = (children as string).split("\n").length > 2;
const isMultiLine = (children as string)?.split?.("\n")?.length > 2;
const language = (className?.replace(/language-/, "") ?? "jsx") as Language;
const codeString = String(children).trim();
if (!className) {
return <InlineCode>{children}</InlineCode>;
}
return (
<Components.Snippet
disableTooltip
@@ -129,7 +145,7 @@ const Code = ({
}}
codeString={codeString}
>
<Codeblock codeString={codeString} language={language} metastring={metastring} />
<Codeblock codeString={codeString} language={language} metastring={meta} />
</Components.Snippet>
);
};
@@ -144,14 +160,6 @@ const Link = ({href, children}: {href?: string; children?: React.ReactNode}) =>
);
};
const InlineCode = ({children}: {children?: React.ReactNode}) => {
return (
<Components.Code className="font-normal bg-transparent px-0 py-0 text-code-mdx">
{children}
</Components.Code>
);
};
export const MDXComponents = {
/**
* Next.js components
@@ -166,6 +174,11 @@ export const MDXComponents = {
*/
...DocsComponents,
Sandpack,
ThemeSwitch,
/**
* Blog components
*/
...BlogComponents,
/**
* Markdown components
*/
@@ -183,16 +196,13 @@ export const MDXComponents = {
thead: Thead,
tr: Trow,
td: Tcol,
// Playground,
// CarbonAd,
CarbonAd,
code: Code,
ul: List,
a: (props: React.HTMLAttributes<HTMLAnchorElement>) => <Link {...props} />,
blockquote: (props: Omit<React.HTMLAttributes<HTMLElement>, "color">) => (
<DocsComponents.Blockquote {...props} />
),
inlineCode: InlineCode,
InlineCode,
kbd: (props: React.HTMLAttributes<HTMLElement>) => (
<Components.Kbd {...props} className="py-0.5 px-1.5" />
),
+21
View File
@@ -0,0 +1,21 @@
/* eslint-disable react/display-name */
"use client";
import type {MDXComponents as MDXComponentsType} from "mdx/types";
import {useMDXComponent} from "next-contentlayer/hooks";
import {MDXComponents} from "./mdx-components";
interface MDXContentProps {
code: string;
}
export function MDXContent({code}: MDXContentProps) {
const Component = useMDXComponent(code);
return (
<div className="mdx">
<Component components={MDXComponents as MDXComponentsType} />
</div>
);
}
-22
View File
@@ -1,22 +0,0 @@
"use client";
import {FC} from "react";
import {MDXRemote as BaseMDXRemote, MDXRemoteSerializeResult} from "next-mdx-remote";
import {MDXComponents} from "@/components/mdx-components";
import * as componentsContent from "@/content/components";
const scope = {
// Markdown content
...componentsContent,
};
export interface MDXRemoteProps {
source?: MDXRemoteSerializeResult;
}
export const MDXRemote: FC<MDXRemoteProps> = ({source}) => {
if (!source) return null;
return <BaseMDXRemote {...source} components={MDXComponents} scope={scope} />;
};
+76 -39
View File
@@ -1,6 +1,6 @@
"use client";
import {useRef, useState, FC, ReactNode} from "react";
import {useRef, useState, FC, ReactNode, Key} from "react";
import {
link,
Navbar as NextUINavbar,
@@ -16,7 +16,9 @@ import {
DropdownMenu,
DropdownItem,
DropdownTrigger,
Chip,
} from "@nextui-org/react";
import {dataFocusVisibleClasses} from "@nextui-org/theme";
import {ChevronDownIcon, LinkIcon} from "@nextui-org/shared-icons";
import {isAppleDevice} from "@react-aria/utils";
import {clsx} from "@nextui-org/shared-utils";
@@ -28,6 +30,7 @@ import {useEffect} from "react";
import {usePress} from "@react-aria/interactions";
import {useFocusRing} from "@react-aria/focus";
import {currentVersion} from "@/utils/version";
import {siteConfig} from "@/config/site";
import {Route} from "@/libs/docs/page";
import {LargeLogo, SmallLogo, ThemeSwitch} from "@/components";
@@ -44,21 +47,22 @@ import {useCmdkStore} from "@/components/cmdk";
export interface NavbarProps {
routes: Route[];
mobileRoutes?: Route[];
tag?: string;
slug?: string;
children?: ReactNode;
}
export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], slug, tag}) => {
const [isMenuOpen, setIsMenuOpen] = useState<boolean | undefined>(false);
const [commandKey, setCommandKey] = useState<"ctrl" | "command">("command");
const ref = useRef(null);
const ref = useRef<HTMLElement>(null);
const isMounted = useIsMounted();
const pathname = usePathname();
const cmkdStore = useCmdkStore();
const cmdkStore = useCmdkStore();
useEffect(() => {
if (isMenuOpen) {
@@ -71,7 +75,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
}, []);
const {pressProps} = usePress({
onPress: () => cmkdStore.onOpen(),
onPress: () => cmdkStore.onOpen(),
});
const {focusProps, isFocusVisible} = useFocusRing();
@@ -97,12 +101,26 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
strokeWidth={2}
/>
}
onPress={() => cmkdStore.onOpen()}
onPress={() => cmdkStore.onOpen()}
>
Quick Search...
</Button>
);
if (pathname.includes("/examples")) {
return null;
}
const navLinkClasses = clsx(link({color: "foreground"}), "data-[active=true]:text-primary");
const handleVersionChange = (key: Key) => {
if (key === "v1") {
const newWindow = window.open("https://v1.nextui.org", "_blank", "noopener,noreferrer");
if (newWindow) newWindow.opener = null;
}
};
return (
<NextUINavbar
ref={ref}
@@ -111,12 +129,16 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
})}
isMenuOpen={isMenuOpen}
maxWidth="xl"
position="fixed"
position="sticky"
onMenuOpenChange={setIsMenuOpen}
>
<NavbarContent className="basis-1/5 sm:basis-full" justify="start">
<NavbarBrand as="li" className="gap-3 max-w-fit">
<NextLink aria-label="Home" className="flex justify-start items-center gap-2" href="/">
<NextLink
aria-label="Home"
className="flex justify-start items-center gap-2 tap-highlight-transparent transition-opacity active:opacity-50"
href="/"
>
<SmallLogo className="w-6 h-6 md:hidden" />
<LargeLogo className="h-5 md:h-6" />
</NextLink>
@@ -127,13 +149,13 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
<motion.div animate={{opacity: 1}} exit={{opacity: 0}} initial={{opacity: 0}}>
<DropdownTrigger>
<Button
className="hidden min-w-fit max-w-[64px] sm:flex gap-0.5 bg-default-400/20 dark:bg-default-500/20"
endContent={<ChevronDownIcon className="text-xs" />}
className="hidden text-xs h-6 w-[74px] py-1 min-w-fit sm:flex gap-0.5 bg-default-400/20 dark:bg-default-500/20"
endContent={<ChevronDownIcon className="text-tiny" />}
radius="full"
size="xs"
size="sm"
variant="flat"
>
v2.0.0
v{currentVersion}
</Button>
</DropdownTrigger>
</motion.div>
@@ -143,24 +165,22 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
aria-label="NextUI versions"
defaultSelectedKeys={["v2"]}
selectionMode="single"
onAction={handleVersionChange}
>
<DropdownItem key="v2">v2.0.0</DropdownItem>
<DropdownItem key="v2">v{currentVersion}</DropdownItem>
<DropdownItem key="v1" endContent={<LinkIcon />}>
v1.0.0
</DropdownItem>
</DropdownMenu>
</Dropdown>
) : (
<div className="w-[64px]" />
<div className="w-[74px]" />
)}
</NavbarBrand>
<ul className="hidden lg:flex gap-4 justify-start">
<ul className="hidden lg:flex gap-4 justify-start items-center">
<NavbarItem>
<NextLink
className={clsx(
link({color: "foreground"}),
"data-[active=true]:text-primary data-[active=true]:font-medium",
)}
className={navLinkClasses}
color="foreground"
data-active={includes(docsPaths, pathname)}
href="/docs/guide/introduction"
@@ -170,10 +190,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
</NavbarItem>
<NavbarItem>
<NextLink
className={clsx(
link({color: "foreground"}),
"data-[active=true]:text-primary data-[active=true]:font-medium",
)}
className={navLinkClasses}
color="foreground"
data-active={includes(pathname, "components")}
href="/docs/components/avatar"
@@ -183,17 +200,38 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
</NavbarItem>
<NavbarItem>
<NextLink
className={clsx(
link({color: "foreground"}),
"data-[active=true]:text-primary data-[active=true]:font-medium",
)}
className={navLinkClasses}
color="foreground"
data-active={pathname === "/figma"}
data-active={includes(pathname, "blog")}
href="/blog"
>
Blog
</NextLink>
</NavbarItem>
<NavbarItem>
<NextLink
className={navLinkClasses}
color="foreground"
data-active={includes(pathname, "figma")}
href="/figma"
>
Figma
</NextLink>
</NavbarItem>
<NavbarItem>
<Chip
as={NextLink}
className="hover:bg-default-100 border-default-200/80 dark:border-default-100/80 transition-colors cursor-pointer"
color="secondary"
href="/blog/v2.1.0"
variant="dot"
>
New components v2.1.0&nbsp;
<span aria-label="party emoji" role="img">
🎉
</span>
</Chip>
</NavbarItem>
</ul>
</NavbarContent>
@@ -216,13 +254,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
className={clsx(
"transition-opacity p-1 hover:opacity-80 rounded-full cursor-pointer outline-none",
// focus ring
"data-[focus-visible=true]:z-10",
"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}
{...focusProps}
@@ -231,7 +263,12 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
<SearchLinearIcon className="mt-px text-default-600 dark:text-default-500" size={20} />
</button>
</NavbarItem>
<NavbarMenuToggle aria-label={isMenuOpen ? "Close menu" : "Open menu"} className="ml-2" />
<NavbarItem className="w-10 h-full">
<NavbarMenuToggle
aria-label={isMenuOpen ? "Close menu" : "Open menu"}
className="w-full h-full pt-1"
/>
</NavbarItem>
</NavbarContent>
<NavbarContent className="hidden sm:flex basis-1/5 sm:basis-full" justify="end">
@@ -261,15 +298,15 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
>
Sponsor
</Button>
</NavbarItem>{" "}
</NavbarItem>
<NavbarMenuToggle
aria-label={isMenuOpen ? "Close menu" : "Open menu"}
className="hidden sm:flex lg:hidden"
className="hidden sm:flex lg:hidden ml-4"
/>
</NavbarContent>
<NavbarMenu>
<DocsSidebar className="mt-4" routes={routes} slug={slug} tag={tag} />
<DocsSidebar className="mt-4" routes={[...mobileRoutes, ...routes]} slug={slug} tag={tag} />
{children}
</NavbarMenu>
</NextUINavbar>
@@ -24,11 +24,11 @@ export const BugReportButton = () => {
isIconOnly
as={Link}
href={`${ISSUE_REPORT_URL}${componentTitle}`}
size="xs"
size="sm"
title="Report a bug"
variant="light"
>
<BugIcon className="text-zinc-500" height={16} width={16} />
<BugIcon className="text-white dark:text-zinc-500" height={16} width={16} />
</Button>
</Tooltip>
);
@@ -106,7 +106,7 @@ export const SandpackCodeViewer = React.forwardRef<any, CodeViewerProps>(
<SandpackStack>
{shouldShowTabs ? <FileTabs /> : null}
<div
className={clsx("sp-code-viewer", {
className={clsx("sp-code-viewer max-h-[600px] overflow-y-scroll", {
"is-expanded": isExpanded,
})}
>
@@ -13,7 +13,7 @@ export const CodeSandboxButton = () => {
placement="top"
radius="md"
>
<Button isIconOnly as="span" size="xs" title="Open in CodeSandbox" variant="light">
<Button isIconOnly as="span" size="sm" title="Open in CodeSandbox" variant="light">
<UnstyledOpenInCodeSandboxButton
style={{
width: "100%",
@@ -30,7 +30,7 @@ export const CodeSandboxButton = () => {
cursor: "pointer",
}}
>
<CodeSandboxIcon className="text-zinc-500" height={20} width={20} />
<CodeSandboxIcon className="text-white dark:text-zinc-500" height={20} width={20} />
</UnstyledOpenInCodeSandboxButton>
</Button>
</Tooltip>
@@ -23,8 +23,8 @@ export const CopyButton = () => {
content={copied ? "Copied!" : "Copy"}
radius="md"
>
<Button isIconOnly size="xs" title="Copy Code" variant="light" onClick={copyHandler}>
<CopyLinearIcon className="text-zinc-500" height={16} width={16} />
<Button isIconOnly size="sm" title="Copy Code" variant="light" onClick={copyHandler}>
<CopyLinearIcon className="text-white dark:text-zinc-500" height={16} width={16} />
</Button>
</Tooltip>
);
+2 -19
View File
@@ -1,4 +1,4 @@
export const defaultEntry = `
export const getFileEntry = (theme: string) => `
import React from "react";
import ReactDOM from "react-dom/client";
import { NextUIProvider } from "@nextui-org/react";
@@ -8,24 +8,7 @@ import "./styles.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<NextUIProvider>
<div className="w-screen h-screen p-8 flex items-start justify-center">
<App />
</div>
</NextUIProvider>
</React.StrictMode>
);`;
export const systemEntry = `
import React from "react";
import ReactDOM from "react-dom/client";
import { NextUIProvider } from "@nextui-org/system";
import App from "./App";
import "./styles.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<NextUIProvider>
<div className="w-screen h-screen p-8 flex items-start justify-center">
<div className="w-screen h-screen ${theme} text-foreground bg-background p-8 flex items-start justify-center">
<App />
</div>
</NextUIProvider>
@@ -1,4 +1,4 @@
import React from "react";
import React, {useCallback} from "react";
import {Tabs, Tab} from "@nextui-org/react";
import {SandpackPredefinedTemplate} from "@codesandbox/sandpack-react";
@@ -12,11 +12,17 @@ interface Props {
export type LanguageSelectorProps = Props;
export const LanguageSelector: React.FC<LanguageSelectorProps> = ({template, onChange}) => {
const handleToggle = () => {
const [selectedTemplate, setSelectedTemplate] = React.useState(template);
const handleToggle = useCallback(() => {
const newTemplate = template === "vite-react" ? "vite-react-ts" : "vite-react";
onChange?.(newTemplate);
};
setSelectedTemplate(newTemplate);
setTimeout(() => {
onChange?.(newTemplate);
}, 250);
}, [template, onChange]);
return (
<Tabs
@@ -27,21 +33,21 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({template, onC
tabList:
"bg-transparent relative before:bg-white/5 before:w-full before:rounded-lg before:h-full before:content-[''] before:block before:z-1 before:absolute before:inset-0 before:backdrop-blur-md before:backdrop-saturate-100",
}}
radius="lg"
selectedKey={template}
size="xs"
radius="md"
selectedKey={selectedTemplate}
size="sm"
onSelectionChange={handleToggle}
>
<Tab
key="vite-react-ts"
title={
<TypescriptIcon className="text-lg text-[#6b6b6b] group-data-[selected=true]:text-[#fafafa]" />
<TypescriptIcon className="text-lg text-white dark:text-[#6b6b6b] group-data-[selected=true]:text-[#fafafa]" />
}
/>
<Tab
key="vite-react"
title={
<JavascriptIcon className="text-lg text-[#6b6b6b] group-data-[selected=true]:text-[#fafafa]" />
<JavascriptIcon className="text-lg text-white dark:text-[#6b6b6b] group-data-[selected=true]:text-[#fafafa]" />
}
/>
</Tabs>

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