├── .all-contributorsrc ├── .eslintignore ├── .eslintrc.json ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .kodiak.toml ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── .release-it.json ├── .storybook ├── main.ts ├── manager.ts ├── preview-body.html ├── preview-head.html ├── preview.tsx ├── storybookTheme.ts └── utils.ts ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── logo.png ├── babel.config.js ├── docs-templates ├── accordion.md ├── breadcrumb.md ├── calendar.md ├── datefield.md ├── datepicker.md ├── daterange-picker.md ├── disclosure.md ├── drawer.md ├── link.md ├── meter.md ├── numberfield.md ├── pagination.md ├── progress.md ├── range-calendar.md ├── slider.md ├── timefield.md └── toast.md ├── docs ├── accordion.md ├── breadcrumb.md ├── calendar.md ├── code-base-overview.md ├── core-principles.md ├── datefield.md ├── datepicker.md ├── daterange-picker.md ├── disclosure.md ├── drawer.md ├── getting-started.md ├── link.md ├── meter.md ├── numberfield.md ├── pagination.md ├── progress.md ├── range-calendar.md ├── select.md ├── slider.md ├── timefield.md └── toast.md ├── index.d.ts ├── jest.config.ts ├── jest.setup.js ├── package.json ├── postcss.config.js ├── renovate.json ├── scripts ├── builds │ └── create-previews.js ├── docs │ ├── add-composition.js │ ├── add-csb-links.js │ ├── add-examples.js │ ├── add-props.js │ ├── add-toc.js │ ├── index.js │ ├── typeFootPrint.js │ └── utils │ │ ├── add-md-content.js │ │ ├── index.js │ │ └── md-prettify.js └── utils │ ├── common-utils.js │ ├── index.js │ └── transpile-ts.js ├── src ├── accordion │ ├── __utils.ts │ ├── accordion-base.ts │ ├── accordion-disclosure.ts │ ├── accordion-panel.ts │ ├── accordion-state.ts │ ├── index.ts │ └── stories │ │ ├── AccordionBasic.component.tsx │ │ ├── AccordionBasic.stories.tsx │ │ ├── AccordionMultiple.component.tsx │ │ ├── AccordionMultiple.stories.tsx │ │ ├── AccordionStyled.component.tsx │ │ ├── AccordionStyled.css │ │ └── AccordionStyled.stories.tsx ├── breadcrumbs │ ├── breadcrumb-link.ts │ ├── breadcrumbs-base.ts │ ├── index.ts │ └── stories │ │ ├── BreadcrumbsBasic.component.tsx │ │ ├── BreadcrumbsBasic.css │ │ └── BreadcrumbsBasic.stories.tsx ├── calendar │ ├── calendar-base-state.ts │ ├── calendar-base.ts │ ├── calendar-cell-button.ts │ ├── calendar-cell-state.ts │ ├── calendar-cell.ts │ ├── calendar-grid-state.ts │ ├── calendar-grid.ts │ ├── calendar-next-button.ts │ ├── calendar-prev-button.ts │ ├── calendar-state.ts │ ├── calendar-title.ts │ ├── index.ts │ └── stories │ │ ├── CalendarBasic.component.tsx │ │ ├── CalendarBasic.css │ │ ├── CalendarBasic.stories.tsx │ │ ├── CalendarStyled.component.tsx │ │ ├── CalendarStyled.stories.tsx │ │ ├── Utils.component.tsx │ │ └── tailwind.css ├── datefield │ ├── date-segment.ts │ ├── datefield-base-state.ts │ ├── datefield-base.ts │ ├── datefield-description.ts │ ├── datefield-errormessage.ts │ ├── datefield-label.ts │ ├── datefield-state.ts │ ├── index.ts │ └── stories │ │ ├── DateFieldBasic.component.tsx │ │ ├── DateFieldBasic.css │ │ ├── DateFieldBasic.stories.tsx │ │ ├── DateFieldStyled.component.tsx │ │ ├── DateFieldStyled.stories.tsx │ │ └── tailwind.css ├── datepicker │ ├── datepicker-base-state.ts │ ├── datepicker-disclosure.ts │ ├── datepicker-group.ts │ ├── datepicker-label.ts │ ├── datepicker-popover.ts │ ├── datepicker-state.ts │ ├── index.ts │ └── stories │ │ ├── DatePickerBasic.component.tsx │ │ ├── DatePickerBasic.css │ │ ├── DatePickerBasic.stories.tsx │ │ ├── DatePickerStyled.component.tsx │ │ ├── DatePickerStyled.stories.tsx │ │ ├── Utils.component.tsx │ │ └── tailwind.css ├── daterange-picker │ ├── daterangepicker-base-state.ts │ ├── daterangepicker-state.ts │ ├── index.ts │ └── stories │ │ ├── DateRangePickerBasic.component.tsx │ │ ├── DateRangePickerBasic.css │ │ ├── DateRangePickerBasic.stories.tsx │ │ ├── DateRangePickerStyled.component.tsx │ │ ├── DateRangePickerStyled.stories.tsx │ │ ├── Utils.component.tsx │ │ └── tailwind.css ├── disclosure │ ├── __utils.ts │ ├── disclosure-collapsible-content.ts │ ├── index.ts │ └── stories │ │ ├── DisclosureHorizontalCollapseBasic.component.tsx │ │ ├── DisclosureHorizontalCollapseBasic.stories.tsx │ │ ├── DisclosureVerticalCollapseBasic.component.tsx │ │ └── DisclosureVerticalCollapseBasic.stories.tsx ├── drawer │ ├── drawer.ts │ ├── index.ts │ └── stories │ │ ├── DrawerBasic.component.tsx │ │ ├── DrawerBasic.css │ │ └── DrawerBasic.stories.tsx ├── index.ts ├── link │ ├── index.ts │ ├── link-base.ts │ └── stories │ │ ├── LinkBasic.component.tsx │ │ ├── LinkBasic.stories.tsx │ │ ├── LinkSpan.component.tsx │ │ └── LinkSpan.stories.tsx ├── meter │ ├── __utils.ts │ ├── index.ts │ ├── meter-base.tsx │ ├── meter-state.ts │ └── stories │ │ ├── MeterBasic.component.tsx │ │ ├── MeterBasic.css │ │ ├── MeterBasic.stories.tsx │ │ ├── MeterStyled.component.tsx │ │ └── MeterStyled.stories.tsx ├── numberfield │ ├── index.ts │ ├── numberfield-base-state.ts │ ├── numberfield-decrement-button.ts │ ├── numberfield-group.ts │ ├── numberfield-increment-button.ts │ ├── numberfield-input.ts │ ├── numberfield-label.ts │ ├── numberfield-state.ts │ └── stories │ │ ├── NumberFieldBasic.component.tsx │ │ └── NumberFieldBasic.stories.tsx ├── pagination │ ├── __utils.ts │ ├── index.ts │ ├── pagination-base.ts │ ├── pagination-button.ts │ ├── pagination-state.ts │ └── stories │ │ ├── PaginationBasic.component.tsx │ │ └── PaginationBasic.stories.tsx ├── progress │ ├── __utils.ts │ ├── index.ts │ ├── progress-base.tsx │ ├── progress-state.ts │ └── stories │ │ ├── CircularProgress.component.tsx │ │ ├── CircularProgress.stories.tsx │ │ ├── LinearProgress.component.tsx │ │ ├── LinearProgress.stories.tsx │ │ ├── ProgressBasic.component.tsx │ │ ├── ProgressBasic.css │ │ └── ProgressBasic.stories.tsx ├── range-calendar │ ├── index.ts │ ├── range-calendar-base-state.ts │ ├── range-calendar-state.ts │ ├── range-calendar.ts │ └── stories │ │ ├── RangeCalendarBasic.component.tsx │ │ ├── RangeCalendarBasic.css │ │ ├── RangeCalendarBasic.stories.tsx │ │ ├── RangeCalendarStyled.component.tsx │ │ ├── RangeCalendarStyled.stories.tsx │ │ └── tailwind.css ├── slider │ ├── index.ts │ ├── slider-base-state.ts │ ├── slider-base.ts │ ├── slider-input.tsx │ ├── slider-label.ts │ ├── slider-output.ts │ ├── slider-state.ts │ ├── slider-thumb-state.ts │ ├── slider-thumb.tsx │ ├── slider-track.ts │ └── stories │ │ ├── SliderAllInOne.component.tsx │ │ ├── SliderAllInOne.stories.tsx │ │ ├── SliderBasic.component.tsx │ │ ├── SliderBasic.css │ │ ├── SliderBasic.stories.tsx │ │ ├── SliderMulti.component.tsx │ │ ├── SliderMulti.stories.tsx │ │ ├── SliderRange.component.tsx │ │ ├── SliderRange.stories.tsx │ │ ├── SliderSingle.component.tsx │ │ ├── SliderSingle.stories.tsx │ │ ├── SliderSingleOrigin.component.tsx │ │ ├── SliderSingleOrigin.stories.tsx │ │ ├── SliderSingleReversed.component.tsx │ │ ├── SliderSingleReversed.stories.tsx │ │ ├── SliderSingleVertical.component.tsx │ │ └── SliderSingleVertical.stories.tsx ├── timefield │ ├── index.ts │ ├── stories │ │ ├── TimeFieldBasic.component.tsx │ │ ├── TimeFieldBasic.css │ │ ├── TimeFieldBasic.stories.tsx │ │ ├── TimeFieldStyled.component.tsx │ │ ├── TimeFieldStyled.stories.tsx │ │ └── tailwind.css │ ├── time-segment.ts │ ├── timefield-base-state.ts │ ├── timefield-base.ts │ ├── timefield-description.ts │ ├── timefield-errormessage.ts │ ├── timefield-label.ts │ └── timefield-state.ts └── toast │ ├── CreateToastContext.tsx │ ├── CreateToastContext.types.ts │ ├── ToastState.ts │ ├── ToastTypes.ts │ ├── __keys.ts │ ├── __tests__ │ └── ToastState.test.tsx │ ├── helpers │ └── index.ts │ ├── index.ts │ ├── stories │ ├── ToastBasic.component.tsx │ ├── ToastBasic.css │ ├── ToastBasic.stories.tsx │ ├── ToastCSSAnimated.component.tsx │ ├── ToastCSSAnimated.stories.tsx │ ├── ToastCSSTransition.component.tsx │ ├── ToastCSSTransition.stories.tsx │ ├── ToastReactSpring.component.tsx │ ├── ToastReactSpring.stories.tsx │ └── Utils.component.tsx │ └── useToastTimer.tsx ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.prod.json └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "react", 3 | "projectOwner": "adaptui", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "commitConvention": "angular", 12 | "contributors": [ 13 | { 14 | "login": "navin-moorthy", 15 | "name": "Navin Moorthy", 16 | "avatar_url": "https://avatars0.githubusercontent.com/u/39694575?v=4", 17 | "profile": "https://navinmoorthy.me/", 18 | "contributions": [ 19 | "code" 20 | ] 21 | }, 22 | { 23 | "login": "anuraghazra", 24 | "name": "Anurag Hazra", 25 | "avatar_url": "https://avatars3.githubusercontent.com/u/35374649?v=4", 26 | "profile": "http://anuraghazra.github.io/", 27 | "contributions": [ 28 | "code" 29 | ] 30 | }, 31 | { 32 | "login": "sandeepprabhakaran", 33 | "name": "Sandeep Prabhakaran", 34 | "avatar_url": "https://avatars2.githubusercontent.com/u/6380293?v=4", 35 | "profile": "http://timeless.co/", 36 | "contributions": [ 37 | "ideas" 38 | ] 39 | }, 40 | { 41 | "login": "mcnaveen", 42 | "name": "MC Naveen", 43 | "avatar_url": "https://avatars.githubusercontent.com/u/8493007?v=4", 44 | "profile": "https://github.com/mcnaveen", 45 | "contributions": [ 46 | "code" 47 | ] 48 | } 49 | ], 50 | "contributorsPerLine": 7 51 | } 52 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # dotenv environment variable files 2 | .env* 3 | 4 | # testing 5 | /coverage 6 | 7 | # next.js 8 | /.next/ 9 | /out/ 10 | 11 | # Storybook build 12 | storybook-static 13 | 14 | # Local Netlify 15 | .netlify 16 | .idea 17 | *.env 18 | 19 | # Vercel folder 20 | .vercel 21 | 22 | # Optional eslint cache 23 | .eslintcache 24 | 25 | # Logs 26 | *.log* 27 | 28 | # Dependency directories 29 | node_modules 30 | jspm_packages 31 | 32 | # Mac files 33 | .DS_Store 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Yarn 2 39 | .pnp/ 40 | .pnp.* 41 | .yarn/* 42 | !.yarn/cache 43 | !.yarn/patches 44 | !.yarn/plugins 45 | !.yarn/releases 46 | !.yarn/sdks 47 | !.yarn/versions 48 | .yarn-integrity # Yarn Integrity file 49 | 50 | # Lock files 51 | package-lock.json 52 | yarn.lock 53 | 54 | ## library folder 55 | dist 56 | __js 57 | templates 58 | docs 59 | CHANGELOG.md 60 | .yalc 61 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "react-app", 4 | "react-app/jest", 5 | "plugin:prettier/recommended", 6 | "plugin:storybook/recommended" 7 | ], 8 | "plugins": ["simple-import-sort"], 9 | "rules": { 10 | "no-console": "off", 11 | "simple-import-sort/imports": [ 12 | "error", 13 | { 14 | // https://github.com/lydell/eslint-plugin-simple-import-sort#custom-grouping 15 | "groups": [ 16 | // Packages. `react` related packages come first. 17 | [ 18 | "^react", 19 | "^ariakit-utils/system", 20 | "^ariakit", 21 | "^ariakit-utils", 22 | "^@?\\w" 23 | ], 24 | // Parent imports. Put `..` last. 25 | ["^\\.\\.(?!/?$)", "^\\.\\./?$"], 26 | // Other relative imports. Put same-folder imports and `.` last. 27 | ["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"], 28 | // Style imports. 29 | ["^.+\\.s?css$"] 30 | ] 31 | } 32 | ], 33 | "simple-import-sort/exports": "error", 34 | "import/first": "error", 35 | "import/newline-after-import": "error", 36 | "import/no-duplicates": "error", 37 | "testing-library/prefer-explicit-assert": ["error"], 38 | "testing-library/no-node-access": "off", 39 | "testing-library/consistent-data-testid": [ 40 | 2, 41 | { 42 | "testIdPattern": "^testid(-.*\\w)?$" 43 | } 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Learn how to add code owners here: 2 | # https://help.github.com/en/articles/about-code-owners 3 | 4 | * @navin-moorthy 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | 5 | 6 | 7 | 8 | Fixes # (issue) 9 | 10 | ## Type of change 11 | 12 | Please delete options that are not relevant. 13 | 14 | - [ ] Bug fix (non-breaking change which fixes an issue) 15 | - [ ] New feature (non-breaking change which adds functionality) 16 | - [ ] Breaking change (fix or feature that would cause existing functionality to 17 | not work as expected) 18 | - [ ] This change requires a documentation update 19 | 20 | ## How has this been tested? 21 | 22 | Please describe the tests that you ran to verify your changes. Provide 23 | instructions so we can reproduce. 24 | 25 | - [ ] Linting Passed 26 | - [ ] Others 27 | 28 | ## Checklist: 29 | 30 | - [ ] My code follows the style guidelines of this project 31 | - [ ] I have performed a self-review of my code 32 | - [ ] I have commented my code, particularly in hard-to-understand areas 33 | - [ ] I have made corresponding changes to the documentation 34 | - [ ] My changes generate no new warnings 35 | - [ ] I have added tests that prove my fix is effective or that my feature works 36 | - [ ] New and existing unit tests pass locally with my changes 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dotenv environment variable files 2 | .env* 3 | 4 | # testing 5 | /coverage 6 | 7 | # next.js 8 | /.next/ 9 | /out/ 10 | 11 | # Storybook build 12 | storybook-static 13 | 14 | # Local Netlify 15 | .netlify 16 | .idea 17 | *.env 18 | 19 | # Vercel folder 20 | .vercel 21 | 22 | # Optional eslint cache 23 | .eslintcache 24 | 25 | # Logs 26 | *.log* 27 | 28 | # Dependency directories 29 | node_modules 30 | jspm_packages 31 | 32 | # Mac files 33 | .DS_Store 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Yarn 2 39 | .pnp/ 40 | .pnp.* 41 | .yarn/* 42 | !.yarn/cache 43 | !.yarn/patches 44 | !.yarn/plugins 45 | !.yarn/releases 46 | !.yarn/sdks 47 | !.yarn/versions 48 | .yarn-integrity # Yarn Integrity file 49 | 50 | # Lock files 51 | package-lock.json 52 | yarn.lock 53 | 54 | ## library folder 55 | dist 56 | __js 57 | templates 58 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.kodiak.toml: -------------------------------------------------------------------------------- 1 | # .kodiak.toml 2 | version = 1 3 | 4 | [merge] 5 | automerge_label = "ready 🎉" 6 | require_automerge_label = false 7 | delete_branch_on_merge = true 8 | optimistic_updates = true 9 | prioritize_ready_to_merge = true 10 | notify_on_conflict = false 11 | 12 | [merge.message] 13 | title = "pull_request_title" 14 | body = "github_default" 15 | include_pr_number = true 16 | body_type = "markdown" 17 | strip_html_comments = true 18 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # dotenv environment variable files 2 | .env* 3 | 4 | # testing 5 | /coverage 6 | 7 | # next.js 8 | /.next/ 9 | /out/ 10 | 11 | # Storybook build 12 | storybook-static 13 | 14 | # Local Netlify 15 | .netlify 16 | .idea 17 | *.env 18 | 19 | # Vercel folder 20 | .vercel 21 | 22 | # Optional eslint cache 23 | .eslintcache 24 | 25 | # Logs 26 | *.log* 27 | 28 | # Dependency directories 29 | node_modules 30 | jspm_packages 31 | 32 | # Mac files 33 | .DS_Store 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Yarn 2 39 | .pnp/ 40 | .pnp.* 41 | .yarn/* 42 | !.yarn/cache 43 | !.yarn/patches 44 | !.yarn/plugins 45 | !.yarn/releases 46 | !.yarn/sdks 47 | !.yarn/versions 48 | .yarn-integrity # Yarn Integrity file 49 | 50 | # Lock files 51 | package-lock.json 52 | yarn.lock 53 | 54 | ## library folder 55 | dist 56 | __js 57 | templates 58 | /docs 59 | CHANGELOG.md 60 | .yalc 61 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // max 120 characters per line 3 | printWidth: 80, 4 | // use 2 spaces for indentation 5 | tabWidth: 2, 6 | // use spaces instead of indentations 7 | useTabs: false, 8 | // semicolon at the end of the line 9 | semi: true, 10 | // use single quotes 11 | singleQuote: false, 12 | // object's key is quoted only when necessary 13 | quoteProps: "as-needed", 14 | // use double quotes instead of single quotes in jsx 15 | jsxSingleQuote: false, 16 | // no comma at the end 17 | trailingComma: "all", 18 | // spaces are required at the beginning and end of the braces 19 | bracketSpacing: true, 20 | // brackets are not required for arrow function parameter, when there is only one parameter 21 | arrowParens: "avoid", 22 | // format the entire contents of the file 23 | rangeStart: 0, 24 | rangeEnd: Infinity, 25 | // no need to write the beginning @prettier of the file 26 | requirePragma: false, 27 | // No need to automatically insert @prettier at the beginning of the file 28 | insertPragma: false, 29 | // use default break criteria 30 | proseWrap: "always", 31 | // decide whether to break the html according to the display style 32 | htmlWhitespaceSensitivity: "css", 33 | // vue files script and style tags indentation 34 | vueIndentScriptAndStyle: false, 35 | // lf for newline 36 | endOfLine: "lf", 37 | // formats quoted code embedded 38 | embeddedLanguageFormatting: "auto", 39 | }; 40 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "before:init": [ 4 | "if [ \"$(git log $(git describe --tags --abbrev=0)..HEAD)\" = \"\" ]; then exit 1; fi;", 5 | "yarn test", 6 | "yarn build" 7 | ] 8 | }, 9 | "git": { 10 | "requireBranch": "main", 11 | "commitMessage": "🚀 Release v${version}", 12 | "commitArgs": ["--no-verify", "-S"], 13 | "tagArgs": ["-s"] 14 | }, 15 | "github": { 16 | "release": true, 17 | "releaseName": "Release v${version}" 18 | }, 19 | "plugins": { 20 | "@release-it/conventional-changelog": { 21 | "ignoreRecommendedBump": true, 22 | "infile": "CHANGELOG.md", 23 | "preset": { 24 | "name": "conventionalcommits", 25 | "types": [ 26 | { "type": "feat", "section": "Feature Updates", "hidden": false }, 27 | { "type": "fix", "section": "Bug Fixes", "hidden": false }, 28 | { 29 | "type": "refactor", 30 | "section": "Code Refactors", 31 | "hidden": false 32 | }, 33 | { 34 | "type": "docs", 35 | "section": "Documentation Changes", 36 | "hidden": false 37 | }, 38 | { 39 | "type": "chore", 40 | "section": "Maintanance Updates", 41 | "hidden": false 42 | }, 43 | { "type": "build", "section": "Build Updates", "hidden": false }, 44 | { "type": "test", "section": "Test Updates", "hidden": false }, 45 | { "type": "style", "section": "Other Changes", "hidden": false }, 46 | { 47 | "type": "perf", 48 | "section": "Performance Improvements", 49 | "hidden": false 50 | }, 51 | { "type": "ci", "section": "CI Changes", "hidden": false }, 52 | { "type": "revert", "section": "Updates Reverted", "hidden": false } 53 | ] 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | framework: "@storybook/react", 3 | core: { builder: "webpack5" }, 4 | // storyStoreV7 removes the circular dependency issue with Webpack 5 5 | // So, we added ThemeProvider in preview.jsx and so src/theme should work for HMR 6 | features: { storyStoreV7: true, babelModeV7: true }, 7 | stories: ["../src/*/stories/*.stories.@(ts|tsx)"], 8 | addons: [ 9 | "storybook-addon-preview", 10 | "@storybook/addon-essentials", 11 | "@storybook/addon-a11y", 12 | { 13 | name: "@storybook/addon-postcss", 14 | options: { 15 | postcssLoaderOptions: { 16 | implementation: require("postcss"), 17 | }, 18 | }, 19 | }, 20 | ], 21 | staticDirs: ["../assets"], 22 | }; 23 | 24 | module.exports = config; 25 | -------------------------------------------------------------------------------- /.storybook/manager.ts: -------------------------------------------------------------------------------- 1 | import { addons } from "@storybook/addons"; 2 | import storybookTheme from "./storybookTheme"; 3 | 4 | addons.setConfig({ enableShortcuts: false, theme: storybookTheme }); 5 | -------------------------------------------------------------------------------- /.storybook/preview-body.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /.storybook/preview.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { kebabCase } from "lodash"; 3 | 4 | export const parameters = { 5 | actions: { argTypesRegex: "^on[A-Z].*" }, 6 | controls: { 7 | passArgsFirst: true, 8 | expanded: true, 9 | hideNoControlsWarning: true, 10 | }, 11 | }; 12 | 13 | export const decorators = [ 14 | (Story: any, context: any) => { 15 | document.body.id = kebabCase(context.kind); 16 | document.body.classList.add("font-sans"); 17 | document.body.classList.add("antialiased"); 18 | 19 | return ; 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /.storybook/storybookTheme.ts: -------------------------------------------------------------------------------- 1 | import { create } from "@storybook/theming"; 2 | 3 | export default create({ 4 | base: "light", 5 | brandTitle: "AdaptUI React", 6 | brandUrl: "https://github.com/adaptui/react", 7 | brandImage: "/logo.png", 8 | brandTarget: "_self", 9 | }); 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "vivaxy.vscode-conventional-commits", 6 | "christian-kohler.path-intellisense", 7 | "bradlc.vscode-tailwindcss", 8 | "orta.vscode-jest", 9 | "tlent.jest-snapshot-language-support", 10 | "vespa-dev-works.jestrunit" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "git.branchProtection": ["main"], 4 | "editor.tabSize": 2, 5 | "editor.guides.bracketPairs": true, 6 | "editor.rulers": [80, 100, 120], 7 | "editor.wordWrap": "bounded", 8 | "editor.wordWrapColumn": 120, 9 | "files.insertFinalNewline": true, 10 | "files.trimTrailingWhitespace": true, 11 | "files.eol": "\n", 12 | "editor.defaultFormatter": "esbenp.prettier-vscode", 13 | "prettier.enable": true, 14 | "editor.formatOnSave": true, 15 | "editor.codeActionsOnSave": { 16 | "source.addMissingImports": true, 17 | // "source.organizeImports": true, 18 | // "source.sortImports": true, 19 | // "source.fixAll": true 20 | "source.fixAll.eslint": true 21 | // "source.fixAll.stylelint": true 22 | }, 23 | "eslint.enable": true, 24 | "eslint.useESLintClass": true, 25 | "eslint.validate": [ 26 | "javascript", 27 | "javascriptreact", 28 | "vue", 29 | "typescript", 30 | "typescriptreact", 31 | "html" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Timeless 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adaptui/react/0be2be926f268ce9474566018d92071e37e8ff1c/assets/logo.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const BABEL_ENV = process.env.BABEL_ENV; 2 | const isCommonJS = BABEL_ENV !== undefined && BABEL_ENV === "cjs"; 3 | const isESM = BABEL_ENV !== undefined && BABEL_ENV === "esm"; 4 | const isBuild = !!BABEL_ENV; 5 | 6 | module.exports = function (api) { 7 | api.cache(true); 8 | 9 | const presets = [ 10 | [ 11 | "@babel/env", 12 | { 13 | modules: isCommonJS ? "commonjs" : false, 14 | targets: { 15 | esmodules: isESM ? true : undefined, 16 | }, 17 | }, 18 | ], 19 | ["@babel/preset-react", { runtime: "automatic" }], 20 | "@babel/preset-typescript", 21 | ]; 22 | 23 | const plugins = [ 24 | ["@babel/plugin-proposal-class-properties", { loose: true }], 25 | ["@babel/plugin-proposal-logical-assignment-operators", { loose: true }], 26 | ["@babel/plugin-proposal-private-property-in-object", { loose: true }], 27 | ["@babel/plugin-proposal-private-methods", { loose: true }], 28 | isBuild 29 | ? [ 30 | "babel-plugin-jsx-remove-data-test-id", 31 | { attributes: ["data-testid"] }, 32 | ] 33 | : {}, 34 | ]; 35 | 36 | return { 37 | presets, 38 | plugins, 39 | env: { 40 | test: { 41 | presets: [["@babel/env", { targets: { node: "current" } }]], 42 | }, 43 | }, 44 | ignore: isBuild ? ["**/*/__tests__", "**/*/stories"] : [], 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /docs-templates/accordion.md: -------------------------------------------------------------------------------- 1 | # Accordion 2 | 3 | `Accordion` component expands/collapses to show more information on clicking the 4 | trigger button. It follows The 5 | [WAI-ARIA Accordion Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/) 6 | for 7 | [keyboard interaction](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/#:~:text=at%20a%20time.-,Keyboard%20Interaction,-Enter) 8 | & 9 | [accessibiltiy properties](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/#:~:text=last%20accordion%20header.-,WAI%2DARIA%20Roles%2C%20States%2C%20and%20Properties%3A,-The%20title%20of). 10 | 11 | 12 | 13 | ## Usage 14 | 15 | 16 | 17 | 21 | 25 | 26 | ## Other Examples 27 | 28 | 32 | 36 | 37 | 42 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs-templates/breadcrumb.md: -------------------------------------------------------------------------------- 1 | # Breadcrumb 2 | 3 | `Breadcrumb` component is used for the page navigation and it provides the 4 | required aria attributes for it's links and the current link. It follows the 5 | [WAI-ARIA Breadcrumb Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/) 6 | for its 7 | [accessibility properties](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/#:~:text=Not%20applicable.-,WAI%2DARIA%20Roles%2C%20States%2C%20and%20Properties,-Breadcrumb%20trail%20is). 8 | 9 | 10 | 11 | ## Usage 12 | 13 | 14 | 15 | 20 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs-templates/calendar.md: -------------------------------------------------------------------------------- 1 | # Calendar 2 | 3 | `Calendar` component provides a way to select a date while allowing you to style 4 | them however. All the date, month & year calculations are done internally using 5 | [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) 6 | to provide the ease of use. It follows the 7 | [Grid Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/) for the keyboard 8 | navigaiton & focus management. Supports all the features as React Aria's 9 | [useCalendar](https://react-spectrum.adobe.com/react-aria/useCalendar.html#features). 10 | 11 | 12 | 13 | ## Usage 14 | 15 | 16 | 17 | 23 | 29 | 30 | ## Other Examples 31 | 32 | 38 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs-templates/datefield.md: -------------------------------------------------------------------------------- 1 | # DateField 2 | 3 | `DateField` component is an input that provides a way to select a date and time 4 | using keyboard. It follows the 5 | [Native Input Date](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date) 6 | for the keyboard navigation & accessibility features. Supports all the features 7 | of React Aria's 8 | [useDateField](https://react-spectrum.adobe.com/react-aria/useDateField.html#features). 9 | 10 | 11 | 12 | ## Usage 13 | 14 | 15 | 16 | 21 | 26 | 27 | ## Other Examples 28 | 29 | 34 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs-templates/disclosure.md: -------------------------------------------------------------------------------- 1 | # Disclosure 2 | 3 | `Disclosure` component that controls visibility of a section of content. It 4 | follows the 5 | [WAI-ARIA Disclosure Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/) 6 | for it's 7 | [keyboard interaction](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/#:~:text=Top%2DLevel%20Links-,Keyboard%20Interaction,-When%20the%20disclosure) 8 | & 9 | [accessibility properties](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/#:~:text=the%20disclosure%20content.-,WAI%2DARIA%20Roles%2C%20States%2C%20and%20Properties,-The%20element%20that). 10 | 11 | 12 | 13 | ## Usage 14 | 15 | ### Horizontal Disclosure 16 | 17 | 18 | 19 | 23 | 27 | 28 | ### Vertical Disclosure 29 | 30 | 31 | 32 | 36 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs-templates/drawer.md: -------------------------------------------------------------------------------- 1 | # Drawer 2 | 3 | `Drawer` component that controls visibility of a section of content by following 4 | the 5 | [WAI-ARIA Dialog(Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/) 6 | for it's 7 | [keyboard interaction](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/#:~:text=choosing%20a%20date.-,Keyboard%20Interaction,-In%20the%20following) 8 | & 9 | [accessibility properties](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/#:~:text=or%20cancel%20button.-,WAI%2DARIA%20Roles%2C%20States%2C%20and%20Properties,-The%20element%20that). 10 | 11 | 12 | 13 | ## Usage 14 | 15 | 16 | 17 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs-templates/link.md: -------------------------------------------------------------------------------- 1 | # Link 2 | 3 | `Link` component that provides the required aria role when used under different 4 | compositions. It follows the 5 | [WAI-ARIA Link Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/) for 6 | [keyboard interactions](https://www.w3.org/WAI/ARIA/apg/patterns/link/#:~:text=and%20img%20elements.-,Keyboard%20Interaction,-Enter) 7 | and 8 | [accessibilty properties](https://www.w3.org/WAI/ARIA/apg/patterns/link/#:~:text=for%20the%20link.-,WAI%2DARIA%20Roles%2C%20States%2C%20and%20Properties,-The%20element%20containing) 9 | 10 | 11 | 12 | ## Usage 13 | 14 | 15 | 16 | 20 | 24 | 25 | ## Other Examples 26 | 27 | 31 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs-templates/meter.md: -------------------------------------------------------------------------------- 1 | # Meter 2 | 3 | `Meter` component can be used to provide a graphical display of a numeric value 4 | that varies within a defined range. It follows the 5 | [WAI-ARIA Meter Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/meter/) for 6 | it's 7 | [accessibility properties](https://www.w3.org/WAI/ARIA/apg/patterns/meter/#:~:text=Not%20applicable.-,WAI%2DARIA%20Roles%2C%20States%2C%20and%20Properties,-The%20element%20serving). 8 | 9 | 10 | 11 | ## Usage 12 | 13 | 14 | 15 | 20 | 25 | 26 | ## Other Examples 27 | 28 | 33 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs-templates/numberfield.md: -------------------------------------------------------------------------------- 1 | # NumberField 2 | 3 | `NumberField` component is a form element used to select a number while 4 | following the keyboard interactions & accessibility properties like the 5 | [Native Number Input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number). 6 | It follows 7 | [WAI-ARIA Spin Button Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) 8 | for the 9 | [keyboard interactions](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/#:~:text=month%2C%20and%20year.-,Keyboard%20Interaction,-Up%20Arrow) 10 | and 11 | [accessibility features](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/#:~:text=to%20perform%20them.-,WAI%2DARIA%20Roles%2C%20States%2C%20and%20Properties,-The%20focusable%20element). 12 | Supports all the features as React Aria's 13 | [useNumberField](https://react-spectrum.adobe.com/react-aria/useNumberField.html#features). 14 | 15 | 16 | 17 | ## Usage 18 | 19 | 20 | 21 | 25 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs-templates/pagination.md: -------------------------------------------------------------------------------- 1 | # Pagination 2 | 3 | `Pagination` component provides all the accessibility features for the page 4 | navigation. 5 | 6 | 7 | 8 | ## Usage 9 | 10 | 11 | 12 | 16 | 20 | 21 | ## Accessibility Requirement 22 | 23 | - `Pagination` should have `aria-label` or `aria-labelledby` attribute. 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs-templates/progress.md: -------------------------------------------------------------------------------- 1 | # Progress 2 | 3 | `Progress` component provides a graphical status for tasks that take some amount 4 | of time to load. It follows 5 | [WAI-ARIA Progressbar Pattern](https://www.w3.org/TR/wai-aria-1.2/#progressbar). 6 | 7 | 8 | 9 | ## Usage 10 | 11 | 12 | 13 | 18 | 23 | 24 | ## Other Examples 25 | 26 | 31 | 36 | 37 | 42 | 47 | 48 | ## Accessibility Requirement 49 | 50 | - If the `Progress` is describing the loading progress of a particular region of 51 | a page, you should use `aria-describedby` to point to the status, and set the 52 | `aria-busy `attribute to `true` on the region until it is finished loading. 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /docs-templates/timefield.md: -------------------------------------------------------------------------------- 1 | # TimeField 2 | 3 | `TimeField` component provides a way to select a time while giving the freedom 4 | to style. It follows the 5 | [Native Input Time](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/time) 6 | for the keyboard navigation & accessibility features. Supports all the features 7 | of React Aria's 8 | [useTimeField](https://react-spectrum.adobe.com/react-aria/useTimeField.html#features). 9 | 10 | 11 | 12 | ## Usage 13 | 14 | 15 | 16 | 21 | 26 | 27 | ## Other Examples 28 | 29 | 34 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/core-principles.md: -------------------------------------------------------------------------------- 1 | # Core Principles 2 | 3 | AdaptUI React components follows all the 4 | [basic concepts from ariakit](https://reakit.io/docs/basic-concepts/) to make it 5 | more consistent overall and also composable at the same time, understanding these 6 | concepts are essential to work with components. 7 | 8 | ### Accessibility 9 | 10 | AdaptUI React components are built with a11y in mind from the ground up by strictly 11 | following the [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/) 12 | and ensuring each and every component properly supports keyboard and 13 | screen-readers. 14 | 15 | ## Composable 16 | 17 | All of our components are built in a way which provides fully composability 18 | across the board thanks to Ariakit's approach to use hooks in order to built 19 | larger components. 20 | 21 | ## Stylable 22 | 23 | Components are crafted in a way that are 100% stylable with any styling 24 | solutions available which makes them a great foundation for any Design Systems. 25 | 26 | ## Extensible 27 | 28 | With composability comes extensibility, our main goal was to make the components 29 | extensible so that even everyone can built their own Component APIs on top of 30 | our components which enable components to be adoptable in Design Systems 31 | 32 | --- 33 | 34 | Ariakit concepts which we also follow :- 35 | 36 | - Composition: [reakit.io/docs/composition](https://reakit.io/docs/composition) 37 | - Accessibility: 38 | [reakit.io/docs/accessibility](https://reakit.io/docs/accessibility) 39 | - Styling: [reakit.io/docs/styling](https://reakit.io/docs/styling) 40 | 41 |

42 | Next | Codebase Overview → 43 |

44 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom/extend-expect"; 2 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/en/configuration.html 4 | */ 5 | const { join } = require("path"); 6 | const pkg = require("./package.json"); 7 | 8 | // eslint-disable-next-line import/no-anonymous-default-export 9 | export default { 10 | rootDir: __dirname, 11 | testEnvironment: "jsdom", 12 | displayName: pkg.name, 13 | testMatch: [join(__dirname, "src/**/*.test.{js,ts,tsx}")], 14 | setupFilesAfterEnv: ["/jest.setup.js"], 15 | moduleNameMapper: { 16 | "\\.(css|less|sass|scss)$": "/src/__mocks__/styleMock.js", 17 | "^@shared(.*)$": "/shared$1", 18 | }, 19 | coveragePathIgnorePatterns: [ 20 | "node_modules", 21 | "__mocks__", 22 | "stories", 23 | "/src/meter/__examples__/index.ts", 24 | "/src/meter/__examples__/__tests__/statehook-test-data.ts", 25 | ], 26 | clearMocks: true, 27 | }; 28 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | const { 2 | matcherHint, 3 | printReceived, 4 | printExpected, 5 | } = require("jest-matcher-utils"); 6 | const { toHaveNoViolations: axeMatchers } = require("jest-axe"); 7 | const matchers = require("@testing-library/jest-dom/matchers"); 8 | 9 | // Consider [aria-activedescendant="${id}"] #${id} as the focused element. 10 | function toHaveFocus(element) { 11 | const result = matchers.toHaveFocus.call(this, element); 12 | const { activeElement } = element.ownerDocument; 13 | const activeId = 14 | activeElement && activeElement.getAttribute("aria-activedescendant"); 15 | return { 16 | ...result, 17 | pass: result.pass || activeId === element.id, 18 | message: () => { 19 | if (activeId) { 20 | return [ 21 | matcherHint(`${this.isNot ? ".not" : ""}.toHaveFocus`, "element", ""), 22 | "", 23 | "Expected:", 24 | ` ${printExpected(element)}`, 25 | "Received:", 26 | ` ${printReceived(element.ownerDocument.getElementById(activeId))}`, 27 | ].join("\n"); 28 | } 29 | return result.message(); 30 | }, 31 | }; 32 | } 33 | 34 | expect.extend({ ...matchers, ...axeMatchers, toHaveFocus }); 35 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const scopify = require("postcss-scopify"); 3 | const { kebabCase } = require("lodash"); 4 | 5 | function rewriteRootRule() { 6 | return root => { 7 | root.walkRules(rule => { 8 | rule.selectors = rule.selectors.map(selector => { 9 | if (selector === ":root") { 10 | return "&"; 11 | } 12 | return selector; 13 | }); 14 | }); 15 | }; 16 | } 17 | 18 | function addIdScope() { 19 | return root => { 20 | const filename = root.source.input.file; 21 | const isTailwind = path.basename(path.dirname(filename)) === "tailwind"; 22 | 23 | if (isTailwind) return scopify("#tailwind")(root); 24 | 25 | const basename = path.basename(filename, ".css"); 26 | const id = kebabCase(basename); 27 | 28 | return scopify(`#${id}`)(root); 29 | }; 30 | } 31 | 32 | module.exports = { 33 | plugins: [ 34 | require("postcss-import"), 35 | require("tailwindcss"), 36 | require("postcss-flexbugs-fixes"), 37 | require("autoprefixer")({ flexbox: "no-2009" }), 38 | require("postcss-merge-selectors")({ 39 | matchers: { 40 | active: { 41 | selectorFilter: /(:active|\[data-active\])/, 42 | promote: true, 43 | }, 44 | focusVisible: { 45 | selectorFilter: /(:focus-visible|\[data-focus-visible\])/, 46 | promote: true, 47 | }, 48 | }, 49 | }), 50 | rewriteRootRule(), 51 | addIdScope(), 52 | ], 53 | }; 54 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "extends": ["config:base", ":semanticCommitTypeAll(chore)"], 4 | "commitMessageAction": "⬆️ update", 5 | "ignoreDeps": ["node-fetch", "chalk"], 6 | "packageRules": [ 7 | { 8 | "matchDepTypes": ["devDependencies"], 9 | "matchUpdateTypes": ["patch", "minor"], 10 | "groupName": "dev dependencies (minor)" 11 | }, 12 | { 13 | "matchDepTypes": ["devDependencies"], 14 | "matchUpdateTypes": ["major"], 15 | "groupName": "dev dependencies (major)" 16 | }, 17 | { 18 | "matchDepTypes": ["dependencies"], 19 | "matchUpdateTypes": ["patch", "minor"], 20 | "groupName": "prod dependencies (minor)" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /scripts/docs/add-examples.js: -------------------------------------------------------------------------------- 1 | const strip = require("strip-comments"); 2 | const prettier = require("prettier/standalone"); 3 | const parserBabel = require("prettier/parser-babel"); 4 | 5 | const prettierConfig = require("../../.prettierrc.js"); 6 | const { joinCwd, extractCode } = require("../utils/common-utils"); 7 | const { addMdContent } = require("./utils"); 8 | 9 | // eslint-disable-next-line no-useless-escape 10 | const CODE_EXAMPLE_FLAG = /\<\!\-\- ADD_EXAMPLE (.*) \-\-\>/m; 11 | 12 | const addExamples = docsTemplate => { 13 | return addMdContent(docsTemplate, CODE_EXAMPLE_FLAG, (line, regexMatched) => { 14 | const importString = regexMatched[1]; 15 | const importPath = joinCwd(importString); 16 | const prettifiedCode = prettier.format(strip(extractCode(importPath)), { 17 | parser: "babel", 18 | plugins: [parserBabel], 19 | ...prettierConfig, 20 | }); 21 | return ["```js", "\n", prettifiedCode, "```"].join(""); 22 | }); 23 | }; 24 | 25 | module.exports = { addExamples }; 26 | -------------------------------------------------------------------------------- /scripts/docs/add-toc.js: -------------------------------------------------------------------------------- 1 | const toc = require("markdown-toc"); 2 | const { outdent } = require("outdent"); 3 | 4 | // eslint-disable-next-line no-useless-escape 5 | const TOC_REPLACE_FLAG = /\<\!\-\- ADD_TOC \-\-\>/m; 6 | 7 | const addToc = docsTemplate => { 8 | const tocContents = outdent` 9 | ## Table of Contents 10 | 11 | ${toc(docsTemplate, { firsth1: false }).content} 12 | `; 13 | 14 | return docsTemplate.replace(TOC_REPLACE_FLAG, tocContents); 15 | }; 16 | 17 | module.exports = { addToc }; 18 | -------------------------------------------------------------------------------- /scripts/docs/index.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const chalk = require("chalk"); 3 | 4 | const { 5 | walkSync, 6 | createFile, 7 | joinCwd, 8 | readFile, 9 | } = require("../utils/common-utils"); 10 | const { addToc } = require("./add-toc"); 11 | const { addProps } = require("./add-props"); 12 | const { addExamples } = require("./add-examples"); 13 | const { addCsbLinks } = require("./add-csb-links"); 14 | const { addComposition } = require("./add-composition"); 15 | const { mdPrettify } = require("./utils"); 16 | const { typeFootprint } = require("./typeFootPrint"); 17 | 18 | const docsFolder = joinCwd("docs"); 19 | 20 | const logProgress = (msg, fileName) => { 21 | console.log(chalk.red.yellow(`${msg}:`, chalk.red.greenBright(fileName))); 22 | }; 23 | 24 | const generateDocs = async templateFilePath => { 25 | const fileName = path.basename(templateFilePath); 26 | const template = readFile(templateFilePath, "utf-8"); 27 | 28 | const addedExamples = addExamples(template); 29 | logProgress(`Added examples`, fileName); 30 | 31 | const addedCsbLinks = await addCsbLinks(addedExamples); 32 | logProgress(`Added csb links`, fileName); 33 | 34 | const addedComposition = addComposition(addedCsbLinks); 35 | logProgress(`Added composition`, fileName); 36 | 37 | const addedProps = addProps(addedComposition); 38 | logProgress(`Added props`, fileName); 39 | 40 | const addedToc = addToc(addedProps); 41 | logProgress(`Added table of contents`, fileName); 42 | 43 | createFile(path.join(docsFolder, fileName), mdPrettify(addedToc)); 44 | logProgress(`Docs generated`, fileName); 45 | }; 46 | 47 | if (process.argv[2]) { 48 | const templateFile = path.join("docs-templates", `${process.argv[2]}.md`); 49 | const templateFilePath = joinCwd(templateFile); 50 | 51 | generateDocs(templateFilePath); 52 | } else { 53 | const docsTemplatesFolder = joinCwd("docs-templates"); 54 | 55 | const docsTemplateFiles = walkSync(docsTemplatesFolder); 56 | docsTemplateFiles.forEach(generateDocs); 57 | } 58 | -------------------------------------------------------------------------------- /scripts/docs/utils/add-md-content.js: -------------------------------------------------------------------------------- 1 | const addMdContent = (md, regex, callback) => { 2 | return md 3 | .split("\n") 4 | .map(line => { 5 | const flagMatch = line.match(regex); 6 | if (flagMatch) { 7 | return callback(line, flagMatch); 8 | } 9 | return line; 10 | }) 11 | .join("\n"); 12 | }; 13 | 14 | module.exports = { addMdContent }; 15 | -------------------------------------------------------------------------------- /scripts/docs/utils/index.js: -------------------------------------------------------------------------------- 1 | const { mdPrettify } = require("./md-prettify"); 2 | const { addMdContent } = require("./add-md-content"); 3 | 4 | module.exports = { mdPrettify, addMdContent }; 5 | -------------------------------------------------------------------------------- /scripts/docs/utils/md-prettify.js: -------------------------------------------------------------------------------- 1 | const prettier = require("prettier/standalone"); 2 | const markdownParser = require("prettier/parser-markdown"); 3 | 4 | const prettierConfig = require("../../../.prettierrc.js"); 5 | 6 | const mdPrettify = docsTemplate => { 7 | return prettier.format(docsTemplate, { 8 | parser: "markdown", 9 | plugins: [markdownParser], 10 | ...prettierConfig, 11 | }); 12 | }; 13 | 14 | module.exports = { mdPrettify }; 15 | -------------------------------------------------------------------------------- /scripts/utils/transpile-ts.js: -------------------------------------------------------------------------------- 1 | const ts = require("typescript"); 2 | const prettier = require("prettier/standalone"); 3 | const parserBabel = require("prettier/parser-babel"); 4 | 5 | const prettierConfig = require("../../.prettierrc.js"); 6 | 7 | module.exports = function transformTs(file) { 8 | const emptyLinesPreserved = file.replace(/\n$/gm, "\n/** NEWLINE **/"); 9 | 10 | const { outputText } = ts.transpileModule(emptyLinesPreserved, { 11 | compilerOptions: { 12 | target: ts.ScriptTarget.ESNext, 13 | module: ts.ModuleKind.ESNext, 14 | jsx: ts.JsxEmit.Preserve, 15 | }, 16 | }); 17 | const emptyLinesRestored = outputText.replace(/\/\*\* NEWLINE \*\*\//g, "\n"); 18 | 19 | return prettier.format(emptyLinesRestored, { 20 | parser: "babel", 21 | plugins: [parserBabel], 22 | ...prettierConfig, 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /src/accordion/__utils.ts: -------------------------------------------------------------------------------- 1 | import { createStoreContext } from "ariakit-utils/store"; 2 | 3 | import { AccordionState, Item } from "./accordion-state"; 4 | 5 | export const AccordionContext = createStoreContext(); 6 | 7 | export const getSelectedId = (state?: AccordionState, id?: string) => { 8 | if (!id) return; 9 | 10 | if (state?.allowMultiple) return state?.selectedId?.includes(id); 11 | 12 | return state?.selectedId === id; 13 | }; 14 | 15 | export const findEnabledAccordionById = (items: Item[], id?: string | null) => { 16 | return items.find(item => item.id === id && !item.disabled && !item.dimmed); 17 | }; 18 | 19 | export const findFirstEnabledAccordion = (items: Item[]) => { 20 | return items.find(item => !item.disabled && !item.dimmed); 21 | }; 22 | 23 | export const getAccordionId = ( 24 | panels?: AccordionState["panels"], 25 | id?: string, 26 | ) => { 27 | if (!id) return; 28 | 29 | return panels?.items.find(panel => panel.id === id)?.accordionId; 30 | }; 31 | 32 | export const getPanelId = (panels?: AccordionState["panels"], id?: string) => { 33 | if (!id) return; 34 | return panels?.items.find(panel => panel.accordionId === id)?.id; 35 | }; 36 | -------------------------------------------------------------------------------- /src/accordion/accordion-base.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { CompositeOptions, useComposite } from "ariakit"; 7 | import { useStoreProvider } from "ariakit-utils/store"; 8 | import { As, Props } from "ariakit-utils/types"; 9 | 10 | import { AccordionContext } from "./__utils"; 11 | import { AccordionState } from "./accordion-state"; 12 | 13 | export const useAccordion = createHook( 14 | ({ state, ...props }) => { 15 | props = useStoreProvider({ state, ...props }, AccordionContext); 16 | props = useComposite({ state, ...props }); 17 | 18 | return props; 19 | }, 20 | ); 21 | 22 | export const Accordion = createComponent(props => { 23 | const htmlProps = useAccordion(props); 24 | 25 | return createElement("div", htmlProps); 26 | }); 27 | 28 | export type AccordionOptions = Omit< 29 | CompositeOptions, 30 | "state" 31 | > & { 32 | /** 33 | * Object returned by the `useAccordionState` hook. 34 | */ 35 | state: AccordionState; 36 | }; 37 | 38 | export type AccordionProps = Props>; 39 | -------------------------------------------------------------------------------- /src/accordion/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./accordion-base"; 2 | export * from "./accordion-disclosure"; 3 | export * from "./accordion-panel"; 4 | export * from "./accordion-state"; 5 | -------------------------------------------------------------------------------- /src/accordion/stories/AccordionBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { 4 | Accordion, 5 | AccordionDisclosure, 6 | AccordionPanel, 7 | AccordionStateProps, 8 | useAccordionState, 9 | } from "../../index"; 10 | 11 | export type AccordionBasicProps = AccordionStateProps; 12 | 13 | export const AccordionBasic: React.FC = props => { 14 | const state = useAccordionState(props); 15 | 16 | return ( 17 | 18 |

19 | Trigger 1 20 |

21 | Panel 1 22 |

23 | Trigger 2 24 |

25 | Panel 2 26 |

27 | Trigger 3 28 |

29 | Panel 3 30 |

31 | Trigger 4 32 |

33 | Panel 4 34 |

35 | Trigger 5 36 |

37 | Panel 5 38 |

39 | Trigger 6 40 |

41 | Panel 6 42 |
43 | ); 44 | }; 45 | 46 | export default AccordionBasic; 47 | -------------------------------------------------------------------------------- /src/accordion/stories/AccordionMultiple.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { 4 | Accordion, 5 | AccordionDisclosure, 6 | AccordionPanel, 7 | AccordionStateProps, 8 | useAccordionState, 9 | } from "../../index"; 10 | 11 | export type AccordionMultipleProps = AccordionStateProps; 12 | 13 | export const AccordionMultiple: React.FC = props => { 14 | const state = useAccordionState({ allowMultiple: true, ...props }); 15 | 16 | return ( 17 | 18 |

19 | Trigger 1 20 |

21 | Panel 1 22 |

23 | Trigger 2 24 |

25 | Panel 2 26 |

27 | Trigger 3 28 |

29 | Panel 3 30 |

31 | Trigger 4 32 |

33 | Panel 4 34 |

35 | Trigger 5 36 |

37 | Panel 5 38 |

39 | Trigger 6 40 |

41 | Panel 6 42 |
43 | ); 44 | }; 45 | 46 | export default AccordionMultiple; 47 | -------------------------------------------------------------------------------- /src/accordion/stories/AccordionMultiple.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createControls, createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/AccordionMultipleJsx"; 6 | import ts from "./templates/AccordionMultipleTsx"; 7 | import { AccordionMultiple } from "./AccordionMultiple.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | title: "Accordion/Multiple", 14 | component: AccordionMultiple, 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ js, ts }), 18 | }, 19 | argTypes: createControls({ 20 | ignore: [ 21 | "items", 22 | "setItems", 23 | "orientation", 24 | "virtualFocus", 25 | "rtl", 26 | "focusLoop", 27 | "focusWrap", 28 | "focusShift", 29 | "moves", 30 | "includesBaseElement", 31 | "activeId", 32 | "defaultActiveId", 33 | "setMoves", 34 | "setActiveId", 35 | "selectedId", 36 | "defaultSelectedId", 37 | "setSelectedId", 38 | "allowToggle", 39 | ], 40 | }), 41 | } as Meta; 42 | 43 | export const Default: Story = { args: {} }; 44 | 45 | export const DefaultFirstIdSelected: Story = { 46 | ...Default, 47 | args: { ...Default.args, shouldSelectFirstId: true }, 48 | }; 49 | 50 | export const DefaultSelected: Story = { 51 | ...Default, 52 | args: { ...Default.args, defaultSelectedId: ["Trigger 3", "Trigger 4"] }, 53 | }; 54 | 55 | export const NoLoop: Story = { 56 | ...Default, 57 | args: { ...Default.args, focusLoop: false }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/breadcrumbs/breadcrumb-link.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { CommandOptions } from "ariakit"; 7 | import { As, Props } from "ariakit-utils/types"; 8 | 9 | import { useLink } from "../link"; 10 | 11 | export const useBreadcrumbLink = createHook( 12 | ({ isCurrentPage, ...props }) => { 13 | props = { 14 | "aria-current": isCurrentPage && "page", 15 | ...props, 16 | }; 17 | 18 | props = useLink(props); 19 | 20 | return props; 21 | }, 22 | ); 23 | 24 | export const BreadcrumbLink = createComponent(props => { 25 | const htmlProps = useBreadcrumbLink(props); 26 | 27 | return createElement("a", htmlProps); 28 | }); 29 | 30 | export type BreadcrumbLinkOptions = CommandOptions & { 31 | /** 32 | * If true, sets `aria-current: "page"` 33 | */ 34 | isCurrentPage?: boolean; 35 | }; 36 | 37 | export type BreadcrumbLinkProps = Props< 38 | BreadcrumbLinkOptions 39 | >; 40 | -------------------------------------------------------------------------------- /src/breadcrumbs/breadcrumbs-base.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | 8 | export const useBreadcrumbs = createHook(({ ...props }) => { 9 | props = { 10 | "aria-label": "breadcrumbs", 11 | ...props, 12 | }; 13 | 14 | return props; 15 | }); 16 | 17 | export const Breadcrumbs = createComponent(props => { 18 | const htmlProps = useBreadcrumbs(props); 19 | 20 | return createElement("nav", htmlProps); 21 | }); 22 | 23 | export type BreadcrumbsOptions = Options & {}; 24 | 25 | export type BreadcrumbsProps = Props< 26 | BreadcrumbsOptions 27 | >; 28 | -------------------------------------------------------------------------------- /src/breadcrumbs/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./breadcrumb-link"; 2 | export * from "./breadcrumbs-base"; 3 | -------------------------------------------------------------------------------- /src/breadcrumbs/stories/BreadcrumbsBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { BreadcrumbLink, Breadcrumbs, BreadcrumbsProps } from "../../index"; 4 | 5 | export type BreadcrumbsBasicProps = BreadcrumbsProps & {}; 6 | 7 | export const BreadcrumbsBasic: React.FC = props => { 8 | return ( 9 | 10 |
    11 |
  1. 12 | 13 | ARIA Authoring Practices Guide 14 | 15 |
  2. 16 |
  3. 17 | 18 | APG Patterns 19 | 20 |
  4. 21 |
  5. 22 | 26 | Breadcrumb Pattern 27 | 28 |
  6. 29 |
  7. 30 | 31 | Breadcrumb Example 32 | 33 |
  8. 34 |
35 |
36 | ); 37 | }; 38 | 39 | export default BreadcrumbsBasic; 40 | -------------------------------------------------------------------------------- /src/breadcrumbs/stories/BreadcrumbsBasic.css: -------------------------------------------------------------------------------- 1 | .breadcrumb { 2 | padding: 0.8em 1em; 3 | border: 1px solid hsl(0, 0%, 90%); 4 | border-radius: 4px; 5 | background: hsl(300, 14%, 97%); 6 | } 7 | 8 | .breadcrumb ol { 9 | margin: 0; 10 | padding-left: 0; 11 | list-style: none; 12 | } 13 | 14 | .breadcrumb li { 15 | display: inline; 16 | } 17 | 18 | .breadcrumb li + li::before { 19 | display: inline-block; 20 | margin: 0 0.25em; 21 | transform: rotate(15deg); 22 | border-right: 0.1em solid currentColor; 23 | height: 0.8em; 24 | content: ""; 25 | } 26 | 27 | .breadcrumb [aria-current="page"] { 28 | color: #000; 29 | font-weight: 700; 30 | text-decoration: none; 31 | } 32 | -------------------------------------------------------------------------------- /src/breadcrumbs/stories/BreadcrumbsBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/BreadcrumbsBasicJsx"; 6 | import ts from "./templates/BreadcrumbsBasicTsx"; 7 | import { BreadcrumbsBasic } from "./BreadcrumbsBasic.component"; 8 | 9 | import "./BreadcrumbsBasic.css"; 10 | 11 | type Meta = ComponentMeta; 12 | type Story = ComponentStoryObj; 13 | 14 | export default { 15 | title: "Breadcrumbs/Basic", 16 | component: BreadcrumbsBasic, 17 | parameters: { 18 | layout: "centered", 19 | preview: createPreviewTabs({ js, ts }), 20 | }, 21 | } as Meta; 22 | 23 | export const Default: Story = {}; 24 | -------------------------------------------------------------------------------- /src/calendar/calendar-base-state.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CalendarState, 3 | CalendarStateOptions as CalendarStateProps, 4 | useCalendarState, 5 | } from "@react-stately/calendar"; 6 | 7 | export function useCalendarBaseState( 8 | props: CalendarBaseStateProps, 9 | ): CalendarBaseState { 10 | const state = useCalendarState(props); 11 | 12 | return state; 13 | } 14 | 15 | export type CalendarBaseState = CalendarState & {}; 16 | 17 | export type CalendarBaseStateProps = CalendarStateProps & {}; 18 | -------------------------------------------------------------------------------- /src/calendar/calendar-base.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { CalendarState } from "./calendar-state"; 10 | 11 | export const useCalendar = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.calendarProps, props); 14 | 15 | return props; 16 | }, 17 | ); 18 | 19 | export const Calendar = createComponent(props => { 20 | const htmlProps = useCalendar(props); 21 | 22 | return createElement("div", htmlProps); 23 | }); 24 | 25 | export type CalendarOptions = Options & { 26 | /** 27 | * Object returned by the `useCalendarState` hook. 28 | */ 29 | state: CalendarState; 30 | }; 31 | 32 | export type CalendarProps = Props>; 33 | -------------------------------------------------------------------------------- /src/calendar/calendar-cell-button.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useForkRef } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | import { mergeProps } from "@react-aria/utils"; 9 | 10 | import { CalendarCellState } from "./calendar-cell-state"; 11 | 12 | export const useCalendarCellButton = createHook( 13 | ({ state, ...props }) => { 14 | props = { ...props, ref: useForkRef(state.ref, props.ref) }; 15 | props = mergeProps(state.buttonProps, props); 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const CalendarCellButton = createComponent( 22 | props => { 23 | const htmlProps = useCalendarCellButton(props); 24 | 25 | return createElement("span", htmlProps); 26 | }, 27 | ); 28 | 29 | export type CalendarCellButtonOptions = Options & { 30 | /** 31 | * Object returned by the `useCalendarCellState` hook. 32 | */ 33 | state: CalendarCellState; 34 | }; 35 | 36 | export type CalendarCellButtonProps = Props< 37 | CalendarCellButtonOptions 38 | >; 39 | -------------------------------------------------------------------------------- /src/calendar/calendar-cell-state.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useRef } from "react"; 2 | import { CalendarDate } from "@internationalized/date"; 3 | import { 4 | AriaCalendarCellProps, 5 | CalendarCellAria, 6 | useCalendarCell, 7 | } from "@react-aria/calendar"; 8 | 9 | import { RangeCalendarBaseState } from "../range-calendar"; 10 | 11 | import { CalendarBaseState } from "./calendar-base-state"; 12 | 13 | export function useCalendarCellState({ 14 | state, 15 | ...props 16 | }: CalendarCellStateProps): CalendarCellState { 17 | const ref = useRef(null); 18 | const calendarCellProps = useCalendarCell(props, state, ref); 19 | 20 | return { ...calendarCellProps, ref, baseState: state, date: props.date }; 21 | } 22 | 23 | export type CalendarCellState = CalendarCellAria & { 24 | /** 25 | * Reference for the button element within the cell inside the table 26 | */ 27 | ref: RefObject; 28 | /** 29 | * Object returned by the `useSliderState` hook. 30 | */ 31 | baseState: CalendarBaseState | RangeCalendarBaseState; 32 | /** The date that this cell represents. */ 33 | date: CalendarDate; 34 | }; 35 | 36 | export type CalendarCellStateProps = AriaCalendarCellProps & { 37 | /** 38 | * Object returned by the `useCalendarBaseState` & `RangeCalendarBaseState` hook. 39 | */ 40 | state: CalendarBaseState | RangeCalendarBaseState; 41 | }; 42 | -------------------------------------------------------------------------------- /src/calendar/calendar-grid-state.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AriaCalendarGridProps, 3 | CalendarGridAria, 4 | useCalendarGrid, 5 | } from "@react-aria/calendar"; 6 | 7 | import { RangeCalendarBaseState } from "../range-calendar"; 8 | 9 | import { CalendarBaseState } from "./calendar-base-state"; 10 | 11 | export function useCalendarGridState({ 12 | state, 13 | ...props 14 | }: CalendarGridStateProps): CalendarGridState { 15 | const calendarGridProps = useCalendarGrid(props, state); 16 | 17 | return calendarGridProps; 18 | } 19 | 20 | export type CalendarGridState = CalendarGridAria; 21 | 22 | export type CalendarGridStateProps = AriaCalendarGridProps & { 23 | /** 24 | * Object returned by the `useCalendarBaseState` & `RangeCalendarBaseState` hook. 25 | */ 26 | state: CalendarBaseState | RangeCalendarBaseState; 27 | }; 28 | -------------------------------------------------------------------------------- /src/calendar/calendar-grid.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { CalendarGridState } from "./calendar-grid-state"; 10 | 11 | export const useCalendarGrid = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.gridProps, props); 14 | 15 | return props; 16 | }, 17 | ); 18 | 19 | export const CalendarGrid = createComponent(props => { 20 | const htmlProps = useCalendarGrid(props); 21 | 22 | return createElement("table", htmlProps); 23 | }); 24 | 25 | export type CalendarGridOptions = Options & { 26 | /** 27 | * Object returned by the `useCalendarGridState` hook. 28 | */ 29 | state: CalendarGridState; 30 | }; 31 | 32 | export type CalendarGridProps = Props< 33 | CalendarGridOptions 34 | >; 35 | -------------------------------------------------------------------------------- /src/calendar/calendar-next-button.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { useForkRef } from "ariakit-utils"; 8 | import { As, Options, Props } from "ariakit-utils/types"; 9 | import { useButton } from "@react-aria/button"; 10 | import { mergeProps } from "@react-aria/utils"; 11 | 12 | import { RangeCalendarState } from "../range-calendar"; 13 | 14 | import { CalendarState } from "./calendar-state"; 15 | 16 | export const useCalendarNextButton = createHook( 17 | ({ state, ...props }) => { 18 | const ref = useRef(null); 19 | props = { ...props, ref: useForkRef(ref, props.ref) }; 20 | const { buttonProps } = useButton(state.nextButtonProps, ref); 21 | props = mergeProps(buttonProps, props); 22 | 23 | return props; 24 | }, 25 | ); 26 | 27 | export const CalendarNextButton = createComponent( 28 | props => { 29 | const htmlProps = useCalendarNextButton(props); 30 | 31 | return createElement("button", htmlProps); 32 | }, 33 | ); 34 | 35 | export type CalendarNextButtonOptions = Options & { 36 | /** 37 | * Object returned by the `useCalendarState` & `RangeCalendarState` hook. 38 | */ 39 | state: CalendarState | RangeCalendarState; 40 | }; 41 | 42 | export type CalendarNextButtonProps = Props< 43 | CalendarNextButtonOptions 44 | >; 45 | -------------------------------------------------------------------------------- /src/calendar/calendar-prev-button.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { useForkRef } from "ariakit-utils"; 8 | import { As, Options, Props } from "ariakit-utils/types"; 9 | import { useButton } from "@react-aria/button"; 10 | import { mergeProps } from "@react-aria/utils"; 11 | 12 | import { RangeCalendarState } from "../range-calendar"; 13 | 14 | import { CalendarState } from "./calendar-state"; 15 | 16 | export const useCalendarPreviousButton = 17 | createHook(({ state, ...props }) => { 18 | const ref = useRef(null); 19 | props = { ...props, ref: useForkRef(ref, props.ref) }; 20 | const { buttonProps } = useButton(state.prevButtonProps, ref); 21 | props = mergeProps(buttonProps, props); 22 | 23 | return props; 24 | }); 25 | 26 | export const CalendarPreviousButton = 27 | createComponent(props => { 28 | const htmlProps = useCalendarPreviousButton(props); 29 | 30 | return createElement("button", htmlProps); 31 | }); 32 | 33 | export type CalendarPreviousButtonOptions = 34 | Options & { 35 | /** 36 | * Object returned by the `useCalendarState` & `RangeCalendarState` hook. 37 | */ 38 | state: CalendarState | RangeCalendarState; 39 | }; 40 | 41 | export type CalendarPreviousButtonProps = Props< 42 | CalendarPreviousButtonOptions 43 | >; 44 | -------------------------------------------------------------------------------- /src/calendar/calendar-state.ts: -------------------------------------------------------------------------------- 1 | import { CalendarAria, useCalendar } from "@react-aria/calendar"; 2 | import { CalendarProps, DateValue } from "@react-types/calendar"; 3 | 4 | import { CalendarBaseState } from "./calendar-base-state"; 5 | 6 | export function useCalendarState({ 7 | state, 8 | ...props 9 | }: CalendarStateProps): CalendarState { 10 | const calendarProps = useCalendar(props, state); 11 | 12 | return calendarProps; 13 | } 14 | 15 | export type CalendarState = CalendarAria & {}; 16 | 17 | export type CalendarStateProps = CalendarProps & { 18 | /** 19 | * Object returned by the `useCalendarBaseState` hook. 20 | */ 21 | state: CalendarBaseState; 22 | }; 23 | -------------------------------------------------------------------------------- /src/calendar/calendar-title.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | 8 | import { CalendarState } from "./calendar-state"; 9 | 10 | export const useCalendarTitle = createHook( 11 | ({ state, ...props }) => { 12 | props = { children: state.title, ...props }; 13 | 14 | return props; 15 | }, 16 | ); 17 | 18 | export const CalendarTitle = createComponent(props => { 19 | const htmlProps = useCalendarTitle(props); 20 | 21 | return createElement("h2", htmlProps); 22 | }); 23 | 24 | export type CalendarTitleOptions = Options & { 25 | /** 26 | * Object returned by the `useCalendarState` hook. 27 | */ 28 | state: CalendarState; 29 | }; 30 | 31 | export type CalendarTitleProps = Props< 32 | CalendarTitleOptions 33 | >; 34 | -------------------------------------------------------------------------------- /src/calendar/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./calendar-base"; 2 | export * from "./calendar-base-state"; 3 | export * from "./calendar-cell"; 4 | export * from "./calendar-cell-button"; 5 | export * from "./calendar-cell-state"; 6 | export * from "./calendar-grid"; 7 | export * from "./calendar-grid-state"; 8 | export * from "./calendar-next-button"; 9 | export * from "./calendar-prev-button"; 10 | export * from "./calendar-state"; 11 | export * from "./calendar-title"; 12 | -------------------------------------------------------------------------------- /src/calendar/stories/CalendarBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ComponentMeta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/CalendarBasicCss"; 7 | import js from "./templates/CalendarBasicJsx"; 8 | import ts from "./templates/CalendarBasicTsx"; 9 | import jsUtils from "./templates/UtilsJsx"; 10 | import tsUtils from "./templates/UtilsTsx"; 11 | import { CalendarBasic } from "./CalendarBasic.component"; 12 | 13 | import "./CalendarBasic.css"; 14 | 15 | type Meta = ComponentMeta; 16 | // type Story = ComponentStoryObj; 17 | 18 | export default { 19 | title: "Calendar/Basic", 20 | component: CalendarBasic, 21 | parameters: { 22 | layout: "centered", 23 | preview: createPreviewTabs({ js, ts, css, jsUtils, tsUtils }), 24 | }, 25 | } as Meta; 26 | 27 | export const Default = () => { 28 | return ; 29 | }; 30 | -------------------------------------------------------------------------------- /src/calendar/stories/CalendarStyled.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ComponentMeta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import js from "./templates/CalendarStyledJsx"; 7 | import ts from "./templates/CalendarStyledTsx"; 8 | import jsUtils from "./templates/UtilsJsx"; 9 | import tsUtils from "./templates/UtilsTsx"; 10 | import { CalendarStyled } from "./CalendarStyled.component"; 11 | 12 | import "./tailwind.css"; 13 | 14 | type Meta = ComponentMeta; 15 | // type Story = ComponentStoryObj; 16 | 17 | export default { 18 | title: "Calendar/Styled", 19 | component: CalendarStyled, 20 | parameters: { 21 | layout: "centered", 22 | preview: createPreviewTabs({ js, ts, jsUtils, tsUtils }), 23 | }, 24 | decorators: [ 25 | Story => { 26 | document.body.id = "tailwind"; 27 | return ; 28 | }, 29 | ], 30 | } as Meta; 31 | 32 | export const Default = () => { 33 | return ; 34 | }; 35 | -------------------------------------------------------------------------------- /src/calendar/stories/Utils.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const ChevronLeft = (props: React.SVGProps) => { 4 | return ( 5 | 12 | 18 | 19 | ); 20 | }; 21 | 22 | export const ChevronRight = (props: React.SVGProps) => ( 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /src/calendar/stories/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .styled-datepicker .calendar__cell { 7 | height: 32px; 8 | width: 32px; 9 | max-height: 32px; 10 | max-width: 32px; 11 | @apply text-sm text-center rounded-lg; 12 | } 13 | .styled-datepicker .calendar__cell[data-is-range-selection] { 14 | @apply bg-blue-100 rounded-none text-gray-800 !important; 15 | } 16 | .styled-datepicker .calendar__cell[data-is-selection-start] { 17 | @apply bg-blue-500 rounded-l-lg text-white !important; 18 | } 19 | .styled-datepicker .calendar__cell[data-is-selection-end] { 20 | @apply bg-blue-500 rounded-r-lg text-white !important; 21 | } 22 | 23 | .styled-datepicker .calendar__cell[data-is-range-selection]:focus-within { 24 | @apply bg-blue-400 text-white !important; 25 | } 26 | .styled-datepicker .calendar__cell:focus-within { 27 | @apply bg-gray-100; 28 | } 29 | 30 | .styled-datepicker.calendar [data-weekend] { 31 | @apply text-red-600; 32 | } 33 | 34 | .styled-datepicker.calendar [aria-selected="true"] { 35 | @apply text-white bg-blue-500; 36 | } 37 | 38 | .styled-datepicker.calendar [aria-selected]:focus-within { 39 | @apply bg-gray-100; 40 | } 41 | 42 | .styled-datepicker.calendar [aria-selected="true"]:focus-within { 43 | @apply text-white bg-blue-400; 44 | } 45 | 46 | .styled-datepicker.calendar [aria-disabled="true"] { 47 | @apply text-gray-500; 48 | } 49 | 50 | .styled-datepicker.calendar span { 51 | outline: none; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/datefield/date-segment.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { useForkRef } from "ariakit-utils"; 8 | import { As, Options, Props } from "ariakit-utils/types"; 9 | import { useDateSegment as useAriaDateSegment } from "@react-aria/datepicker"; 10 | import { mergeProps } from "@react-aria/utils"; 11 | import { DateSegment as DateSegmentState } from "@react-stately/datepicker"; 12 | 13 | import { DateFieldBaseState } from "./datefield-base-state"; 14 | 15 | export const useDateSegment = createHook( 16 | ({ state, segment, ...props }) => { 17 | const ref = useRef(null); 18 | 19 | props = { ...props, ref: useForkRef(ref, props.ref) }; 20 | const { segmentProps } = useAriaDateSegment(segment, state, ref); 21 | props = mergeProps(segmentProps, props); 22 | 23 | return props; 24 | }, 25 | ); 26 | 27 | export const DateSegment = createComponent(props => { 28 | const htmlProps = useDateSegment(props); 29 | 30 | return createElement("div", htmlProps); 31 | }); 32 | 33 | export type DateSegmentOptions = Options & { 34 | /** 35 | * Current segment state return from `state.segments`. 36 | */ 37 | segment: DateSegmentState; 38 | /** 39 | * Object returned by the `useDateFieldBaseState` hook. 40 | */ 41 | state: DateFieldBaseState; 42 | }; 43 | 44 | export type DateSegmentProps = Props< 45 | DateSegmentOptions 46 | >; 47 | -------------------------------------------------------------------------------- /src/datefield/datefield-base-state.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DateFieldState, 3 | DateFieldStateOptions as DateFieldStateProps, 4 | useDateFieldState, 5 | } from "@react-stately/datepicker"; 6 | 7 | export function useDateFieldBaseState( 8 | props: DateFieldBaseStateProps, 9 | ): DateFieldBaseState { 10 | const state = useDateFieldState(props); 11 | 12 | return state; 13 | } 14 | 15 | export type DateFieldBaseState = DateFieldState & {}; 16 | 17 | export type DateFieldBaseStateProps = DateFieldStateProps & {}; 18 | -------------------------------------------------------------------------------- /src/datefield/datefield-base.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useForkRef } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | import { mergeProps } from "@react-aria/utils"; 9 | 10 | import { DateFieldState } from "./datefield-state"; 11 | 12 | export const useDateField = createHook( 13 | ({ state, ...props }) => { 14 | props = { ...props, ref: useForkRef(state.ref, props.ref) }; 15 | props = mergeProps(state.fieldProps, props); 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const DateField = createComponent(props => { 22 | const htmlProps = useDateField(props); 23 | 24 | return createElement("div", htmlProps); 25 | }); 26 | 27 | export type DateFieldOptions = Options & { 28 | /** 29 | * Object returned by the `useDateFieldState` hook. 30 | */ 31 | state: DateFieldState; 32 | }; 33 | 34 | export type DateFieldProps = Props>; 35 | -------------------------------------------------------------------------------- /src/datefield/datefield-description.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { DateFieldState } from "./datefield-state"; 10 | 11 | export const useDateFieldDescription = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.descriptionProps, props); 14 | 15 | return props; 16 | }, 17 | ); 18 | 19 | export const DateFieldDescription = 20 | createComponent(props => { 21 | const htmlProps = useDateFieldDescription(props); 22 | 23 | return createElement("label", htmlProps); 24 | }); 25 | 26 | export type DateFieldDescriptionOptions = Options & { 27 | /** 28 | * Object returned by the `useDateFieldState` hook. 29 | */ 30 | state: DateFieldState; 31 | }; 32 | 33 | export type DateFieldDescriptionProps = Props< 34 | DateFieldDescriptionOptions 35 | >; 36 | -------------------------------------------------------------------------------- /src/datefield/datefield-errormessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { DateFieldState } from "./datefield-state"; 10 | 11 | export const useDateFieldErrorMessage = 12 | createHook(({ state, ...props }) => { 13 | props = mergeProps(state.errorMessageProps, props); 14 | 15 | return props; 16 | }); 17 | 18 | export const DateFieldErrorMessage = 19 | createComponent(props => { 20 | const htmlProps = useDateFieldErrorMessage(props); 21 | 22 | return createElement("label", htmlProps); 23 | }); 24 | 25 | export type DateFieldErrorMessageOptions = 26 | Options & { 27 | /** 28 | * Object returned by the `useDateFieldState` hook. 29 | */ 30 | state: DateFieldState; 31 | }; 32 | 33 | export type DateFieldErrorMessageProps = Props< 34 | DateFieldErrorMessageOptions 35 | >; 36 | -------------------------------------------------------------------------------- /src/datefield/datefield-label.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { DateFieldState } from "./datefield-state"; 10 | 11 | export const useDateFieldLabel = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.labelProps, props); 14 | 15 | return props; 16 | }, 17 | ); 18 | 19 | export const DateFieldLabel = createComponent(props => { 20 | const htmlProps = useDateFieldLabel(props); 21 | 22 | return createElement("span", htmlProps); 23 | }); 24 | 25 | export type DateFieldLabelOptions = Options & { 26 | /** 27 | * Object returned by the `useDateFieldState` hook. 28 | */ 29 | state: DateFieldState; 30 | }; 31 | 32 | export type DateFieldLabelProps = Props< 33 | DateFieldLabelOptions 34 | >; 35 | -------------------------------------------------------------------------------- /src/datefield/datefield-state.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useRef } from "react"; 2 | import { DateValue } from "@internationalized/date"; 3 | import { 4 | AriaDateFieldProps, 5 | DateFieldAria, 6 | useDateField, 7 | } from "@react-aria/datepicker"; 8 | 9 | import { DateFieldBaseState } from "./datefield-base-state"; 10 | 11 | export function useDateFieldState({ 12 | state, 13 | ...props 14 | }: DateFieldStateProps): DateFieldState { 15 | const ref = useRef(null); 16 | const datefield = useDateField(props, state, ref); 17 | 18 | return { ...datefield, ref }; 19 | } 20 | 21 | export type DateFieldState = DateFieldAria & { 22 | /** 23 | * Reference for the date picker's visible label element, if any. 24 | */ 25 | ref: RefObject; 26 | }; 27 | 28 | export type DateFieldStateProps = AriaDateFieldProps & { 29 | /** 30 | * Object returned by the `useDateFieldBaseState` hook. 31 | */ 32 | state: DateFieldBaseState; 33 | }; 34 | -------------------------------------------------------------------------------- /src/datefield/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./date-segment"; 2 | export * from "./datefield-base"; 3 | export * from "./datefield-base-state"; 4 | export * from "./datefield-description"; 5 | export * from "./datefield-errormessage"; 6 | export * from "./datefield-label"; 7 | export * from "./datefield-state"; 8 | -------------------------------------------------------------------------------- /src/datefield/stories/DateFieldBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createCalendar } from "@internationalized/date"; 3 | import { useLocale } from "@react-aria/i18n"; 4 | 5 | import { 6 | DateField, 7 | DateFieldBaseStateProps, 8 | DateFieldLabel, 9 | DateSegment, 10 | useDateFieldBaseState, 11 | useDateFieldState, 12 | } from "../../index"; 13 | 14 | export type DateFieldBasicProps = Omit< 15 | DateFieldBaseStateProps, 16 | "locale" | "createCalendar" 17 | > & {}; 18 | 19 | // Example from https://react-spectrum.adobe.com/react-aria/useDateField.html 20 | export const DateFieldBasic: React.FC = props => { 21 | let { locale } = useLocale(); 22 | 23 | const state = useDateFieldBaseState({ locale, createCalendar, ...props }); 24 | const datefield = useDateFieldState({ ...props, state }); 25 | 26 | return ( 27 |
28 | 29 | {props.label} 30 | 31 | 32 | {state.segments.map((segment, i) => ( 33 | 41 | {segment.text} 42 | 43 | ))} 44 | {state.validationState === "invalid" && ( 45 | 46 | )} 47 | 48 |
49 | ); 50 | }; 51 | 52 | export default DateFieldBasic; 53 | -------------------------------------------------------------------------------- /src/datefield/stories/DateFieldBasic.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .datefield { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: flex-start; 9 | } 10 | 11 | .datefield__label { 12 | display: inline-flex; 13 | margin-bottom: 0.5rem; 14 | } 15 | 16 | .datefield__field { 17 | display: inline-flex; 18 | padding: 2px 4px; 19 | border-radius: 2px; 20 | border: 1px solid #6f6f6f; 21 | background: #fff; 22 | } 23 | 24 | .datefield__field--item { 25 | padding: 0 2px; 26 | border-radius: 4px; 27 | font-variant-numeric: tabular-nums; 28 | text-align: end; 29 | } 30 | 31 | .datefield__field--item.placeholder { 32 | color: #767676; 33 | } 34 | 35 | .datefield__field--item:focus { 36 | background-color: #1e65fd; 37 | outline: none; 38 | color: white; 39 | } 40 | -------------------------------------------------------------------------------- /src/datefield/stories/DateFieldBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ComponentMeta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/DateFieldBasicCss"; 7 | import js from "./templates/DateFieldBasicJsx"; 8 | import ts from "./templates/DateFieldBasicTsx"; 9 | import { DateFieldBasic } from "./DateFieldBasic.component"; 10 | 11 | import "./DateFieldBasic.css"; 12 | 13 | type Meta = ComponentMeta; 14 | // type Story = ComponentStoryObj; 15 | 16 | export default { 17 | title: "DateField/Basic", 18 | component: DateFieldBasic, 19 | parameters: { 20 | layout: "centered", 21 | preview: createPreviewTabs({ js, ts, css }), 22 | }, 23 | } as Meta; 24 | 25 | export const Default = () => { 26 | return ; 27 | }; 28 | -------------------------------------------------------------------------------- /src/datefield/stories/DateFieldStyled.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createCalendar } from "@internationalized/date"; 3 | import { useLocale } from "@react-aria/i18n"; 4 | 5 | import { 6 | DateField, 7 | DateFieldBaseStateProps, 8 | DateFieldLabel, 9 | DateSegment, 10 | useDateFieldBaseState, 11 | useDateFieldState, 12 | } from "../../index"; 13 | 14 | export type DateFieldStyledProps = Omit< 15 | DateFieldBaseStateProps, 16 | "locale" | "createCalendar" 17 | > & {}; 18 | 19 | export const DateFieldStyled: React.FC = props => { 20 | let { locale } = useLocale(); 21 | 22 | const state = useDateFieldBaseState({ locale, createCalendar, ...props }); 23 | const datefield = useDateFieldState({ ...props, state }); 24 | 25 | return ( 26 |
27 | 28 | {props.label} 29 | 30 | 34 | {state.segments.map((segment, i) => ( 35 | 43 | {segment.text} 44 | 45 | ))} 46 | 47 |
48 | ); 49 | }; 50 | 51 | export default DateFieldStyled; 52 | -------------------------------------------------------------------------------- /src/datefield/stories/DateFieldStyled.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ComponentMeta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import js from "./templates/DateFieldStyledJsx"; 7 | import ts from "./templates/DateFieldStyledTsx"; 8 | import { DateFieldStyled } from "./DateFieldStyled.component"; 9 | 10 | import "./tailwind.css"; 11 | 12 | type Meta = ComponentMeta; 13 | // type Story = ComponentStoryObj; 14 | 15 | export default { 16 | title: "DateField/Styled", 17 | component: DateFieldStyled, 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ js, ts }), 21 | }, 22 | decorators: [ 23 | Story => { 24 | document.body.id = "tailwind"; 25 | return ; 26 | }, 27 | ], 28 | } as Meta; 29 | 30 | export const Default = () => { 31 | return ; 32 | }; 33 | -------------------------------------------------------------------------------- /src/datefield/stories/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/datepicker/datepicker-base-state.ts: -------------------------------------------------------------------------------- 1 | import { PopoverState, PopoverStateProps, usePopoverState } from "ariakit"; 2 | import { 3 | DatePickerState, 4 | DatePickerStateOptions as DatePickerStateProps, 5 | useDatePickerState, 6 | } from "@react-stately/datepicker"; 7 | 8 | export function useDatePickerBaseState( 9 | props: DatePickerBaseStateProps, 10 | ): DatePickerBaseState { 11 | const datepicker = useDatePickerState(props); 12 | const { isOpen, setOpen } = datepicker; 13 | console.log("%cisOpen", "color: #007300", isOpen); 14 | 15 | const popover = usePopoverState({ 16 | open: isOpen, 17 | setOpen: setOpen, 18 | ...props, 19 | }); 20 | 21 | return { datepicker, popover }; 22 | } 23 | 24 | export type DatePickerBaseState = { 25 | /** 26 | * Object returned by the `useDatePickerState` hook. 27 | */ 28 | datepicker: DatePickerState; 29 | /** 30 | * Object returned by the `usePopoverState` hook. 31 | */ 32 | popover: PopoverState; 33 | }; 34 | 35 | export type DatePickerBaseStateProps = DatePickerStateProps & 36 | PopoverStateProps & {}; 37 | -------------------------------------------------------------------------------- /src/datepicker/datepicker-disclosure.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { PopoverDisclosureOptions, usePopoverDisclosure } from "ariakit"; 8 | import { useForkRef } from "ariakit-utils"; 9 | import { As, Props } from "ariakit-utils/types"; 10 | import { useButton } from "@react-aria/button"; 11 | 12 | import { DateRangePickerState } from "../daterange-picker"; 13 | 14 | import { DatePickerState } from "./datepicker-state"; 15 | 16 | export const useDatePickerDisclosure = createHook( 17 | ({ state, ...props }) => { 18 | const ref = useRef(null); 19 | props = { ...props, ref: useForkRef(ref, props.ref, state.ref) }; 20 | 21 | props = usePopoverDisclosure({ ...props, state: state.baseState.popover }); 22 | 23 | const { buttonProps } = useButton(state.buttonProps, ref); 24 | props = { ...props, ...buttonProps }; 25 | 26 | return props; 27 | }, 28 | ); 29 | 30 | export const DatePickerDisclosure = 31 | createComponent(props => { 32 | const htmlProps = useDatePickerDisclosure(props); 33 | 34 | return createElement("button", htmlProps); 35 | }); 36 | 37 | export type DatePickerDisclosureOptions = Omit< 38 | PopoverDisclosureOptions, 39 | "state" 40 | > & { 41 | /** 42 | * Object returned by the `useDatePickerState` & `useDateRangePickerState` hook. 43 | */ 44 | state: DatePickerState | DateRangePickerState; 45 | }; 46 | 47 | export type DatePickerDisclosureProps = Props< 48 | DatePickerDisclosureOptions 49 | >; 50 | -------------------------------------------------------------------------------- /src/datepicker/datepicker-group.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useForkRef } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | import { mergeProps } from "@react-aria/utils"; 9 | 10 | import { DateRangePickerState } from "../daterange-picker"; 11 | 12 | import { DatePickerState } from "./datepicker-state"; 13 | 14 | export const useDatePickerGroup = createHook( 15 | ({ state, ...props }) => { 16 | props = { ...props, ref: useForkRef(state.ref, props.ref) }; 17 | props = mergeProps(state.groupProps, props); 18 | 19 | return props; 20 | }, 21 | ); 22 | 23 | export const DatePickerGroup = createComponent( 24 | props => { 25 | const htmlProps = useDatePickerGroup(props); 26 | 27 | return createElement("div", htmlProps); 28 | }, 29 | ); 30 | 31 | export type DatePickerGroupOptions = Options & { 32 | /** 33 | * Object returned by the `useDatePickerState` & `useDateRangePickerState` hook. 34 | */ 35 | state: DatePickerState | DateRangePickerState; 36 | }; 37 | 38 | export type DatePickerGroupProps = Props< 39 | DatePickerGroupOptions 40 | >; 41 | -------------------------------------------------------------------------------- /src/datepicker/datepicker-label.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { DateRangePickerState } from "../daterange-picker"; 10 | 11 | import { DatePickerState } from "./datepicker-state"; 12 | 13 | export const useDatePickerLabel = createHook( 14 | ({ state, ...props }) => { 15 | props = mergeProps(state.labelProps, props); 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const DatePickerLabel = createComponent( 22 | props => { 23 | const htmlProps = useDatePickerLabel(props); 24 | 25 | return createElement("span", htmlProps); 26 | }, 27 | ); 28 | 29 | export type DatePickerLabelOptions = Options & { 30 | /** 31 | * Object returned by the `useDatePickerState` & `useDateRangePickerState` hook. 32 | */ 33 | state: DatePickerState | DateRangePickerState; 34 | }; 35 | 36 | export type DatePickerLabelProps = Props< 37 | DatePickerLabelOptions 38 | >; 39 | -------------------------------------------------------------------------------- /src/datepicker/datepicker-popover.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { PopoverOptions, usePopover } from "ariakit"; 7 | import { useEvent } from "ariakit-utils"; 8 | import { As, Props } from "ariakit-utils/types"; 9 | import { mergeProps } from "@react-aria/utils"; 10 | 11 | import { DateRangePickerState } from "../daterange-picker"; 12 | 13 | import { DatePickerState } from "./datepicker-state"; 14 | 15 | export const useDatePickerPopover = createHook( 16 | ({ state, ...props }) => { 17 | const onKeyDownProp = props.onKeyDown; 18 | 19 | const onKeyDown = useEvent((event: React.KeyboardEvent) => { 20 | onKeyDownProp?.(event); 21 | 22 | if (event.key !== "Escape") return; 23 | state.baseState.popover.hide(); 24 | 25 | if (event.defaultPrevented) return; 26 | }); 27 | 28 | props = usePopover({ 29 | ...props, 30 | state: state.baseState.popover, 31 | modal: true, 32 | autoFocusOnShow: false, 33 | backdropProps: { onKeyDown }, 34 | }); 35 | props = mergeProps(state.dialogProps, props); 36 | 37 | return props; 38 | }, 39 | ); 40 | 41 | export const DatePickerPopover = createComponent( 42 | props => { 43 | const htmlProps = useDatePickerPopover(props); 44 | 45 | return createElement("span", htmlProps); 46 | }, 47 | ); 48 | 49 | export type DatePickerPopoverOptions = Omit< 50 | PopoverOptions, 51 | "state" 52 | > & { 53 | /** 54 | * Object returned by the `useDatePickerState` & `useDateRangePickerState` hook. 55 | */ 56 | state: DatePickerState | DateRangePickerState; 57 | }; 58 | 59 | export type DatePickerPopoverProps = Props< 60 | DatePickerPopoverOptions 61 | >; 62 | -------------------------------------------------------------------------------- /src/datepicker/datepicker-state.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useRef } from "react"; 2 | import { DateValue } from "@internationalized/date"; 3 | import { DatePickerAria, useDatePicker } from "@react-aria/datepicker"; 4 | import { AriaDatePickerProps } from "@react-types/datepicker"; 5 | 6 | import { DatePickerBaseState } from "./datepicker-base-state"; 7 | 8 | export function useDatePickerState({ 9 | state, 10 | ...props 11 | }: DatePickerStateProps): DatePickerState { 12 | const ref = useRef(null); 13 | const datepicker = useDatePicker(props, state.datepicker, ref); 14 | 15 | return { ...datepicker, ref, baseState: state }; 16 | } 17 | 18 | export type DatePickerState = DatePickerAria & { 19 | /** 20 | * Reference for the date picker's visible label element, if any. 21 | */ 22 | ref: RefObject; 23 | /** 24 | * Object returned by the `useDatePickerBaseState` hook. 25 | */ 26 | baseState: DatePickerBaseState; 27 | }; 28 | 29 | export type DatePickerStateProps = 30 | AriaDatePickerProps & { 31 | /** 32 | * Object returned by the `useDatePickerBaseState` hook. 33 | */ 34 | state: DatePickerBaseState; 35 | }; 36 | -------------------------------------------------------------------------------- /src/datepicker/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./datepicker-base-state"; 2 | export * from "./datepicker-disclosure"; 3 | export * from "./datepicker-group"; 4 | export * from "./datepicker-label"; 5 | export * from "./datepicker-popover"; 6 | export * from "./datepicker-state"; 7 | -------------------------------------------------------------------------------- /src/datepicker/stories/DatePickerBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import CalendarBasic from "../../calendar/stories/CalendarBasic.component"; 4 | import DateFieldBasic from "../../datefield/stories/DateFieldBasic.component"; 5 | import { 6 | DatePickerBaseStateProps, 7 | DatePickerDisclosure, 8 | DatePickerGroup, 9 | DatePickerLabel, 10 | DatePickerPopover, 11 | useDatePickerBaseState, 12 | useDatePickerState, 13 | } from "../../index"; 14 | 15 | import { CalendarIcon } from "./Utils.component"; 16 | 17 | export type DatePickerBasicProps = DatePickerBaseStateProps & {}; 18 | 19 | export const DatePickerBasic: React.FC = props => { 20 | const state = useDatePickerBaseState({ ...props, gutter: 10 }); 21 | const datepicker = useDatePickerState({ ...props, state }); 22 | 23 | return ( 24 |
25 | 26 | {props.label} 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | {state.popover.open && ( 38 | 39 | 40 | 41 | )} 42 |
43 | ); 44 | }; 45 | 46 | export default DatePickerBasic; 47 | -------------------------------------------------------------------------------- /src/datepicker/stories/DatePickerBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import css from "./templates/DatePickerBasicCss"; 6 | import js from "./templates/DatePickerBasicJsx"; 7 | import ts from "./templates/DatePickerBasicTsx"; 8 | import { DatePickerBasic } from "./DatePickerBasic.component"; 9 | 10 | import "./DatePickerBasic.css"; 11 | 12 | type Meta = ComponentMeta; 13 | type Story = ComponentStoryObj; 14 | 15 | export default { 16 | title: "DatePicker/Basic", 17 | component: DatePickerBasic, 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ js, ts, css }), 21 | }, 22 | } as Meta; 23 | 24 | export const Default: Story = { 25 | args: { label: "DatePicker" }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/datepicker/stories/DatePickerStyled.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import CalendarStyled from "../../calendar/stories/CalendarStyled.component"; 4 | import DateFieldStyled from "../../datefield/stories/DateFieldStyled.component"; 5 | import { 6 | DatePickerBaseStateProps, 7 | DatePickerDisclosure, 8 | DatePickerGroup, 9 | DatePickerLabel, 10 | DatePickerPopover, 11 | useDatePickerBaseState, 12 | useDatePickerState, 13 | } from "../../index"; 14 | 15 | import { CalendarStyledIcon } from "./Utils.component"; 16 | 17 | export type DatePickerStyledProps = DatePickerBaseStateProps & {}; 18 | 19 | export const DatePickerStyled: React.FC = props => { 20 | const state = useDatePickerBaseState({ ...props, gutter: 10 }); 21 | const datepicker = useDatePickerState({ ...props, state }); 22 | 23 | return ( 24 |
25 | 26 | {props.label} 27 | 28 | 32 | 33 | 37 | 38 | 39 | {state.popover.open && ( 40 | 41 | 42 | 43 | )} 44 | 45 |
46 | ); 47 | }; 48 | 49 | export default DatePickerStyled; 50 | -------------------------------------------------------------------------------- /src/datepicker/stories/DatePickerStyled.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import js from "./templates/DatePickerStyledJsx"; 7 | import ts from "./templates/DatePickerStyledTsx"; 8 | import { DatePickerStyled } from "./DatePickerStyled.component"; 9 | 10 | import "./tailwind.css"; 11 | 12 | type Meta = ComponentMeta; 13 | type Story = ComponentStoryObj; 14 | 15 | export default { 16 | title: "DatePicker/Styled", 17 | component: DatePickerStyled, 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ js, ts }), 21 | }, 22 | decorators: [ 23 | Story => { 24 | document.body.id = "tailwind"; 25 | return ; 26 | }, 27 | ], 28 | } as Meta; 29 | 30 | export const Default: Story = { 31 | args: { label: "DatePicker" }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/datepicker/stories/Utils.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const CalendarIcon = (props: React.SVGProps) => ( 4 | 14 | ); 15 | 16 | export const CalendarStyledIcon = (props: React.SVGProps) => ( 17 | 34 | ); 35 | 36 | export const ChevronLeft = (props: React.SVGProps) => { 37 | return ( 38 | 45 | 51 | 52 | ); 53 | }; 54 | 55 | export const ChevronRight = (props: React.SVGProps) => ( 56 | 57 | ); 58 | -------------------------------------------------------------------------------- /src/datepicker/stories/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .styled-datepicker .calendar__cell { 7 | height: 32px; 8 | width: 32px; 9 | max-height: 32px; 10 | max-width: 32px; 11 | @apply text-sm text-center rounded-lg; 12 | } 13 | .styled-datepicker .calendar__cell[data-is-range-selection] { 14 | @apply bg-blue-100 rounded-none text-gray-800 !important; 15 | } 16 | .styled-datepicker .calendar__cell[data-is-selection-start] { 17 | @apply bg-blue-500 rounded-l-lg text-white !important; 18 | } 19 | .styled-datepicker .calendar__cell[data-is-selection-end] { 20 | @apply bg-blue-500 rounded-r-lg text-white !important; 21 | } 22 | 23 | .styled-datepicker .calendar__cell[data-is-range-selection]:focus-within { 24 | @apply bg-blue-400 text-white !important; 25 | } 26 | .styled-datepicker .calendar__cell:focus-within { 27 | @apply bg-gray-100; 28 | } 29 | 30 | .styled-datepicker.calendar [data-weekend] { 31 | @apply text-red-600; 32 | } 33 | 34 | .styled-datepicker.calendar [aria-selected="true"] { 35 | @apply text-white bg-blue-500; 36 | } 37 | 38 | .styled-datepicker.calendar [aria-selected]:focus-within { 39 | @apply bg-gray-100; 40 | } 41 | 42 | .styled-datepicker.calendar [aria-selected="true"]:focus-within { 43 | @apply text-white bg-blue-400; 44 | } 45 | 46 | .styled-datepicker.calendar [aria-disabled="true"] { 47 | @apply text-gray-500; 48 | } 49 | 50 | .styled-datepicker.calendar span { 51 | outline: none; 52 | } 53 | 54 | .datepicker__dash:before { 55 | content: "-"; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/daterange-picker/daterangepicker-base-state.ts: -------------------------------------------------------------------------------- 1 | import { PopoverState, PopoverStateProps, usePopoverState } from "ariakit"; 2 | import { 3 | DateRangePickerState, 4 | DateRangePickerStateOptions as DateRangePickerStateProps, 5 | useDateRangePickerState, 6 | } from "@react-stately/datepicker"; 7 | 8 | export function useDateRangePickerBaseState( 9 | props: DateRangePickerBaseStateProps, 10 | ): DateRangePickerBaseState { 11 | const datepicker = useDateRangePickerState(props); 12 | const { isOpen, setOpen } = datepicker; 13 | 14 | const popover = usePopoverState({ open: isOpen, setOpen, ...props }); 15 | 16 | return { datepicker, popover }; 17 | } 18 | 19 | export type DateRangePickerBaseState = { 20 | /** 21 | * Object returned by the `useDatePickerState` hook. 22 | */ 23 | datepicker: DateRangePickerState; 24 | /** 25 | * Object returned by the `usePopoverState` hook. 26 | */ 27 | popover: PopoverState; 28 | }; 29 | 30 | export type DateRangePickerBaseStateProps = DateRangePickerStateProps & 31 | PopoverStateProps & {}; 32 | -------------------------------------------------------------------------------- /src/daterange-picker/daterangepicker-state.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useRef } from "react"; 2 | import { DateValue } from "@internationalized/date"; 3 | import { 4 | DateRangePickerAria, 5 | useDateRangePicker, 6 | } from "@react-aria/datepicker"; 7 | import { AriaDateRangePickerProps } from "@react-types/datepicker"; 8 | 9 | import { DateRangePickerBaseState } from "./daterangepicker-base-state"; 10 | 11 | export function useDateRangePickerState({ 12 | state, 13 | ...props 14 | }: DateRangePickerStateProps): DateRangePickerState { 15 | const ref = useRef(null); 16 | const datepicker = useDateRangePicker(props, state.datepicker, ref); 17 | 18 | return { ...datepicker, ref, baseState: state }; 19 | } 20 | 21 | export type DateRangePickerState = DateRangePickerAria & { 22 | /** 23 | * Reference for the date picker's visible label element, if any. 24 | */ 25 | ref: RefObject; 26 | /** 27 | * Object returned by the `useDateRangePickerBaseState` hook. 28 | */ 29 | baseState: DateRangePickerBaseState; 30 | }; 31 | 32 | export type DateRangePickerStateProps = 33 | AriaDateRangePickerProps & { 34 | /** 35 | * Object returned by the `useDateRangePickerBaseState` hook. 36 | */ 37 | state: DateRangePickerBaseState; 38 | }; 39 | -------------------------------------------------------------------------------- /src/daterange-picker/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./daterangepicker-base-state"; 2 | export * from "./daterangepicker-state"; 3 | -------------------------------------------------------------------------------- /src/daterange-picker/stories/DateRangePickerBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import DateFieldBasic from "../../datefield/stories/DateFieldBasic.component"; 4 | import { 5 | DatePickerDisclosure, 6 | DatePickerGroup, 7 | DatePickerLabel, 8 | DatePickerPopover, 9 | DateRangePickerBaseStateProps, 10 | useDateRangePickerBaseState, 11 | useDateRangePickerState, 12 | } from "../../index"; 13 | import CalendarRange from "../../range-calendar/stories/RangeCalendarBasic.component"; 14 | 15 | import { CalendarIcon } from "./Utils.component"; 16 | 17 | export type DateRangePickerBasicProps = DateRangePickerBaseStateProps & {}; 18 | 19 | export const DateRangePickerBasic: React.FC< 20 | DateRangePickerBasicProps 21 | > = props => { 22 | const state = useDateRangePickerBaseState({ ...props, gutter: 10 }); 23 | const daterangepicker = useDateRangePickerState({ ...props, state }); 24 | 25 | return ( 26 |
27 | 28 | {props.label} 29 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | {state.popover.open && ( 41 | 42 | 43 | 44 | )} 45 |
46 | ); 47 | }; 48 | 49 | export default DateRangePickerBasic; 50 | -------------------------------------------------------------------------------- /src/daterange-picker/stories/DateRangePickerBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/DateRangePickerBasicCss"; 7 | import js from "./templates/DateRangePickerBasicJsx"; 8 | import ts from "./templates/DateRangePickerBasicTsx"; 9 | import { DateRangePickerBasic } from "./DateRangePickerBasic.component"; 10 | 11 | import "./DateRangePickerBasic.css"; 12 | 13 | type Meta = ComponentMeta; 14 | type Story = ComponentStoryObj; 15 | 16 | export default { 17 | title: "DateRangePicker/Basic", 18 | component: DateRangePickerBasic, 19 | parameters: { 20 | layout: "centered", 21 | preview: createPreviewTabs({ js, ts, css }), 22 | }, 23 | decorators: [ 24 | Story => { 25 | document.body.id = "date-range-picker-basic"; 26 | return ; 27 | }, 28 | ], 29 | } as Meta; 30 | 31 | export const Default: Story = { 32 | args: { label: "DateRangePicker" }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/daterange-picker/stories/DateRangePickerStyled.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import js from "./templates/DateRangePickerStyledJsx"; 7 | import ts from "./templates/DateRangePickerStyledTsx"; 8 | import { DateRangePickerStyled } from "./DateRangePickerStyled.component"; 9 | 10 | import "./tailwind.css"; 11 | 12 | type Meta = ComponentMeta; 13 | type Story = ComponentStoryObj; 14 | 15 | export default { 16 | title: "DateRangePicker/Styled", 17 | component: DateRangePickerStyled, 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ js, ts }), 21 | }, 22 | decorators: [ 23 | Story => { 24 | document.body.id = "tailwind"; 25 | return ; 26 | }, 27 | ], 28 | } as Meta; 29 | 30 | export const Default: Story = { 31 | args: { label: "DateRangePicker" }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/daterange-picker/stories/Utils.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const CalendarIcon = (props: React.SVGProps) => ( 4 | 14 | ); 15 | 16 | export const CalendarStyledIcon = (props: React.SVGProps) => ( 17 | 34 | ); 35 | 36 | export const ChevronLeft = (props: React.SVGProps) => { 37 | return ( 38 | 45 | 51 | 52 | ); 53 | }; 54 | 55 | export const ChevronRight = (props: React.SVGProps) => ( 56 | 57 | ); 58 | -------------------------------------------------------------------------------- /src/daterange-picker/stories/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .styled-datepicker .calendar__cell { 7 | height: 32px; 8 | width: 32px; 9 | max-height: 32px; 10 | max-width: 32px; 11 | @apply text-sm text-center rounded-lg; 12 | } 13 | .styled-datepicker .calendar__cell[data-is-range-selection] { 14 | @apply bg-blue-100 rounded-none text-gray-800 !important; 15 | } 16 | .styled-datepicker .calendar__cell[data-is-selection-start] { 17 | @apply bg-blue-500 rounded-l-lg text-white !important; 18 | } 19 | .styled-datepicker .calendar__cell[data-is-selection-end] { 20 | @apply bg-blue-500 rounded-r-lg text-white !important; 21 | } 22 | 23 | .styled-datepicker .calendar__cell[data-is-range-selection]:focus-within { 24 | @apply bg-blue-400 text-white !important; 25 | } 26 | .styled-datepicker .calendar__cell:focus-within { 27 | @apply bg-gray-100; 28 | } 29 | 30 | .styled-datepicker.calendar [data-weekend] { 31 | @apply text-red-600; 32 | } 33 | 34 | .styled-datepicker.calendar [aria-selected="true"] { 35 | @apply text-white bg-blue-500; 36 | } 37 | 38 | .styled-datepicker.calendar [aria-selected]:focus-within { 39 | @apply bg-gray-100; 40 | } 41 | 42 | .styled-datepicker.calendar [aria-selected="true"]:focus-within { 43 | @apply text-white bg-blue-400; 44 | } 45 | 46 | .styled-datepicker.calendar [aria-disabled="true"] { 47 | @apply text-gray-500; 48 | } 49 | 50 | .styled-datepicker.calendar span { 51 | outline: none; 52 | } 53 | 54 | .datepicker__dash:before { 55 | content: "-"; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/disclosure/__utils.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/mui-org/material-ui/blob/da362266f7c137bf671d7e8c44c84ad5cfc0e9e2/packages/material-ui/src/styles/transitions.js#L89-L98 2 | export function getAutoSizeDuration(size: number | string): number { 3 | if (!size || typeof size === "string") { 4 | return 0; 5 | } 6 | 7 | const constant = size / 36; 8 | 9 | // https://www.wolframalpha.com/input/?i=(4+%2B+15+*+(x+%2F+36+)+**+0.25+%2B+(x+%2F+36)+%2F+5)+*+10 10 | return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10); 11 | } 12 | 13 | export function getElementHeight( 14 | el: React.RefObject | { current?: { scrollHeight: number } }, 15 | ): string | number { 16 | if (!el?.current) { 17 | return "auto"; 18 | } 19 | 20 | return el.current.scrollHeight; 21 | } 22 | 23 | export function getElementWidth( 24 | el: React.RefObject | { current?: { scrollWidth: number } }, 25 | ): string | number { 26 | if (!el?.current) { 27 | return "auto"; 28 | } 29 | 30 | return el.current.scrollWidth; 31 | } 32 | -------------------------------------------------------------------------------- /src/disclosure/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./disclosure-collapsible-content"; 2 | -------------------------------------------------------------------------------- /src/disclosure/stories/DisclosureHorizontalCollapseBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Disclosure, DisclosureStateProps, useDisclosureState } from "ariakit"; 3 | 4 | import { DisclosureCollapsibleContent } from "../../index"; 5 | 6 | export type DisclosureHorizontalCollapseBasicProps = DisclosureStateProps & {}; 7 | 8 | export const DisclosureHorizontalCollapseBasic: React.FC< 9 | DisclosureHorizontalCollapseBasicProps 10 | > = props => { 11 | const state = useDisclosureState(props); 12 | 13 | return ( 14 |
15 | Show More 16 | 24 | Item 1 25 | Item 2 26 | Item 3 27 | Item 4 28 | Item 5 29 | Item 6 30 | 31 |
32 | ); 33 | }; 34 | 35 | export default DisclosureHorizontalCollapseBasic; 36 | -------------------------------------------------------------------------------- /src/disclosure/stories/DisclosureHorizontalCollapseBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/DisclosureHorizontalCollapseBasicJsx"; 6 | import ts from "./templates/DisclosureHorizontalCollapseBasicTsx"; 7 | import { DisclosureHorizontalCollapseBasic } from "./DisclosureHorizontalCollapseBasic.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | title: "Disclosure/Horizontal", 14 | component: DisclosureHorizontalCollapseBasic, 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ js, ts }), 18 | }, 19 | } as Meta; 20 | 21 | export const Default: Story = { args: {} }; 22 | -------------------------------------------------------------------------------- /src/disclosure/stories/DisclosureVerticalCollapseBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Disclosure, DisclosureStateProps, useDisclosureState } from "ariakit"; 3 | 4 | import { DisclosureCollapsibleContent } from "../../index"; 5 | 6 | export type DisclosureVerticalCollapseBasicProps = DisclosureStateProps & {}; 7 | 8 | export const DisclosureVerticalCollapseBasic: React.FC< 9 | DisclosureVerticalCollapseBasicProps 10 | > = props => { 11 | const state = useDisclosureState(props); 12 | 13 | return ( 14 |
15 | Show More 16 | 24 | Item 1 25 | Item 2 26 | Item 3 27 | Item 4 28 | Item 5 29 | Item 6 30 | 31 |
32 | ); 33 | }; 34 | 35 | export default DisclosureVerticalCollapseBasic; 36 | -------------------------------------------------------------------------------- /src/disclosure/stories/DisclosureVerticalCollapseBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/DisclosureVerticalCollapseBasicJsx"; 6 | import ts from "./templates/DisclosureVerticalCollapseBasicTsx"; 7 | import { DisclosureVerticalCollapseBasic } from "./DisclosureVerticalCollapseBasic.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | title: "Disclosure/Vertical", 14 | component: DisclosureVerticalCollapseBasic, 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ js, ts }), 18 | }, 19 | } as Meta; 20 | 21 | export const Default: Story = { args: {} }; 22 | -------------------------------------------------------------------------------- /src/drawer/drawer.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { DialogOptions, useDialog } from "ariakit/dialog"; 8 | import { As, Props } from "ariakit-utils/types"; 9 | 10 | export const useDrawer = createHook( 11 | ({ placement = "left", state, ...props }) => { 12 | const style: CSSProperties = { 13 | ...PLACEMENTS[placement], 14 | position: "fixed", 15 | ...props.style, 16 | }; 17 | 18 | props = { ...props, style }; 19 | props = useDialog({ state, ...props }); 20 | 21 | return props; 22 | }, 23 | ); 24 | 25 | export const Drawer = createComponent(props => { 26 | const htmlProps = useDrawer(props); 27 | 28 | return createElement("div", htmlProps); 29 | }); 30 | 31 | export type DrawerOptions = DialogOptions & { 32 | /** 33 | * Direction to place the drawer. 34 | * 35 | * @default left 36 | */ 37 | placement?: Placement; 38 | }; 39 | 40 | export type DrawerProps = Props>; 41 | 42 | export type Placement = keyof typeof PLACEMENTS; 43 | 44 | export const PLACEMENTS = { 45 | left: { 46 | left: 0, 47 | top: 0, 48 | bottom: 0, 49 | height: "100vh", 50 | }, 51 | right: { 52 | right: 0, 53 | top: 0, 54 | bottom: 0, 55 | height: "100vh", 56 | }, 57 | top: { 58 | right: 0, 59 | left: 0, 60 | top: 0, 61 | width: "100vw", 62 | }, 63 | bottom: { 64 | right: 0, 65 | left: 0, 66 | bottom: 0, 67 | width: "100vw", 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /src/drawer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./drawer"; 2 | -------------------------------------------------------------------------------- /src/drawer/stories/DrawerBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | Button, 4 | DialogDismiss, 5 | DialogHeading, 6 | DisclosureStateProps, 7 | useDialogState, 8 | } from "ariakit"; 9 | 10 | import { Drawer } from "../../index"; 11 | 12 | export type DrawerBasicProps = DisclosureStateProps & {}; 13 | 14 | export const DrawerBasic: React.FC = props => { 15 | const dialog = useDialogState({ animated: true, ...props }); 16 | 17 | return ( 18 | <> 19 | 22 | 27 |
28 | Apples 29 | 30 |
31 |
    32 |
  • 33 | Calories: 95 34 |
  • 35 |
  • 36 | Carbs: 25 grams 37 |
  • 38 |
  • 39 | Fibers: 4 grams 40 |
  • 41 |
  • 42 | Vitamin C: 14% of the Reference Daily Intake (RDI) 43 |
  • 44 |
  • 45 | Potassium: 6% of the RDI 46 |
  • 47 |
  • 48 | Vitamin K: 5% of the RDI 49 |
  • 50 |
51 |
52 | 53 | ); 54 | }; 55 | 56 | export default DrawerBasic; 57 | -------------------------------------------------------------------------------- /src/drawer/stories/DrawerBasic.css: -------------------------------------------------------------------------------- 1 | .dialog { 2 | opacity: 0; 3 | padding: 10px; 4 | background-color: white; 5 | transition: 250ms ease-in-out; 6 | transform: translate(-200px, 0); 7 | } 8 | 9 | .dialog[data-enter] { 10 | opacity: 1; 11 | transform: translate(0, 0); 12 | } 13 | 14 | .dialog[data-leave] { 15 | opacity: 0; 16 | transform: translate(-200px, 0); 17 | } 18 | 19 | .backdrop { 20 | opacity: 0; 21 | position: fixed; 22 | top: 0; 23 | left: 0; 24 | bottom: 0; 25 | right: 0; 26 | transition: opacity 250ms ease-in-out; 27 | background-color: rgba(0, 0, 0, 0.2); 28 | } 29 | 30 | .backdrop[data-enter] { 31 | opacity: 1; 32 | } 33 | 34 | .header { 35 | display: flex; 36 | justify-content: space-between; 37 | } 38 | 39 | .heading { 40 | margin: 0px; 41 | } 42 | -------------------------------------------------------------------------------- /src/drawer/stories/DrawerBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/DrawerBasicJsx"; 6 | import ts from "./templates/DrawerBasicTsx"; 7 | import { DrawerBasic } from "./DrawerBasic.component"; 8 | 9 | import "./DrawerBasic.css"; 10 | 11 | type Meta = ComponentMeta; 12 | type Story = ComponentStoryObj; 13 | 14 | export default { 15 | title: "Drawer/Basic", 16 | component: DrawerBasic, 17 | parameters: { 18 | layout: "centered", 19 | preview: createPreviewTabs({ js, ts }), 20 | }, 21 | } as Meta; 22 | 23 | export const Default: Story = { args: {} }; 24 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./accordion"; 2 | export * from "./breadcrumbs"; 3 | export * from "./calendar"; 4 | export * from "./datefield"; 5 | export * from "./datepicker"; 6 | export * from "./daterange-picker"; 7 | export * from "./disclosure"; 8 | export * from "./drawer"; 9 | export * from "./link"; 10 | export * from "./meter"; 11 | export * from "./numberfield"; 12 | export * from "./pagination"; 13 | export * from "./progress"; 14 | export * from "./range-calendar"; 15 | export * from "./slider"; 16 | export * from "./timefield"; 17 | export * from "./toast"; 18 | -------------------------------------------------------------------------------- /src/link/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./link-base"; 2 | -------------------------------------------------------------------------------- /src/link/link-base.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { CommandOptions, useCommand } from "ariakit"; 8 | import { useForkRef, useTagName } from "ariakit-utils"; 9 | import { As, Props } from "ariakit-utils/types"; 10 | 11 | export const useLink = createHook( 12 | ({ isExternal = false, ...props }) => { 13 | const ref = React.useRef(null); 14 | const tagName = useTagName(ref, props.as || "a"); 15 | const [isNativeLink, setIsNativeLink] = React.useState( 16 | () => !!tagName && isLink({ tagName }), 17 | ); 18 | 19 | React.useEffect(() => { 20 | if (!ref.current) return; 21 | 22 | setIsNativeLink(isLink(ref.current)); 23 | }, []); 24 | 25 | props = { 26 | role: !isNativeLink && tagName !== "a" ? "link" : undefined, 27 | ...(isExternal && { target: "_blank", rel: "noopener noreferrer" }), 28 | ...props, 29 | ref: useForkRef(ref, props.ref), 30 | }; 31 | 32 | props = useCommand({ clickOnSpace: false, ...props }); 33 | 34 | return props; 35 | }, 36 | ); 37 | 38 | export const Link = createComponent(props => { 39 | const htmlProps = useLink(props); 40 | 41 | return createElement("a", htmlProps); 42 | }); 43 | 44 | export type LinkOptions = CommandOptions & { 45 | /** 46 | * Opens the link in a new tab 47 | * @default false 48 | */ 49 | isExternal?: boolean; 50 | }; 51 | 52 | export type LinkProps = Props>; 53 | 54 | export function isLink(element: { tagName: string }) { 55 | const tagName = element.tagName.toLowerCase(); 56 | if (tagName === "a") return true; 57 | 58 | return false; 59 | } 60 | -------------------------------------------------------------------------------- /src/link/stories/LinkBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Link, LinkProps } from "../../index"; 4 | 5 | export type LinkBasicProps = LinkProps & {}; 6 | 7 | export const LinkBasic: React.FC = props => { 8 | return ( 9 | 10 | Timeless 11 | 12 | ); 13 | }; 14 | 15 | export default LinkBasic; 16 | -------------------------------------------------------------------------------- /src/link/stories/LinkBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/LinkBasicJsx"; 6 | import ts from "./templates/LinkBasicTsx"; 7 | import { LinkBasic } from "./LinkBasic.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | title: "Link/Basic", 14 | component: LinkBasic, 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ js, ts }), 18 | }, 19 | } as Meta; 20 | 21 | export const Default: Story = { args: {} }; 22 | 23 | export const DisabledLink: Story = { 24 | args: { disabled: true }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/link/stories/LinkSpan.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Link, LinkProps } from "../../index"; 4 | 5 | export type LinkSpanProps = LinkProps & {}; 6 | 7 | export const LinkSpan: React.FC = props => { 8 | return ( 9 | 12 | goToLink(event, "https://timeless.co/") 13 | } 14 | onKeyDown={(event: React.KeyboardEvent) => 15 | goToLink(event, "https://timeless.co/") 16 | } 17 | {...props} 18 | > 19 | Timeless 20 | 21 | ); 22 | }; 23 | 24 | export default LinkSpan; 25 | 26 | function goToLink(event: React.MouseEvent | React.KeyboardEvent, url: string) { 27 | var type = event.type; 28 | 29 | // @ts-ignore 30 | if (type === "click" || (type === "keydown" && event.key === "Enter")) { 31 | window.location.href = url; 32 | 33 | event.preventDefault(); 34 | event.stopPropagation(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/link/stories/LinkSpan.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/LinkSpanJsx"; 6 | import ts from "./templates/LinkSpanTsx"; 7 | import { LinkSpan } from "./LinkSpan.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | title: "Link/Span", 14 | component: LinkSpan, 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ js, ts }), 18 | }, 19 | } as Meta; 20 | 21 | export const Default: Story = { args: {} }; 22 | 23 | export const DisabledLink: Story = { args: { disabled: true } }; 24 | -------------------------------------------------------------------------------- /src/meter/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./__utils"; 2 | export * from "./meter-base"; 3 | export * from "./meter-state"; 4 | -------------------------------------------------------------------------------- /src/meter/meter-base.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | 8 | import { MeterState } from "./meter-state"; 9 | 10 | export const useMeter = createHook(({ state, ...props }) => { 11 | const { value, max, min, percent } = state; 12 | 13 | props = { 14 | role: "meter progressbar", 15 | "aria-valuemax": max, 16 | "aria-valuemin": min, 17 | "aria-valuenow": value, 18 | "aria-valuetext": !percent.toString() ? undefined : `${percent}%`, 19 | ...props, 20 | }; 21 | 22 | return props; 23 | }); 24 | 25 | export const Meter = createComponent(props => { 26 | const htmlProps = useMeter(props); 27 | 28 | return createElement("div", htmlProps); 29 | }); 30 | 31 | export type MeterOptions = Options & { 32 | /** 33 | * Object returned by the `useMeterState` hook. 34 | */ 35 | state: MeterState; 36 | }; 37 | 38 | export type MeterProps = Props>; 39 | -------------------------------------------------------------------------------- /src/meter/stories/MeterBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Meter, MeterStateProps, useMeterState } from "../../index"; 4 | 5 | export const MeterBasic: React.FC = props => { 6 | const state = useMeterState({ 7 | value: 5, 8 | min: 0, 9 | max: 10, 10 | low: 0, 11 | high: 10, 12 | optimum: 5, 13 | ...props, 14 | }); 15 | const { percent, status } = state; 16 | 17 | return ( 18 |
19 | 28 |
29 | ); 30 | }; 31 | 32 | export default MeterBasic; 33 | 34 | const background = { 35 | safe: "#8bcf69", 36 | caution: "#e6d450", 37 | danger: "#f28f68", 38 | }; 39 | -------------------------------------------------------------------------------- /src/meter/stories/MeterBasic.css: -------------------------------------------------------------------------------- 1 | /* CSS Styles from https://css-tricks.com/html5-meter-element/ */ 2 | 3 | .meter { 4 | position: relative; 5 | width: 500px; 6 | height: 1rem; 7 | background: whiteSmoke; 8 | border-radius: 3px; 9 | border: 1px solid #ccc; 10 | box-shadow: 0 5px 5px -5px #333 inset; 11 | overflow: hidden; 12 | } 13 | 14 | .meterbar { 15 | height: 100%; 16 | } 17 | -------------------------------------------------------------------------------- /src/meter/stories/MeterStyled.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/MeterStyledJsx"; 6 | import ts from "./templates/MeterStyledTsx"; 7 | import { MeterStyled } from "./MeterStyled.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | component: MeterStyled, 14 | title: "Meter/Styled", 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ js, ts, deps: ["@emotion/css@latest"] }), 18 | }, 19 | } as Meta; 20 | 21 | const Default: Story = { 22 | args: { value: 5, min: 0, max: 10, low: 0, high: 10, optimum: 5 }, 23 | }; 24 | 25 | export const WithLabel = { args: { ...Default.args, withLabel: true } }; 26 | 27 | export const WithStripe = { args: { ...Default.args, withStripe: true } }; 28 | 29 | export const WithStripeAnimation = { 30 | args: { ...Default.args, withStripe: true, withStripeAnimation: true }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/numberfield/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./numberfield-base-state"; 2 | export * from "./numberfield-decrement-button"; 3 | export * from "./numberfield-group"; 4 | export * from "./numberfield-increment-button"; 5 | export * from "./numberfield-input"; 6 | export * from "./numberfield-label"; 7 | export * from "./numberfield-state"; 8 | -------------------------------------------------------------------------------- /src/numberfield/numberfield-base-state.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NumberFieldState, 3 | NumberFieldStateProps, 4 | useNumberFieldState, 5 | } from "@react-stately/numberfield"; 6 | 7 | export function useNumberFieldBaseState( 8 | props: NumberFieldBaseStateProps, 9 | ): NumberFieldBaseState { 10 | const state = useNumberFieldState(props); 11 | 12 | return state; 13 | } 14 | 15 | export type NumberFieldBaseState = NumberFieldState & {}; 16 | 17 | export type NumberFieldBaseStateProps = NumberFieldStateProps & {}; 18 | -------------------------------------------------------------------------------- /src/numberfield/numberfield-decrement-button.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { useForkRef } from "ariakit-utils"; 8 | import { As, Options, Props } from "ariakit-utils/types"; 9 | import { useButton } from "@react-aria/button"; 10 | import { mergeProps } from "@react-aria/utils"; 11 | 12 | import { NumberFieldState } from "./numberfield-state"; 13 | 14 | export const useNumberFieldDecrementButton = 15 | createHook(({ state, ...props }) => { 16 | const ref = useRef(null); 17 | const { buttonProps } = useButton(state.decrementButtonProps, ref); 18 | 19 | props = { ...props, ref: useForkRef(ref, props.ref) }; 20 | props = mergeProps(buttonProps, props); 21 | 22 | return props; 23 | }); 24 | 25 | export const NumberFieldDecrementButton = 26 | createComponent(props => { 27 | const htmlProps = useNumberFieldDecrementButton(props); 28 | 29 | return createElement("button", htmlProps); 30 | }); 31 | 32 | export type NumberFieldDecrementButtonOptions = 33 | Options & { 34 | /** 35 | * Object returned by the `useNumberFieldState` hook. 36 | */ 37 | state: NumberFieldState; 38 | }; 39 | 40 | export type NumberFieldDecrementButtonProps = Props< 41 | NumberFieldDecrementButtonOptions 42 | >; 43 | -------------------------------------------------------------------------------- /src/numberfield/numberfield-group.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { NumberFieldState } from "./numberfield-state"; 10 | 11 | export const useNumberFieldGroup = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.groupProps, props); 14 | 15 | return props; 16 | }, 17 | ); 18 | 19 | export const NumberFieldGroup = createComponent( 20 | props => { 21 | const htmlProps = useNumberFieldGroup(props); 22 | 23 | return createElement("div", htmlProps); 24 | }, 25 | ); 26 | 27 | export type NumberFieldGroupOptions = Options & { 28 | /** 29 | * Object returned by the `useNumberFieldState` hook. 30 | */ 31 | state: NumberFieldState; 32 | }; 33 | 34 | export type NumberFieldGroupProps = Props< 35 | NumberFieldGroupOptions 36 | >; 37 | -------------------------------------------------------------------------------- /src/numberfield/numberfield-increment-button.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { useForkRef } from "ariakit-utils"; 8 | import { As, Options, Props } from "ariakit-utils/types"; 9 | import { useButton } from "@react-aria/button"; 10 | import { mergeProps } from "@react-aria/utils"; 11 | 12 | import { NumberFieldState } from "./numberfield-state"; 13 | 14 | export const useNumberFieldIncrementButton = 15 | createHook(({ state, ...props }) => { 16 | const ref = useRef(null); 17 | const { buttonProps } = useButton(state.incrementButtonProps, ref); 18 | 19 | props = { ...props, ref: useForkRef(ref, props.ref) }; 20 | props = mergeProps(buttonProps, props); 21 | 22 | return props; 23 | }); 24 | 25 | export const NumberFieldIncrementButton = 26 | createComponent(props => { 27 | const htmlProps = useNumberFieldIncrementButton(props); 28 | 29 | return createElement("button", htmlProps); 30 | }); 31 | 32 | export type NumberFieldIncrementButtonOptions = 33 | Options & { 34 | /** 35 | * Object returned by the `useNumberFieldState` hook. 36 | */ 37 | state: NumberFieldState; 38 | }; 39 | 40 | export type NumberFieldIncrementButtonProps = Props< 41 | NumberFieldIncrementButtonOptions 42 | >; 43 | -------------------------------------------------------------------------------- /src/numberfield/numberfield-input.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useForkRef } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | import { mergeProps } from "@react-aria/utils"; 9 | 10 | import { NumberFieldState } from "./numberfield-state"; 11 | 12 | export const useNumberFieldInput = createHook( 13 | ({ state, ...props }) => { 14 | props = { ...props, ref: useForkRef(state.inputRef, props.ref) }; 15 | props = mergeProps(state.inputProps, props); 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const NumberFieldInput = createComponent( 22 | props => { 23 | const htmlProps = useNumberFieldInput(props); 24 | 25 | return createElement("input", htmlProps); 26 | }, 27 | ); 28 | 29 | export type NumberFieldInputOptions = Options & { 30 | /** 31 | * Object returned by the `useNumberFieldState` hook. 32 | */ 33 | state: NumberFieldState; 34 | }; 35 | 36 | export type NumberFieldInputProps = Props< 37 | NumberFieldInputOptions 38 | >; 39 | -------------------------------------------------------------------------------- /src/numberfield/numberfield-label.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { NumberFieldState } from "./numberfield-state"; 10 | 11 | export const useNumberFieldLabel = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.labelProps, props); 14 | 15 | return props; 16 | }, 17 | ); 18 | 19 | export const NumberFieldLabel = createComponent( 20 | props => { 21 | const htmlProps = useNumberFieldLabel(props); 22 | 23 | return createElement("label", htmlProps); 24 | }, 25 | ); 26 | 27 | export type NumberFieldLabelOptions = Options & { 28 | /** 29 | * Object returned by the `useNumberFieldState` hook. 30 | */ 31 | state: NumberFieldState; 32 | }; 33 | 34 | export type NumberFieldLabelProps = Props< 35 | NumberFieldLabelOptions 36 | >; 37 | -------------------------------------------------------------------------------- /src/numberfield/numberfield-state.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useRef } from "react"; 2 | import { NumberFieldAria, useNumberField } from "@react-aria/numberfield"; 3 | import { AriaNumberFieldProps } from "@react-types/numberfield"; 4 | 5 | import { NumberFieldBaseState } from "./numberfield-base-state"; 6 | 7 | export function useNumberFieldState( 8 | props: NumberFieldStateProps, 9 | ): NumberFieldState { 10 | const { state: baseState, ...rest } = props; 11 | const inputRef = useRef(null); 12 | const state = useNumberField(rest, baseState, inputRef); 13 | 14 | return { ...state, baseState, inputRef }; 15 | } 16 | 17 | export type NumberFieldState = NumberFieldAria & { 18 | /** 19 | * Reference for the input element in number field element, if any. 20 | */ 21 | inputRef: RefObject; 22 | /** 23 | * Object returned by the `useNumberFieldBaseState` hook. 24 | */ 25 | baseState: NumberFieldBaseState; 26 | }; 27 | 28 | export type NumberFieldStateProps = AriaNumberFieldProps & { 29 | /** 30 | * Object returned by the `useNumberFieldBaseState` hook. 31 | */ 32 | state: NumberFieldBaseState; 33 | }; 34 | -------------------------------------------------------------------------------- /src/numberfield/stories/NumberFieldBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useLocale } from "@react-aria/i18n"; 3 | 4 | import { 5 | NumberFieldBaseStateProps, 6 | NumberFieldDecrementButton, 7 | NumberFieldGroup, 8 | NumberFieldIncrementButton, 9 | NumberFieldInput, 10 | NumberFieldLabel, 11 | useNumberFieldBaseState, 12 | useNumberFieldState, 13 | } from "../../index"; 14 | 15 | export type NumberFieldBasicProps = NumberFieldBaseStateProps & {}; 16 | 17 | export const NumberFieldBasic: React.FC = props => { 18 | let { locale } = useLocale(); 19 | const baseState = useNumberFieldBaseState({ ...props, locale }); 20 | const state = useNumberFieldState({ ...props, state: baseState }); 21 | 22 | return ( 23 |
24 | NumberField 25 | 26 | - 27 | 28 | + 29 | 30 |
31 | ); 32 | }; 33 | 34 | export default NumberFieldBasic; 35 | -------------------------------------------------------------------------------- /src/numberfield/stories/NumberFieldBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/NumberFieldBasicJsx"; 6 | import ts from "./templates/NumberFieldBasicTsx"; 7 | import { NumberFieldBasic } from "./NumberFieldBasic.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | title: "NumberField/Basic", 14 | component: NumberFieldBasic, 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ js, ts }), 18 | }, 19 | } as Meta; 20 | 21 | export const Default: Story = { args: { label: "NumberField" } }; 22 | -------------------------------------------------------------------------------- /src/pagination/__utils.ts: -------------------------------------------------------------------------------- 1 | import { createStoreContext } from "ariakit-utils/store"; 2 | 3 | import { PaginationState } from "./pagination-state"; 4 | 5 | export const PaginationContextState = createStoreContext(); 6 | -------------------------------------------------------------------------------- /src/pagination/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./pagination-base"; 2 | export * from "./pagination-button"; 3 | export * from "./pagination-state"; 4 | -------------------------------------------------------------------------------- /src/pagination/pagination-base.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useStoreProvider } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | 9 | import { PaginationContextState } from "./__utils"; 10 | import { PaginationState } from "./pagination-state"; 11 | 12 | export const usePagination = createHook( 13 | ({ state, ...props }) => { 14 | props = useStoreProvider({ state, ...props }, PaginationContextState); 15 | props = { "aria-label": "pagination", ...props }; 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const Pagination = createComponent(props => { 22 | const htmlProps = usePagination(props); 23 | 24 | return createElement("div", htmlProps); 25 | }); 26 | 27 | export type PaginationOptions = Options & { 28 | /** 29 | * Object returned by the `usePaginationState` hook. 30 | */ 31 | state: PaginationState; 32 | }; 33 | 34 | export type PaginationProps = Props>; 35 | -------------------------------------------------------------------------------- /src/pagination/stories/PaginationBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { 4 | Goto, 5 | Pagination, 6 | PaginationButton, 7 | PaginationStateProps, 8 | usePaginationState, 9 | } from "../../index"; 10 | 11 | export const PaginationBasic: React.FC = props => { 12 | const state = usePaginationState({ count: 10, ...props }); 13 | 14 | return ( 15 | 16 |
    17 |
  • 18 | First 19 |
  • 20 |
  • 21 | Previous 22 |
  • 23 | {state.pages.map(page => { 24 | if (page === "start-ellipsis" || page === "end-ellipsis") { 25 | return
  • ...
  • ; 26 | } 27 | 28 | return ( 29 |
  • 30 | 36 | {page} 37 | 38 |
  • 39 | ); 40 | })} 41 |
  • 42 | Next 43 |
  • 44 |
  • 45 | Last 46 |
  • 47 |
48 |
49 | ); 50 | }; 51 | 52 | export default PaginationBasic; 53 | -------------------------------------------------------------------------------- /src/pagination/stories/PaginationBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/PaginationBasicJsx"; 6 | import ts from "./templates/PaginationBasicTsx"; 7 | import { PaginationBasic } from "./PaginationBasic.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | title: "Pagination/Basic", 14 | component: PaginationBasic, 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ js, ts }), 18 | }, 19 | } as Meta; 20 | 21 | export const Default: Story = { args: {} }; 22 | 23 | export const DefaultPage: Story = { 24 | args: { 25 | defaultPage: 5, 26 | count: 10, 27 | }, 28 | }; 29 | 30 | export const BoundaryCount: Story = { 31 | args: { 32 | defaultPage: 25, 33 | count: 50, 34 | boundaryCount: 5, 35 | }, 36 | }; 37 | 38 | export const SiblingCount: Story = { 39 | args: { 40 | defaultPage: 25, 41 | count: 50, 42 | siblingCount: 5, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /src/progress/__utils.ts: -------------------------------------------------------------------------------- 1 | export function clamp(value: number | null, min: number, max: number) { 2 | if (value == null) return null; 3 | 4 | return Math.min(Math.max(value, min), max); 5 | } 6 | 7 | /** 8 | * Convert a value to percentage based on lower and upper bound values 9 | * 10 | * @param value the value in number 11 | * @param min the minimum value 12 | * @param max the maximum value 13 | */ 14 | export function valueToPercent(value: number, min: number, max: number) { 15 | return ((value - min) * 100) / (max - min); 16 | } 17 | -------------------------------------------------------------------------------- /src/progress/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./progress-base"; 2 | export * from "./progress-state"; 3 | -------------------------------------------------------------------------------- /src/progress/progress-base.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | 8 | import { ProgressState } from "./progress-state"; 9 | 10 | export const useProgress = createHook( 11 | ({ state, ...props }) => { 12 | const { value, max, min, isIndeterminate } = state; 13 | 14 | props = { 15 | role: "progressbar", 16 | "data-indeterminate": isIndeterminate, 17 | "aria-valuemax": max, 18 | "aria-valuemin": min, 19 | "aria-valuenow": value == null ? undefined : value, 20 | "aria-valuetext": !value?.toString() ? "intermediate" : value.toString(), 21 | ...props, 22 | }; 23 | 24 | return props; 25 | }, 26 | ); 27 | 28 | export const Progress = createComponent(props => { 29 | const htmlProps = useProgress(props); 30 | 31 | return createElement("div", htmlProps); 32 | }); 33 | 34 | export type ProgressOptions = Options & { 35 | /** 36 | * Object returned by the `useProgressState` hook. 37 | */ 38 | state: ProgressState; 39 | }; 40 | 41 | export type ProgressProps = Props>; 42 | -------------------------------------------------------------------------------- /src/progress/progress-state.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | 3 | import { clamp, valueToPercent } from "./__utils"; 4 | 5 | /** 6 | * Provides state for the `Progress` components. 7 | * @example 8 | * ```jsx 9 | * const progress = useProgressState(); 10 | * 11 | * ``` 12 | */ 13 | export function useProgressState({ 14 | value: defaultValue = 0, 15 | min = 0, 16 | max = 100, 17 | ...props 18 | }: ProgressStateProps = {}): ProgressState { 19 | const value = clamp(defaultValue, min, max); 20 | const isIndeterminate = value == null; 21 | const percent = isIndeterminate ? null : valueToPercent(value, min, max); 22 | 23 | const state = useMemo( 24 | () => ({ 25 | value, 26 | min, 27 | max, 28 | isIndeterminate, 29 | percent, 30 | }), 31 | [value, min, max, isIndeterminate, percent], 32 | ); 33 | 34 | return state; 35 | } 36 | 37 | export type ProgressState = { 38 | /** 39 | * The `value` of the progress indicator. 40 | * 41 | * If `null` the progress bar will be in `indeterminate` state 42 | * @default 0 43 | */ 44 | value: number | null; 45 | /** 46 | * The minimum value of the progress 47 | * @default 0 48 | */ 49 | min: number; 50 | /** 51 | * The maximum value of the 52 | * @default 100 53 | */ 54 | max: number; 55 | /** 56 | * `true` if `value` is `null` 57 | */ 58 | isIndeterminate: boolean; 59 | /** 60 | * Percentage of the value progressed with respect to min & max 61 | */ 62 | percent: number | null; 63 | }; 64 | 65 | export type ProgressStateProps = Pick< 66 | Partial, 67 | "value" | "min" | "max" 68 | >; 69 | -------------------------------------------------------------------------------- /src/progress/stories/CircularProgress.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/CircularProgressJsx"; 6 | import ts from "./templates/CircularProgressTsx"; 7 | import { CircularProgress } from "./CircularProgress.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | component: CircularProgress, 14 | title: "Progress/Circular", 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ 18 | js, 19 | ts, 20 | deps: ["@emotion/css@latest"], 21 | }), 22 | }, 23 | } as Meta; 24 | 25 | export const Default: Story = { args: {} }; 26 | 27 | export const IsIndeterminate: Story = { args: { value: null } }; 28 | 29 | export const WithLabel: Story = { args: { ...Default.args, withLabel: true } }; 30 | -------------------------------------------------------------------------------- /src/progress/stories/LinearProgress.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/LinearProgressJsx"; 6 | import ts from "./templates/LinearProgressTsx"; 7 | import { LinearProgress } from "./LinearProgress.component"; 8 | 9 | type Meta = ComponentMeta; 10 | type Story = ComponentStoryObj; 11 | 12 | export default { 13 | title: "Progress/Linear", 14 | component: LinearProgress, 15 | parameters: { 16 | layout: "centered", 17 | preview: createPreviewTabs({ 18 | js, 19 | ts, 20 | deps: ["@emotion/css@latest"], 21 | }), 22 | }, 23 | } as Meta; 24 | 25 | export const Default: Story = { args: {} }; 26 | 27 | export const IsIndeterminate: Story = { args: { value: null } }; 28 | 29 | export const WithLabel = { args: { ...Default.args, withLabel: true } }; 30 | 31 | export const WithStripe = { args: { ...Default.args, withStripe: true } }; 32 | 33 | export const WithStripeAnimation = { 34 | args: { ...Default.args, withStripe: true, withStripeAnimation: true }, 35 | }; 36 | -------------------------------------------------------------------------------- /src/progress/stories/ProgressBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Progress, ProgressStateProps, useProgressState } from "../../index"; 4 | 5 | export const ProgressBasic: React.FC = props => { 6 | const state = useProgressState({ value: 50, ...props }); 7 | const { percent, isIndeterminate } = state; 8 | 9 | return ( 10 |
11 | 17 |
18 | ); 19 | }; 20 | 21 | export default ProgressBasic; 22 | -------------------------------------------------------------------------------- /src/progress/stories/ProgressBasic.css: -------------------------------------------------------------------------------- 1 | .progress { 2 | background: rgb(237, 242, 247); 3 | height: 0.5rem; 4 | width: 400px; 5 | overflow: hidden; 6 | position: relative; 7 | } 8 | 9 | .progressbar { 10 | background-color: #3182ce; 11 | height: 100%; 12 | transition: all 0.3s; 13 | } 14 | 15 | .progressbar.indeterminate { 16 | position: absolute; 17 | will-change: left; 18 | min-width: 50%; 19 | width: 100%; 20 | height: 100%; 21 | background-image: linear-gradient( 22 | to right, 23 | transparent 0%, 24 | #3182ce 50%, 25 | transparent 100% 26 | ); 27 | animation: progressAnim 1s ease infinite normal none running; 28 | } 29 | 30 | @keyframes progressAnim { 31 | from { 32 | left: -40%; 33 | } 34 | 35 | to { 36 | left: 100%; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/progress/stories/ProgressBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import css from "./templates/ProgressBasicCss"; 6 | import js from "./templates/ProgressBasicJsx"; 7 | import ts from "./templates/ProgressBasicTsx"; 8 | import { ProgressBasic } from "./ProgressBasic.component"; 9 | 10 | import "./ProgressBasic.css"; 11 | 12 | type Meta = ComponentMeta; 13 | type Story = ComponentStoryObj; 14 | 15 | export default { 16 | title: "Progress/Basic", 17 | component: ProgressBasic, 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ js, ts, css }), 21 | }, 22 | } as Meta; 23 | 24 | export const Default: Story = { args: {} }; 25 | 26 | export const IsIndeterminate = { args: { value: null } }; 27 | -------------------------------------------------------------------------------- /src/range-calendar/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./range-calendar"; 2 | export * from "./range-calendar-base-state"; 3 | export * from "./range-calendar-state"; 4 | -------------------------------------------------------------------------------- /src/range-calendar/range-calendar-base-state.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RangeCalendarState, 3 | RangeCalendarStateOptions as RangeCalendarStateProps, 4 | useRangeCalendarState, 5 | } from "@react-stately/calendar"; 6 | 7 | export function useRangeCalendarBaseState( 8 | props: RangeCalendarBaseStateProps, 9 | ): RangeCalendarBaseState { 10 | const state = useRangeCalendarState(props); 11 | 12 | return state; 13 | } 14 | 15 | export type RangeCalendarBaseState = RangeCalendarState & {}; 16 | 17 | export type RangeCalendarBaseStateProps = RangeCalendarStateProps & {}; 18 | -------------------------------------------------------------------------------- /src/range-calendar/range-calendar-state.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useRef } from "react"; 2 | import { CalendarAria, useRangeCalendar } from "@react-aria/calendar"; 3 | import { DateValue, RangeCalendarProps } from "@react-types/calendar"; 4 | 5 | import { RangeCalendarBaseState } from "./range-calendar-base-state"; 6 | 7 | export function useRangeCalendarState({ 8 | state, 9 | ...props 10 | }: RangeCalendarStateProps): RangeCalendarState { 11 | const ref = useRef(null); 12 | const calendarProps = useRangeCalendar(props, state, ref); 13 | 14 | return { ...calendarProps, ref }; 15 | } 16 | 17 | export type RangeCalendarState = CalendarAria & { 18 | /** 19 | * Reference for the calendar wrapper element within the cell inside the table 20 | */ 21 | ref: RefObject; 22 | }; 23 | 24 | export type RangeCalendarStateProps = 25 | RangeCalendarProps & { 26 | /** 27 | * Object returned by the `useRangeCalendarBaseState` hook. 28 | */ 29 | state: RangeCalendarBaseState; 30 | }; 31 | -------------------------------------------------------------------------------- /src/range-calendar/range-calendar.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useForkRef } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | import { mergeProps } from "@react-aria/utils"; 9 | 10 | import { RangeCalendarState } from "./range-calendar-state"; 11 | 12 | export const useRangeCalendar = createHook( 13 | ({ state, ...props }) => { 14 | props = { ...props, ref: useForkRef(state.ref, props.ref) }; 15 | props = mergeProps(state.calendarProps, props); 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const RangeCalendar = createComponent(props => { 22 | const htmlProps = useRangeCalendar(props); 23 | 24 | return createElement("div", htmlProps); 25 | }); 26 | 27 | export type RangeCalendarOptions = Options & { 28 | /** 29 | * Object returned by the `useRangeCalendarState` hook. 30 | */ 31 | state: RangeCalendarState; 32 | }; 33 | 34 | export type RangeCalendarProps = Props< 35 | RangeCalendarOptions 36 | >; 37 | -------------------------------------------------------------------------------- /src/range-calendar/stories/RangeCalendarBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ComponentMeta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/RangeCalendarBasicCss"; 7 | import js from "./templates/RangeCalendarBasicJsx"; 8 | import ts from "./templates/RangeCalendarBasicTsx"; 9 | import jsUtils from "./templates/UtilsJsx"; 10 | import tsUtils from "./templates/UtilsTsx"; 11 | import { RangeCalendarBasic } from "./RangeCalendarBasic.component"; 12 | 13 | import "./RangeCalendarBasic.css"; 14 | 15 | type Meta = ComponentMeta; 16 | // type Story = ComponentStoryObj; 17 | 18 | export default { 19 | title: "RangeCalendar/Basic", 20 | component: RangeCalendarBasic, 21 | parameters: { 22 | layout: "centered", 23 | preview: createPreviewTabs({ js, ts, css, jsUtils, tsUtils }), 24 | }, 25 | } as Meta; 26 | 27 | export const Default = () => { 28 | return ; 29 | }; 30 | -------------------------------------------------------------------------------- /src/range-calendar/stories/RangeCalendarStyled.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ComponentMeta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import js from "./templates/RangeCalendarStyledJsx"; 7 | import ts from "./templates/RangeCalendarStyledTsx"; 8 | import jsUtils from "./templates/UtilsJsx"; 9 | import tsUtils from "./templates/UtilsTsx"; 10 | import { RangeCalendarStyled } from "./RangeCalendarStyled.component"; 11 | 12 | import "./tailwind.css"; 13 | 14 | type Meta = ComponentMeta; 15 | 16 | export default { 17 | title: "RangeCalendar/Styled", 18 | component: RangeCalendarStyled, 19 | parameters: { 20 | layout: "centered", 21 | preview: createPreviewTabs({ js, ts, jsUtils, tsUtils }), 22 | }, 23 | decorators: [ 24 | Story => { 25 | document.body.id = "tailwind"; 26 | return ; 27 | }, 28 | ], 29 | } as Meta; 30 | 31 | export const Default = () => { 32 | return ; 33 | }; 34 | -------------------------------------------------------------------------------- /src/range-calendar/stories/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .styled-datepicker .calendar__cell { 7 | height: 32px; 8 | width: 32px; 9 | max-height: 32px; 10 | max-width: 32px; 11 | @apply text-sm text-center rounded-lg; 12 | } 13 | .styled-datepicker .calendar__cell[data-is-range-selection] { 14 | @apply bg-blue-100 rounded-none text-gray-800 !important; 15 | } 16 | .styled-datepicker .calendar__cell[data-is-selection-start] { 17 | @apply bg-blue-500 rounded-l-lg text-white !important; 18 | } 19 | .styled-datepicker .calendar__cell[data-is-selection-end] { 20 | @apply bg-blue-500 rounded-r-lg text-white !important; 21 | } 22 | 23 | .styled-datepicker .calendar__cell[data-is-range-selection]:focus-within { 24 | @apply bg-blue-400 text-white !important; 25 | } 26 | .styled-datepicker .calendar__cell:focus-within { 27 | @apply bg-gray-100; 28 | } 29 | 30 | .styled-datepicker.calendar [data-weekend] { 31 | @apply text-red-600; 32 | } 33 | 34 | .styled-datepicker.calendar [aria-selected="true"] { 35 | @apply text-white bg-blue-500; 36 | } 37 | 38 | .styled-datepicker.calendar [aria-selected]:focus-within { 39 | @apply bg-gray-100; 40 | } 41 | 42 | .styled-datepicker.calendar [aria-selected="true"]:focus-within { 43 | @apply text-white bg-blue-400; 44 | } 45 | 46 | .styled-datepicker.calendar [aria-disabled="true"] { 47 | @apply text-gray-500; 48 | } 49 | 50 | .styled-datepicker.calendar span { 51 | outline: none; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/slider/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./slider-base"; 2 | export * from "./slider-base-state"; 3 | export * from "./slider-input"; 4 | export * from "./slider-label"; 5 | export * from "./slider-output"; 6 | export * from "./slider-state"; 7 | export * from "./slider-thumb"; 8 | export * from "./slider-thumb-state"; 9 | export * from "./slider-track"; 10 | -------------------------------------------------------------------------------- /src/slider/slider-base-state.ts: -------------------------------------------------------------------------------- 1 | import { useNumberFormatter } from "@react-aria/i18n"; 2 | import { SliderState, useSliderState } from "@react-stately/slider"; 3 | import { SliderProps as SliderStateProps } from "@react-types/slider"; 4 | 5 | export function useSliderBaseState( 6 | props: SliderBaseStateProps = {}, 7 | ): SliderBaseState { 8 | const numberFormatter = useNumberFormatter(props.formatOptions); 9 | const state = useSliderState({ ...props, numberFormatter }); 10 | 11 | return state; 12 | } 13 | 14 | export type SliderBaseState = SliderState & {}; 15 | 16 | export type SliderBaseStateProps = SliderStateProps & { 17 | /** 18 | * The display format of the value label. 19 | */ 20 | formatOptions?: Intl.NumberFormatOptions; 21 | }; 22 | -------------------------------------------------------------------------------- /src/slider/slider-base.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { SliderState } from "./slider-state"; 10 | 11 | export const useSlider = createHook(({ state, ...props }) => { 12 | props = mergeProps(state.groupProps, props); 13 | 14 | return props; 15 | }); 16 | 17 | export const Slider = createComponent(props => { 18 | const htmlProps = useSlider(props); 19 | 20 | return createElement("div", htmlProps); 21 | }); 22 | 23 | export type SliderOptions = Options & { 24 | /** 25 | * Object returned by the `useSliderState` hook. 26 | */ 27 | state: SliderState; 28 | }; 29 | 30 | export type SliderProps = Props>; 31 | -------------------------------------------------------------------------------- /src/slider/slider-input.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useForkRef } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | import { mergeProps } from "@react-aria/utils"; 9 | 10 | import { SliderThumbState } from "./slider-thumb-state"; 11 | 12 | export const useSliderInput = createHook( 13 | ({ state, ...props }) => { 14 | props = { ...props, ref: useForkRef(state.inputRef, props.ref) }; 15 | props = mergeProps(state.inputProps, props); 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const SliderInput = createComponent(props => { 22 | const htmlProps = useSliderInput(props); 23 | 24 | return createElement("input", htmlProps); 25 | }); 26 | 27 | export type SliderInputOptions = Options & { 28 | /** 29 | * Object returned by the `useSliderState` hook. 30 | */ 31 | state: SliderThumbState; 32 | }; 33 | 34 | export type SliderInputProps = Props< 35 | SliderInputOptions 36 | >; 37 | -------------------------------------------------------------------------------- /src/slider/slider-label.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { SliderState } from "./slider-state"; 10 | 11 | export const useSliderLabel = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.labelProps, props); 14 | return props; 15 | }, 16 | ); 17 | 18 | export const SliderLabel = createComponent(props => { 19 | const htmlProps = useSliderLabel(props); 20 | 21 | return createElement("label", htmlProps); 22 | }); 23 | 24 | export type SliderLabelOptions = Options & { 25 | /** 26 | * Object returned by the `useSliderState` hook. 27 | */ 28 | state: SliderState; 29 | }; 30 | 31 | export type SliderLabelProps = Props< 32 | SliderLabelOptions 33 | >; 34 | -------------------------------------------------------------------------------- /src/slider/slider-output.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { SliderState } from "./slider-state"; 10 | 11 | export const useSliderOutput = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.outputProps, props); 14 | return props; 15 | }, 16 | ); 17 | 18 | export const SliderOutput = createComponent(props => { 19 | const htmlProps = useSliderOutput(props); 20 | 21 | return createElement("output", htmlProps); 22 | }); 23 | 24 | export type SliderOutputOptions = Options & { 25 | /** 26 | * Object returned by the `useSliderState` hook. 27 | */ 28 | state: SliderState; 29 | }; 30 | 31 | export type SliderOutputProps = Props< 32 | SliderOutputOptions 33 | >; 34 | -------------------------------------------------------------------------------- /src/slider/slider-state.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useSlider } from "@react-aria/slider"; 3 | import { AriaSliderProps } from "@react-types/slider"; 4 | 5 | import { SliderBaseState } from "./slider-base-state"; 6 | 7 | export function useSliderState(props: SliderStateProps): SliderState { 8 | const { state, ...rest } = props; 9 | const trackRef = React.useRef(null); 10 | const sliderProps = useSlider(rest, state, trackRef); 11 | 12 | return { ...sliderProps, trackRef, baseState: state }; 13 | } 14 | 15 | export type SliderState = { 16 | /** 17 | * Ref for the "track" element. The width of this element provides the "length" 18 | * of the track -- the span of one dimensional space that the slider thumb can be. It also 19 | * accepts click and drag motions, so that the closest thumb will follow clicks and drags on 20 | * the track.. 21 | */ 22 | trackRef: React.RefObject; 23 | 24 | /** Props for the label element. */ 25 | labelProps: React.LabelHTMLAttributes; 26 | 27 | /** Props for the root element of the slider component; groups slider inputs. */ 28 | groupProps: React.HTMLAttributes; 29 | 30 | /** Props for the track element. */ 31 | trackProps: React.HTMLAttributes; 32 | 33 | /** Props for the output element, displaying the value of the slider thumbs. */ 34 | outputProps: React.OutputHTMLAttributes; 35 | 36 | /** 37 | * Object returned by the `useSliderBaseState` hook. 38 | */ 39 | baseState: SliderBaseState; 40 | }; 41 | 42 | export type SliderStateProps = AriaSliderProps & { 43 | /** 44 | * Object returned by the `useSliderBaseState` hook. 45 | */ 46 | state: SliderBaseState; 47 | }; 48 | -------------------------------------------------------------------------------- /src/slider/slider-thumb-state.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SliderThumbAria, useSliderThumb } from "@react-aria/slider"; 3 | import { AriaSliderThumbProps } from "@react-types/slider"; 4 | 5 | import { SliderBaseState } from "./slider-base-state"; 6 | 7 | export function useSliderThumbState( 8 | props: SliderThumbStateProps, 9 | ): SliderThumbState { 10 | const { state, trackRef, ...thumbProps } = props; 11 | const inputRef = React.useRef(null); 12 | const sliderThumbProps = useSliderThumb( 13 | { 14 | inputRef, 15 | trackRef, 16 | ...thumbProps, 17 | }, 18 | state, 19 | ); 20 | 21 | return { ...sliderThumbProps, inputRef, baseState: state }; 22 | } 23 | 24 | export type SliderThumbState = SliderThumbAria & { 25 | /** A ref to the thumb input element. */ 26 | inputRef: React.RefObject; 27 | /** 28 | * Object returned by the `useSliderBaseState` hook. 29 | */ 30 | baseState: SliderBaseState; 31 | }; 32 | 33 | export type SliderThumbStateProps = AriaSliderThumbProps & { 34 | /** A ref to the track element. */ 35 | trackRef: React.RefObject; 36 | /** 37 | * Object returned by the `useSliderBaseState` hook. 38 | */ 39 | state: SliderBaseState; 40 | }; 41 | -------------------------------------------------------------------------------- /src/slider/slider-thumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { VisuallyHidden } from "ariakit"; 8 | import { As, Options, Props } from "ariakit-utils/types"; 9 | import { mergeProps } from "@react-aria/utils"; 10 | 11 | import { SliderInput } from "./slider-input"; 12 | import { SliderThumbState } from "./slider-thumb-state"; 13 | 14 | export const useSliderThumb = createHook( 15 | ({ state, ...props }) => { 16 | const children = ( 17 | 18 | 19 | 20 | ); 21 | 22 | props = { children, ...props }; 23 | props = mergeProps(state.thumbProps, props); 24 | 25 | return props; 26 | }, 27 | ); 28 | 29 | export const SliderThumb = createComponent(props => { 30 | const htmlProps = useSliderThumb(props); 31 | 32 | return createElement("div", htmlProps); 33 | }); 34 | 35 | export type SliderThumbOptions = Options & { 36 | /** 37 | * Object returned by the `useSliderThumbState` hook. 38 | */ 39 | state: SliderThumbState; 40 | }; 41 | 42 | export type SliderThumbProps = Props< 43 | SliderThumbOptions 44 | >; 45 | -------------------------------------------------------------------------------- /src/slider/slider-track.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useForkRef } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | import { mergeProps } from "@react-aria/utils"; 9 | 10 | import { SliderState } from "./slider-state"; 11 | 12 | export const useSliderTrack = createHook( 13 | ({ state, ...props }) => { 14 | props = { ...props, ref: useForkRef(state.trackRef, props.ref) }; 15 | props = mergeProps(state.trackProps, props); 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const SliderTrack = createComponent(props => { 22 | const htmlProps = useSliderTrack(props); 23 | 24 | return createElement("div", htmlProps); 25 | }); 26 | 27 | export type SliderTrackOptions = Options & { 28 | /** 29 | * Object returned by the `useSliderState` hook. 30 | */ 31 | state: SliderState; 32 | }; 33 | 34 | export type SliderTrackProps = Props< 35 | SliderTrackOptions 36 | >; 37 | -------------------------------------------------------------------------------- /src/slider/stories/SliderBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 2 | 3 | import { createPreviewTabs } from "../../../.storybook/utils"; 4 | 5 | import js from "./templates/SliderBasicJsx"; 6 | import ts from "./templates/SliderBasicTsx"; 7 | import { SliderBasic } from "./SliderBasic.component"; 8 | 9 | import "./SliderBasic.css"; 10 | 11 | type Meta = ComponentMeta; 12 | type Story = ComponentStoryObj; 13 | 14 | export default { 15 | title: "Slider/Basic", 16 | component: SliderBasic, 17 | parameters: { 18 | layout: "centered", 19 | preview: createPreviewTabs({ js, ts }), 20 | options: { showPanel: true }, 21 | }, 22 | } as Meta; 23 | 24 | export const Default: Story = { args: {} }; 25 | 26 | export const MinMax: Story = { 27 | args: { 28 | label: "Min Max", 29 | minValue: 20, 30 | maxValue: 80, 31 | }, 32 | }; 33 | 34 | export const Step: Story = { 35 | args: { 36 | label: "Stepped", 37 | step: 10, 38 | }, 39 | }; 40 | 41 | export const DefaultValue: Story = { 42 | args: { 43 | label: "Default Valued", 44 | defaultValue: [80], 45 | }, 46 | }; 47 | 48 | export const FormatOptions: Story = { 49 | args: { 50 | label: "Temperature Formatted", 51 | formatOptions: { 52 | style: "unit", 53 | unit: "celsius", 54 | unitDisplay: "narrow", 55 | }, 56 | }, 57 | }; 58 | 59 | export const Disabled: Story = { 60 | args: { 61 | label: "Disabled", 62 | isDisabled: true, 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /src/slider/stories/SliderSingle.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/SliderBasicCss"; 7 | import js from "./templates/SliderSingleJsx"; 8 | import ts from "./templates/SliderSingleTsx"; 9 | import { SliderSingle } from "./SliderSingle.component"; 10 | 11 | import "./SliderBasic.css"; 12 | 13 | type Meta = ComponentMeta; 14 | type Story = ComponentStoryObj; 15 | 16 | export default { 17 | component: SliderSingle, 18 | title: "Slider/Single", 19 | parameters: { 20 | layout: "centered", 21 | preview: createPreviewTabs({ js, ts, css }), 22 | options: { showPanel: true }, 23 | }, 24 | decorators: [ 25 | Story => { 26 | document.body.id = "slider-basic"; 27 | return ; 28 | }, 29 | ], 30 | } as Meta; 31 | 32 | export const Default: Story = { args: {} }; 33 | 34 | export const MinMax: Story = { 35 | args: { 36 | label: "Min Max", 37 | minValue: 20, 38 | maxValue: 80, 39 | }, 40 | }; 41 | 42 | export const Step: Story = { 43 | args: { 44 | label: "Stepped", 45 | step: 10, 46 | }, 47 | }; 48 | 49 | export const DefaultValue: Story = { 50 | args: { 51 | label: "Default Valued", 52 | defaultValue: [80], 53 | }, 54 | }; 55 | 56 | export const FormatOptions: Story = { 57 | args: { 58 | label: "Temperature Formatted", 59 | formatOptions: { 60 | style: "unit", 61 | unit: "celsius", 62 | unitDisplay: "narrow", 63 | }, 64 | }, 65 | }; 66 | 67 | export const Disabled: Story = { 68 | args: { 69 | label: "Disabled", 70 | isDisabled: true, 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /src/slider/stories/SliderSingleReversed.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ComponentMeta, ComponentStoryObj } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/SliderBasicCss"; 7 | import js from "./templates/SliderSingleReversedJsx"; 8 | import ts from "./templates/SliderSingleReversedTsx"; 9 | import { SliderSingleReversed } from "./SliderSingleReversed.component"; 10 | 11 | import "./SliderBasic.css"; 12 | 13 | type Meta = ComponentMeta; 14 | type Story = ComponentStoryObj; 15 | 16 | export default { 17 | component: SliderSingleReversed, 18 | title: "Slider/SingleReversed", 19 | parameters: { 20 | layout: "centered", 21 | parameters: { preview: createPreviewTabs({ js, ts, css }) }, 22 | options: { showPanel: true }, 23 | }, 24 | decorators: [ 25 | Story => { 26 | document.body.id = "slider-basic"; 27 | return ; 28 | }, 29 | ], 30 | } as Meta; 31 | 32 | export const Default: Story = { args: {} }; 33 | 34 | export const ThumbTip: Story = { 35 | args: { 36 | label: "Thumb Tipped", 37 | showTip: true, 38 | }, 39 | }; 40 | 41 | export const MinMax: Story = { 42 | args: { 43 | label: "Min Max", 44 | minValue: 20, 45 | maxValue: 80, 46 | }, 47 | }; 48 | 49 | export const Step: Story = { 50 | args: { 51 | label: "Stepped", 52 | step: 10, 53 | }, 54 | }; 55 | 56 | export const DefaultValue: Story = { 57 | args: { 58 | label: "Default Valued", 59 | defaultValue: [80], 60 | }, 61 | }; 62 | 63 | export const FormatOptions: Story = { 64 | args: { 65 | label: "Temperature Formatted", 66 | formatOptions: { 67 | style: "unit", 68 | unit: "celsius", 69 | unitDisplay: "narrow", 70 | }, 71 | }, 72 | }; 73 | 74 | export const Disabled: Story = { 75 | args: { 76 | label: "Disabled", 77 | isDisabled: true, 78 | }, 79 | }; 80 | -------------------------------------------------------------------------------- /src/timefield/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./time-segment"; 2 | export * from "./timefield-base"; 3 | export * from "./timefield-base-state"; 4 | export * from "./timefield-description"; 5 | export * from "./timefield-errormessage"; 6 | export * from "./timefield-label"; 7 | export * from "./timefield-state"; 8 | -------------------------------------------------------------------------------- /src/timefield/stories/TimeFieldBasic.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLocale } from "@react-aria/i18n"; 3 | 4 | import { 5 | TimeField, 6 | TimeFieldBaseStateProps, 7 | TimeFieldLabel, 8 | TimeSegment, 9 | useTimeFieldBaseState, 10 | useTimeFieldState, 11 | } from "../../index"; 12 | 13 | export type TimeFieldBasicProps = Omit & {}; 14 | 15 | // Example from https://react-spectrum.adobe.com/react-aria/useTimeField.html 16 | export const TimeFieldBasic: React.FC = props => { 17 | let { locale } = useLocale(); 18 | 19 | const state = useTimeFieldBaseState({ locale, ...props }); 20 | const datefield = useTimeFieldState({ ...props, state }); 21 | 22 | return ( 23 |
24 | 25 | {props.label} 26 | 27 | 28 | {state.segments.map((segment, i) => ( 29 | 37 | {segment.text} 38 | 39 | ))} 40 | {state.validationState === "invalid" && ( 41 | 42 | )} 43 | 44 |
45 | ); 46 | }; 47 | 48 | export default TimeFieldBasic; 49 | -------------------------------------------------------------------------------- /src/timefield/stories/TimeFieldBasic.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .timefield { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: flex-start; 9 | } 10 | 11 | .timefield__label { 12 | margin-bottom: 0.5rem; 13 | } 14 | 15 | .timefield__field { 16 | display: inline-flex; 17 | padding: 2px 4px; 18 | border-radius: 2px; 19 | border: 1px solid #6f6f6f; 20 | background: #fff; 21 | } 22 | 23 | .timefield__field--item { 24 | padding: 0 2px; 25 | border-radius: 4px; 26 | font-variant-numeric: tabular-nums; 27 | text-align: end; 28 | } 29 | 30 | .timefield__field--item.placeholder { 31 | color: #767676; 32 | } 33 | 34 | .timefield__field--item:focus { 35 | background-color: #1e65fd; 36 | outline: none; 37 | color: white; 38 | } 39 | -------------------------------------------------------------------------------- /src/timefield/stories/TimeFieldBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ComponentMeta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/TimeFieldBasicCss"; 7 | import js from "./templates/TimeFieldBasicJsx"; 8 | import ts from "./templates/TimeFieldBasicTsx"; 9 | import { TimeFieldBasic } from "./TimeFieldBasic.component"; 10 | 11 | import "./TimeFieldBasic.css"; 12 | 13 | type Meta = ComponentMeta; 14 | // type Story = ComponentStoryObj; 15 | 16 | export default { 17 | title: "TimeField/Basic", 18 | component: TimeFieldBasic, 19 | parameters: { 20 | layout: "centered", 21 | preview: createPreviewTabs({ js, ts, css }), 22 | }, 23 | } as Meta; 24 | 25 | export const Default = () => { 26 | return ; 27 | }; 28 | -------------------------------------------------------------------------------- /src/timefield/stories/TimeFieldStyled.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLocale } from "@react-aria/i18n"; 3 | 4 | import { 5 | TimeField, 6 | TimeFieldBaseStateProps, 7 | TimeFieldLabel, 8 | TimeSegment, 9 | useTimeFieldBaseState, 10 | useTimeFieldState, 11 | } from "../../index"; 12 | 13 | export type TimeFieldStyledProps = Omit & {}; 14 | 15 | export const TimeFieldStyled: React.FC = props => { 16 | let { locale } = useLocale(); 17 | 18 | const state = useTimeFieldBaseState({ locale, ...props }); 19 | const datefield = useTimeFieldState({ ...props, state }); 20 | 21 | return ( 22 |
23 | 24 | {props.label} 25 | 26 | 30 | {state.segments.map((segment, i) => ( 31 | 39 | {segment.text} 40 | 41 | ))} 42 | 43 |
44 | ); 45 | }; 46 | 47 | export default TimeFieldStyled; 48 | -------------------------------------------------------------------------------- /src/timefield/stories/TimeFieldStyled.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ComponentMeta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import js from "./templates/TimeFieldStyledJsx"; 7 | import ts from "./templates/TimeFieldStyledTsx"; 8 | import { TimeFieldStyled } from "./TimeFieldStyled.component"; 9 | 10 | import "./tailwind.css"; 11 | 12 | type Meta = ComponentMeta; 13 | // type Story = ComponentStoryObj; 14 | 15 | export default { 16 | title: "TimeField/Styled", 17 | component: TimeFieldStyled, 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ js, ts }), 21 | }, 22 | decorators: [ 23 | Story => { 24 | document.body.id = "tailwind"; 25 | return ; 26 | }, 27 | ], 28 | } as Meta; 29 | 30 | export const Default = () => { 31 | return ; 32 | }; 33 | -------------------------------------------------------------------------------- /src/timefield/stories/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/timefield/time-segment.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | createComponent, 4 | createElement, 5 | createHook, 6 | } from "ariakit-utils/system"; 7 | import { useForkRef } from "ariakit-utils"; 8 | import { As, Options, Props } from "ariakit-utils/types"; 9 | import { useDateSegment as useAriaDateSegment } from "@react-aria/datepicker"; 10 | import { mergeProps } from "@react-aria/utils"; 11 | import { DateSegment as DateSegmentState } from "@react-stately/datepicker"; 12 | 13 | import { TimeFieldBaseState } from "./timefield-base-state"; 14 | 15 | export const useTimeSegment = createHook( 16 | ({ state, segment, ...props }) => { 17 | const ref = useRef(null); 18 | props = { ...props, ref: useForkRef(ref, props.ref) }; 19 | 20 | const { segmentProps } = useAriaDateSegment(segment, state, ref); 21 | props = mergeProps(segmentProps, props); 22 | 23 | return props; 24 | }, 25 | ); 26 | 27 | export const TimeSegment = createComponent(props => { 28 | const htmlProps = useTimeSegment(props); 29 | 30 | return createElement("div", htmlProps); 31 | }); 32 | 33 | export type TimeSegmentOptions = Options & { 34 | /** 35 | * Current segment state return from `state.segments`. 36 | */ 37 | segment: DateSegmentState; 38 | /** 39 | * Object returned by the `useTimeFieldBaseState` hook. 40 | */ 41 | state: TimeFieldBaseState; 42 | }; 43 | 44 | export type TimeSegmentProps = Props< 45 | TimeSegmentOptions 46 | >; 47 | -------------------------------------------------------------------------------- /src/timefield/timefield-base-state.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DateFieldState, 3 | TimeFieldStateOptions, 4 | useTimeFieldState, 5 | } from "@react-stately/datepicker"; 6 | 7 | export function useTimeFieldBaseState( 8 | props: TimeFieldBaseStateProps, 9 | ): TimeFieldBaseState { 10 | const state = useTimeFieldState(props); 11 | 12 | return state; 13 | } 14 | 15 | export type TimeFieldBaseState = DateFieldState & {}; 16 | 17 | export type TimeFieldBaseStateProps = TimeFieldStateOptions & {}; 18 | -------------------------------------------------------------------------------- /src/timefield/timefield-base.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { useForkRef } from "ariakit-utils"; 7 | import { As, Options, Props } from "ariakit-utils/types"; 8 | import { mergeProps } from "@react-aria/utils"; 9 | 10 | import { TimeFieldState } from "./timefield-state"; 11 | 12 | export const useTimeField = createHook( 13 | ({ state, ...props }) => { 14 | props = { ...props, ref: useForkRef(state.ref, props.ref) }; 15 | props = mergeProps(state.fieldProps, props); 16 | 17 | return props; 18 | }, 19 | ); 20 | 21 | export const TimeField = createComponent(props => { 22 | const htmlProps = useTimeField(props); 23 | 24 | return createElement("div", htmlProps); 25 | }); 26 | 27 | export type TimeFieldOptions = Options & { 28 | /** 29 | * Object returned by the `useTimeFieldState` hook. 30 | */ 31 | state: TimeFieldState; 32 | }; 33 | 34 | export type TimeFieldProps = Props>; 35 | -------------------------------------------------------------------------------- /src/timefield/timefield-description.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { TimeFieldState } from "./timefield-state"; 10 | 11 | export const useTimeFieldDescription = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.descriptionProps, props); 14 | 15 | return props; 16 | }, 17 | ); 18 | 19 | export const TimeFieldDescription = 20 | createComponent(props => { 21 | const htmlProps = useTimeFieldDescription(props); 22 | 23 | return createElement("span", htmlProps); 24 | }); 25 | 26 | export type TimeFieldDescriptionOptions = Options & { 27 | /** 28 | * Object returned by the `useTimeFieldState` hook. 29 | */ 30 | state: TimeFieldState; 31 | }; 32 | 33 | export type TimeFieldDescriptionProps = Props< 34 | TimeFieldDescriptionOptions 35 | >; 36 | -------------------------------------------------------------------------------- /src/timefield/timefield-errormessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { TimeFieldState } from "./timefield-state"; 10 | 11 | export const useTimeFieldErrorMessage = 12 | createHook(({ state, ...props }) => { 13 | props = mergeProps(state.errorMessageProps, props); 14 | 15 | return props; 16 | }); 17 | 18 | export const TimeFieldErrorMessage = 19 | createComponent(props => { 20 | const htmlProps = useTimeFieldErrorMessage(props); 21 | 22 | return createElement("span", htmlProps); 23 | }); 24 | 25 | export type TimeFieldErrorMessageOptions = Options & { 26 | /** 27 | * Object returned by the `useTimeFieldState` hook. 28 | */ 29 | state: TimeFieldState; 30 | }; 31 | 32 | export type TimeFieldErrorMessageProps = Props< 33 | TimeFieldErrorMessageOptions 34 | >; 35 | -------------------------------------------------------------------------------- /src/timefield/timefield-label.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createComponent, 3 | createElement, 4 | createHook, 5 | } from "ariakit-utils/system"; 6 | import { As, Options, Props } from "ariakit-utils/types"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | import { TimeFieldState } from "./timefield-state"; 10 | 11 | export const useTimeFieldLabel = createHook( 12 | ({ state, ...props }) => { 13 | props = mergeProps(state.labelProps, props); 14 | 15 | return props; 16 | }, 17 | ); 18 | 19 | export const TimeFieldLabel = createComponent(props => { 20 | const htmlProps = useTimeFieldLabel(props); 21 | 22 | return createElement("span", htmlProps); 23 | }); 24 | 25 | export type TimeFieldLabelOptions = Options & { 26 | /** 27 | * Object returned by the `useTimeFieldState` hook. 28 | */ 29 | state: TimeFieldState; 30 | }; 31 | 32 | export type TimeFieldLabelProps = Props< 33 | TimeFieldLabelOptions 34 | >; 35 | -------------------------------------------------------------------------------- /src/timefield/timefield-state.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useRef } from "react"; 2 | import { DateFieldAria, useTimeField } from "@react-aria/datepicker"; 3 | import { AriaTimeFieldProps, TimeValue } from "@react-types/datepicker"; 4 | 5 | import { TimeFieldBaseState } from "./timefield-base-state"; 6 | 7 | export function useTimeFieldState({ 8 | state, 9 | ...props 10 | }: TimeFieldStateProps): TimeFieldState { 11 | const ref = useRef(null); 12 | const timefield = useTimeField(props, state, ref); 13 | 14 | return { ...timefield, ref }; 15 | } 16 | 17 | export type TimeFieldState = DateFieldAria & { 18 | /** 19 | * Reference for the date picker's visible label element, if any. 20 | */ 21 | ref: RefObject; 22 | }; 23 | 24 | export type TimeFieldStateProps = AriaTimeFieldProps & { 25 | /** 26 | * Object returned by the `useTimeFieldBaseState` hook. 27 | */ 28 | state: TimeFieldBaseState; 29 | }; 30 | -------------------------------------------------------------------------------- /src/toast/ToastTypes.ts: -------------------------------------------------------------------------------- 1 | export interface DefaultToast { 2 | id: string; 3 | visible: boolean; 4 | reverseOrder: boolean; 5 | animationDuration: number; 6 | children?: React.ReactNode; 7 | } 8 | 9 | export type TimerToast = DefaultToast & { 10 | createdAt: number; 11 | pauseDuration: number; 12 | pausedAt: number | null; 13 | autoDismiss: boolean; 14 | dismissDuration: number; 15 | }; 16 | 17 | export interface State { 18 | toasts: T[]; 19 | } 20 | 21 | export enum ActionType { 22 | ADD_TOAST, 23 | UPSERT_TOAST, 24 | UPDATE_TOAST, 25 | UPDATE_FIELD_TOAST, 26 | UPDATE_ALL_TOAST, 27 | DISMISS_TOAST, 28 | REMOVE_TOAST, 29 | } 30 | 31 | export type Action = 32 | | { 33 | type: ActionType.ADD_TOAST; 34 | toast: T; 35 | maxToasts?: number; 36 | } 37 | | { 38 | type: ActionType.UPSERT_TOAST; 39 | toast: T; 40 | } 41 | | { 42 | type: ActionType.UPDATE_TOAST; 43 | toast: Partial; 44 | } 45 | | { 46 | type: ActionType.UPDATE_FIELD_TOAST; 47 | field: keyof T; 48 | fieldValue: any; 49 | toast: Partial; 50 | } 51 | | { 52 | type: ActionType.UPDATE_ALL_TOAST; 53 | toast: Partial; 54 | } 55 | | { 56 | type: ActionType.DISMISS_TOAST; 57 | toastId?: string; 58 | } 59 | | { 60 | type: ActionType.REMOVE_TOAST; 61 | toastId?: string; 62 | }; 63 | -------------------------------------------------------------------------------- /src/toast/__keys.ts: -------------------------------------------------------------------------------- 1 | // Automatically generated 2 | export const DEFAULT_TOAST_KEYS = [] as const; 3 | export const DEFAULT_TOAST_PROVIDER_KEYS = [] as const; 4 | export const CONFIGURABLE_TOAST_KEYS = [] as const; 5 | export const TOAST_KEYS = [] as const; 6 | -------------------------------------------------------------------------------- /src/toast/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { DefaultToast } from "../index"; 2 | 3 | export const genId = (() => { 4 | let count = 0; 5 | return () => { 6 | return (++count).toString(); 7 | }; 8 | })(); 9 | 10 | export const getToast = ( 11 | toasts: T[], 12 | toastId: string, 13 | ) => { 14 | const index = toasts.findIndex(toast => toast.id === toastId); 15 | return toasts[index]; 16 | }; 17 | -------------------------------------------------------------------------------- /src/toast/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./__keys"; 2 | export * from "./CreateToastContext"; 3 | export * from "./CreateToastContext.types"; 4 | export * from "./helpers"; 5 | export * from "./ToastState"; 6 | export * from "./ToastTypes"; 7 | export * from "./useToastTimer"; 8 | -------------------------------------------------------------------------------- /src/toast/stories/ToastBasic.css: -------------------------------------------------------------------------------- 1 | /* Toast Styles */ 2 | .alert-enter { 3 | opacity: 0; 4 | } 5 | 6 | .alert-enter-active { 7 | opacity: 1; 8 | transition: opacity 200ms; 9 | } 10 | 11 | .alert-exit { 12 | opacity: 1; 13 | } 14 | 15 | .alert-exit-active { 16 | opacity: 0; 17 | transition: opacity 200ms; 18 | } 19 | 20 | @keyframes shrink { 21 | from { 22 | height: 100%; 23 | } 24 | 25 | to { 26 | height: 0%; 27 | } 28 | } 29 | 30 | .space-y-2 > * + * { 31 | margin-top: 0.5rem; 32 | margin-bottom: 0.5rem; 33 | } 34 | 35 | .space-x-2 > * + * { 36 | margin-left: 0.5rem; 37 | margin-right: 0.5rem; 38 | } 39 | 40 | .sr-only { 41 | position: absolute; 42 | width: 1px; 43 | height: 1px; 44 | padding: 0; 45 | margin: -1px; 46 | overflow: hidden; 47 | clip: rect(0, 0, 0, 0); 48 | white-space: nowrap; 49 | border-width: 0; 50 | } 51 | -------------------------------------------------------------------------------- /src/toast/stories/ToastBasic.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Meta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/ToastBasicCss"; 7 | import js from "./templates/ToastBasicJsx"; 8 | import ts from "./templates/ToastBasicTsx"; 9 | import jsUtils from "./templates/UtilsJsx"; 10 | import tsUtils from "./templates/UtilsTsx"; 11 | import { Toast } from "./ToastBasic.component"; 12 | 13 | import "./ToastBasic.css"; 14 | 15 | export default { 16 | component: Toast, 17 | title: "Toast/Basic", 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ 21 | js, 22 | ts, 23 | css, 24 | jsUtils, 25 | tsUtils, 26 | deps: ["@chakra-ui/utils"], 27 | }), 28 | options: { showPanel: false }, 29 | }, 30 | } as Meta; 31 | 32 | export const Default = () => ; 33 | -------------------------------------------------------------------------------- /src/toast/stories/ToastCSSAnimated.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Meta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/ToastBasicCss"; 7 | import js from "./templates/ToastCSSAnimatedJsx"; 8 | import ts from "./templates/ToastCSSAnimatedTsx"; 9 | import jsUtils from "./templates/UtilsJsx"; 10 | import tsUtils from "./templates/UtilsTsx"; 11 | import { Toast } from "./ToastCSSAnimated.component"; 12 | 13 | import "./ToastBasic.css"; 14 | 15 | export default { 16 | component: Toast, 17 | title: "Toast/CSSAnimated", 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ 21 | js, 22 | ts, 23 | css, 24 | jsUtils, 25 | tsUtils, 26 | deps: ["@chakra-ui/utils"], 27 | }), 28 | options: { showPanel: false }, 29 | }, 30 | decorators: [ 31 | Story => { 32 | document.body.id = "toast-basic"; 33 | return ; 34 | }, 35 | ], 36 | } as Meta; 37 | 38 | export const Default = () => ; 39 | -------------------------------------------------------------------------------- /src/toast/stories/ToastCSSTransition.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Meta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/ToastBasicCss"; 7 | import js from "./templates/ToastCSSTransitionJsx"; 8 | import ts from "./templates/ToastCSSTransitionTsx"; 9 | import jsUtils from "./templates/UtilsJsx"; 10 | import tsUtils from "./templates/UtilsTsx"; 11 | import { Toast } from "./ToastCSSTransition.component"; 12 | 13 | import "./ToastBasic.css"; 14 | 15 | export default { 16 | component: Toast, 17 | title: "Toast/CSSTransition", 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ 21 | js, 22 | ts, 23 | css, 24 | jsUtils, 25 | tsUtils, 26 | deps: ["react-transition-group", "@chakra-ui/utils"], 27 | }), 28 | options: { showPanel: false }, 29 | }, 30 | decorators: [ 31 | Story => { 32 | document.body.id = "toast-basic"; 33 | return ; 34 | }, 35 | ], 36 | } as Meta; 37 | 38 | export const Default = () => ; 39 | -------------------------------------------------------------------------------- /src/toast/stories/ToastReactSpring.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Meta } from "@storybook/react"; 3 | 4 | import { createPreviewTabs } from "../../../.storybook/utils"; 5 | 6 | import css from "./templates/ToastBasicCss"; 7 | import js from "./templates/ToastReactSpringJsx"; 8 | import ts from "./templates/ToastReactSpringTsx"; 9 | import jsUtils from "./templates/UtilsJsx"; 10 | import tsUtils from "./templates/UtilsTsx"; 11 | import { Toast } from "./ToastReactSpring.component"; 12 | 13 | import "./ToastBasic.css"; 14 | 15 | export default { 16 | component: Toast, 17 | title: "Toast/ReactSpring", 18 | parameters: { 19 | layout: "centered", 20 | preview: createPreviewTabs({ 21 | js, 22 | ts, 23 | css, 24 | jsUtils, 25 | tsUtils, 26 | deps: ["@react-spring/web", "@chakra-ui/utils"], 27 | }), 28 | options: { showPanel: false }, 29 | }, 30 | decorators: [ 31 | Story => { 32 | document.body.id = "toast-basic"; 33 | return ; 34 | }, 35 | ], 36 | } as Meta; 37 | 38 | export const Default = () => ; 39 | -------------------------------------------------------------------------------- /src/toast/useToastTimer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { DismissToast, UpdateToast } from "./CreateToastContext.types"; 4 | import { getToast } from "./helpers"; 5 | import { TimerToast } from "./ToastTypes"; 6 | 7 | export const useToastTimer = ( 8 | toasts: TimerToast[], 9 | updateToast: UpdateToast, 10 | dismissToast: DismissToast, 11 | ) => { 12 | React.useEffect(() => { 13 | const now = Date.now(); 14 | const timeouts = toasts.map(t => { 15 | if (!t.autoDismiss) return undefined; 16 | if (t.pausedAt) return undefined; 17 | 18 | const durationLeft = 19 | (t.dismissDuration || 0) + t.pauseDuration - (now - t.createdAt); 20 | 21 | if (durationLeft < 0) { 22 | if (t.visible) { 23 | dismissToast(t.id); 24 | } 25 | return undefined; 26 | } 27 | 28 | return setTimeout(() => { 29 | dismissToast(t.id); 30 | }, durationLeft); 31 | }); 32 | 33 | return () => { 34 | timeouts.forEach(timeout => timeout && clearTimeout(timeout)); 35 | }; 36 | }, [toasts, dismissToast]); 37 | 38 | const pauseTimer = React.useCallback( 39 | (toastId: string) => { 40 | const toast = getToast(toasts, toastId); 41 | 42 | if (!toast?.autoDismiss) return; 43 | 44 | updateToast(toastId, { pausedAt: Date.now() }); 45 | }, 46 | [toasts, updateToast], 47 | ); 48 | 49 | const resumeTimer = React.useCallback( 50 | (toastId: string) => { 51 | const toast = getToast(toasts, toastId); 52 | 53 | if (!toast?.autoDismiss) return; 54 | 55 | const now = Date.now(); 56 | const diff = now - (toast.pausedAt || 0); 57 | 58 | updateToast(toastId, { 59 | pausedAt: null, 60 | pauseDuration: toast.pauseDuration + diff, 61 | }); 62 | }, 63 | [toasts, updateToast], 64 | ); 65 | 66 | return { resumeTimer, pauseTimer }; 67 | }; 68 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require("tailwindcss/defaultTheme"); 2 | const plugin = require("tailwindcss/plugin"); 3 | 4 | module.exports = { 5 | darkMode: "class", 6 | content: ["./src/**/*"], 7 | theme: { 8 | extend: { 9 | fontFamily: { 10 | sans: ["Inter var", ...defaultTheme.fontFamily.sans], 11 | }, 12 | }, 13 | }, 14 | variants: {}, 15 | plugins: [ 16 | plugin(({ addUtilities, addVariant, theme }) => { 17 | addUtilities({ 18 | ".focus-outline": { 19 | outline: `2px solid ${theme("colors.blue.600")}`, 20 | outlineOffset: "2px", 21 | }, 22 | }); 23 | 24 | addVariant("enter", "&[data-enter]"); 25 | addVariant("leave", "&[data-leave]"); 26 | addVariant("active-item", "&[data-active-item]"); 27 | 28 | addVariant("active", ["&:active", "&[data-active]"]); 29 | addVariant("focus-visible", ["&:focus-visible", "&[data-focus-visible]"]); 30 | addVariant("aria-invalid", '&[aria-invalid="true"]'); 31 | addVariant("aria-disabled", '&[aria-disabled="true"]'); 32 | addVariant("aria-selected", '&[aria-selected="true"]'); 33 | addVariant("aria-expanded", '&[aria-expanded="true"]'); 34 | addVariant("aria-checked", '&[aria-checked="true"]'); 35 | }), 36 | ], 37 | }; 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "esnext", 5 | "lib": ["dom", "esnext", "dom.iterable"], 6 | "types": ["node", "jest", "@testing-library/jest-dom"], 7 | "jsx": "react", 8 | "moduleResolution": "node", 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "strictFunctionTypes": true, 14 | "strictNullChecks": true, 15 | "noImplicitAny": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "downlevelIteration": true, 19 | "strictPropertyInitialization": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "skipLibCheck": true, 22 | "declaration": true, 23 | "declarationDir": "dist/types", 24 | "sourceMap": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["**/*/stories", "**/*/__tests__"] 5 | } 6 | --------------------------------------------------------------------------------