diff --git a/.circleci/config.yml b/.circleci/config.yml index 3cb86a889d..2d3de5efbd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,14 +10,6 @@ jobs: environment: TZ: "/usr/share/zoneinfo/America/Los_Angeles" steps: - # https://circleci.com/docs/2.0/api-job-trigger/ - - run: - name: Start Visual Tests job - command: | - curl --user ${CIRCLE_API_USER_TOKEN} \ - --data build_parameters[CIRCLE_JOB]=visual \ - --data revision=$CIRCLE_SHA1 \ - https://circleci.com/api/v1.1/project/github/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/tree/$CIRCLE_BRANCH - run: name: Update yarn command: | @@ -62,17 +54,6 @@ jobs: name: E2E Tests command: yarn test:e2e - - restore_cache: - key: v1.1-vuln-scans-{{ checksum "yarn.lock" }} - - run: - name: Vulnerability Tests - command: yarn test:vulns - # https://discuss.circleci.com/t/add-mechanism-to-update-existing-cache-key/9014/12 - - save_cache: - key: v1.1-vuln-scans-{{ checksum "yarn.lock" }}-{{ epoch }} - paths: - - .vuln-scans - - run: name: Project Tests command: yarn test:projects @@ -112,41 +93,3 @@ jobs: echo "//registry.npmjs.org/:_authToken=${npm_TOKEN}" > ~/project/.npmrc yarn lerna publish --canary --preid "${CIRCLE_BUILD_NUM}.${CIRCLE_BRANCH}" --yes fi - - visual: - docker: - - image: circleci/node:8 - environment: - TZ: "/usr/share/zoneinfo/America/Los_Angeles" - steps: - - run: - name: Update yarn - command: | - # remove default yarn - sudo rm -rf $(dirname $(which yarn))/yarn* - # download latest - rm -rf ~/.yarn - curl -o- -L https://yarnpkg.com/install.sh | bash - echo 'export PATH="${PATH}:${HOME}/.yarn/bin"' >> $BASH_ENV - - checkout - # because we don't invoke npm (we use yarn) we need to add npm bin to PATH manually - - run: - name: Add npm bin to PATH - command: echo 'export PATH="${PATH}:$(npm bin)"' >> $BASH_ENV - - restore_cache: - keys: - - v1.1-dependencies-{{ checksum "yarn.lock" }} - - v1.1-dependencies - - run: - name: Install Dependencies - command: yarn - - save_cache: - key: v1.1-dependencies-{{ checksum "yarn.lock" }} - paths: - - ~/.cache/yarn - - .yarn-cache - - node_modules - - - run: - name: Visual Tests - command: yarn test:visual diff --git a/.github/add-a-feature.md b/.github/add-a-feature.md index 508fa118e9..02f568e43e 100644 --- a/.github/add-a-feature.md +++ b/.github/add-a-feature.md @@ -1,5 +1,4 @@ -Add a feature -============= +# Add a feature @@ -43,11 +42,14 @@ Once the component spec is solidified, it's time to write some code. The followi You can create a new component `MyComponent` by following the example of an existing component (e.g. Button). The corresponding component directory trees should be created in correct places: - - the component under `/src/components/MyComponent`, + +- the component under `/packages/{package}/src/components/MyComponent`, - the docs under `/docs/src/examples/components/MyComponent`, - - the tests under `/test/specs/components/MyComponent` +- the tests under `/packages/{package}/test/specs/components/MyComponent` + +`{package}` is likely going to stand for `react` if you are contributing a component to the main package. -You can customize the styles of your component by adding necessary variables and styles as part of your theme. +You can customize the styles of your component by adding necessary variables and styles as part of your theme. E.g. for update on the `teams` theme: `/src/themes/` ### Good practice @@ -57,8 +59,7 @@ Generally if you're updating a component, push a small change so that your PR co Stateless components should be written as an arrow `function`: ```tsx - -const Button: React.FunctionalComponent = (props) => { +const Button: React.FunctionalComponent = props => { // ... } ``` @@ -90,45 +91,45 @@ Here's an example: Every component must have fully described `MyComponentProps` interface and `propTypes`. - ```tsx +```tsx import * as PropTypes from 'prop-types' import * as React from 'react' import { - ChildrenComponentProps, - ContentComponentProps, - UIComponentProps, - commonPropTypes, + ChildrenComponentProps, + ContentComponentProps, + UIComponentProps, + commonPropTypes, } from '../../lib' export interface DividerProps - extends UIComponentProps, - ChildrenComponentProps, - ContentComponentProps { - /** - * Accessibility behavior if overridden by the user. - */ - accessibility?: Accessibility - - /** A divider can be fitted, without any space above or below it. */ - fitted?: boolean - - /** Size multiplier (default 0) * */ - size?: number - - /** A divider can appear more important and draw the user's attention. */ - important?: boolean + extends UIComponentProps, + ChildrenComponentProps, + ContentComponentProps { + /** + * Accessibility behavior if overridden by the user. + */ + accessibility?: Accessibility + + /** A divider can be fitted, without any space above or below it. */ + fitted?: boolean + + /** Size multiplier (default 0) * */ + size?: number + + /** A divider can appear more important and draw the user's attention. */ + important?: boolean } // ... - static propTypes = { - ...commonPropTypes.createCommon({ color: true }), - fitted: PropTypes.bool, - important: PropTypes.bool, - size: PropTypes.number, - } - ``` + static propTypes = { + ...commonPropTypes.createCommon({ color: true }), + fitted: PropTypes.bool, + important: PropTypes.bool, + size: PropTypes.number, + } +``` ### State @@ -137,7 +138,7 @@ Strive to use stateless functional components when possible: ```tsx export interface MyComponentProps {} -const MyComponent: React.FunctionalComponent = (props) => { +const MyComponent: React.FunctionalComponent = props => { return
} ``` @@ -148,7 +149,7 @@ If you're component requires event handlers, it is a stateful class component. W export interface MyComponentProps {} class MyComponent extends AutoControlledComponent { - handleClick = (e) => { + handleClick = e => { console.log('Clicked my component!') } @@ -165,6 +166,7 @@ Review [common tests](test-a-feature.md#common-tests) below. You should now add ### Add doc site example Create a new documentation example that demonstrates usage of the new feature. + 1. Create a new example in `/docs/src/examples/components` under the appropriate component. 1. Add your example to the `index.ts` in respective directory. 1. Running `yarn start` should now show your example in the doc site. diff --git a/.github/workflows/screener.yml b/.github/workflows/screener.yml new file mode 100644 index 0000000000..194a6b2893 --- /dev/null +++ b/.github/workflows/screener.yml @@ -0,0 +1,22 @@ +name: Screener +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - master + +jobs: + test: + name: Test visuals on Screener + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 8 + - run: yarn install + - run: yarn test:visual + env: + CI: true + SCREENER_API_KEY: ${{secrets.SCREENER_API_KEY}} diff --git a/.nowignore b/.nowignore index 8b62c97d75..3974400c9e 100644 --- a/.nowignore +++ b/.nowignore @@ -17,7 +17,6 @@ perf/dist .editorconfig .gitignore .prettierignore -.snyk codecov.yml dangerfile.ts jest.config.js diff --git a/.snyk b/.snyk deleted file mode 100644 index 88ba6145f7..0000000000 --- a/.snyk +++ /dev/null @@ -1,4 +0,0 @@ -# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. -version: v1.13.1 -ignore: {} -patch: {} diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e8087c65..8b30852c60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,18 +17,69 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Fixes +- Update `call-missed-line` icon in Teams theme @codepretty ([#2059](https://github.com/stardust-ui/react/pull/2059)) +- Show debug panel correctly for components with no owner @miroslavstastny ([#2055](https://github.com/stardust-ui/react/pull/2055)) +- Correctly handle empty key actions in RTL @miroslavstastny ([#2060](https://github.com/stardust-ui/react/pull/2060)) +- Accessibility improvements for `tree` and `splitButton` @kolaps33 ([#2032](https://github.com/stardust-ui/react/pull/2032)) +- Fixing a core keydown disconnect issue @dzearing ([#2056](https://github.com/stardust-ui/react/pull/2056)) + +### Features +- Add `menu` prop on `ToolbarMenuItem` component @mnajdova ([#1984](https://github.com/stardust-ui/react/pull/1984)) + +### Documentation +- Editor Toolbar prototype: Fix overflow menu overflowing in Portal window @miroslavstastny ([#2053](https://github.com/stardust-ui/react/pull/2053)) + + +## [v0.40.1](https://github.com/stardust-ui/react/tree/v0.40.1) (2019-10-18) +[Compare changes](https://github.com/stardust-ui/react/compare/v0.40.0...v0.40.1) + +### Features +- Export `robot`, `tabs` and `plugs` icon to Teams theme @codepretty ([#2026](https://github.com/stardust-ui/react/pull/2026)) +- Add CSSinJS debug panel @levithomason @miroslavstastny @mnajdova ([#1974](https://github.com/stardust-ui/react/pull/1974)) +- Add ability to set custom footer for `Dialog` @kolaps33 ([#2005](https://github.com/stardust-ui/react/pull/2005)) + +### Fixes +- Correctly handle RTL in `Alert` component @miroslavstastny ([#2018](https://github.com/stardust-ui/react/pull/2018)) +- Popper should use the correct window instance @jurokapsiar ([#2028](https://github.com/stardust-ui/react/pull/2028)) +- Checking if the slot attributes are defined @mshoho ([#2040](https://github.com/stardust-ui/react/pull/2040)) + +### Performance +- Remove redundant usages of `Box` component in `Attachment`, `Popup` and `Tooltip` @layershifter ([#2023](https://github.com/stardust-ui/react/pull/2023)) +- Refactor `ListItem` to avoid usages of `Flex` component @layershifter ([#2025](https://github.com/stardust-ui/react/pull/2025)) +- Cache resolved component variables @jurokapsiar @miroslavstastny ([#2041](https://github.com/stardust-ui/react/pull/2041)) + +### Documentation +- Fix 'RTL' and 'Theme it' in examples @miroslavstastny ([#2020](https://github.com/stardust-ui/react/pull/2020)) +- Prototype for custom scrollbar for menu, dialog, popup and list @jurokapsiar ([#1962](https://github.com/stardust-ui/react/pull/1962)) + + +## [v0.40.0](https://github.com/stardust-ui/react/tree/v0.40.0) (2019-10-09) +[Compare changes](https://github.com/stardust-ui/react/compare/v0.39.0...v0.40.0) + +### BREAKING CHANGES +- Remove `onReduceItems` prop from Toolbar @miroslavstastny ([#2010](https://github.com/stardust-ui/react/pull/2010)) + ### Fixes - Fix `bodyBackground` color for Teams dark theme to be the correct grey value @codepretty ([#1961](https://github.com/stardust-ui/react/pull/1961)) - Updating `Button` styles for Teams dark & high contrast themes to match design @notandrew ([#1933](https://github.com/stardust-ui/react/pull/1933)) - Update Office brand icons in Teams theme with latest version @notandrew ([#1954](https://github.com/stardust-ui/react/pull/1954)) - Fix various component documentation issues @davezuko ([#1992](https://github.com/stardust-ui/react/pull/1992)) +- Fix accessibility issue by adding border to reactions in Teams high contrast theme @codepretty ([#2001](https://github.com/stardust-ui/react/pull/2001)) ### Features - Add experimental runtime accessibility attributes validation (the initial step validates the Button component only) @mshoho ([#1911](https://github.com/stardust-ui/react/pull/1911)) - Add `sync` icon to Teams theme @codepretty ([#1973](https://github.com/stardust-ui/react/pull/1973)) - Updating category colors palette and schemes in Teams theme @codepretty ([#1994](https://github.com/stardust-ui/react/pull/1994)) - Add `bell` icon to Teams theme @codepretty ([#1993](https://github.com/stardust-ui/react/pull/1993)) +- Simplify rendering when tooltip is not visible @jurokapsiar ([#1981](https://github.com/stardust-ui/react/pull/1981)) - Add `thumbtack`, `thumbtack-slash` and `question-circle` icons to Teams theme @codepretty ([#2000](https://github.com/stardust-ui/react/pull/2000)) +- Add `overflow` prop to `Toolbar` @levithomason @miroslavstastny @layershifter ([#2010](https://github.com/stardust-ui/react/pull/2010)) + +### Documentation +- Copy to clipboard prototype - attached confirmation @jurokapsiar ([#1900](https://github.com/stardust-ui/react/pull/1900)) +- Add `EditorToolbar` prototype @layershifter ([#2010](https://github.com/stardust-ui/react/pull/2010)) + ## [v0.39.0](https://github.com/stardust-ui/react/tree/v0.39.0) (2019-09-23) diff --git a/README.md b/README.md index 5bd83e73aa..b1445b69d6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@

- - + +

- Stardust UI + Fluent UI

@@ -38,7 +38,7 @@ *** -Stardust is a set of specifications and tools for building UI libraries. It is based on a fork of [Semantic UI React (SUIR)][200]. +Fluent UI React represents a set of specifications and tools for building UI libraries. ## How Can I Help? @@ -95,17 +95,17 @@ You can find Stardust usage examples by accessing the [doc site][5] See the [MANIFESTO.md][1] for details. SUIR v2 will be built on the specifications and tools from Stardust. -[1]: https://github.com/stardust-ui/react/blob/master/MANIFESTO.md -[2]: https://github.com/stardust-ui/react/issues/new/choose -[3]: https://github.com/stardust-ui/react/blob/master/.github/CONTRIBUTING.md -[4]: https://github.com/stardust-ui/react/blob/master/CHANGELOG.md -[5]: https://stardust-ui.github.io/react/quick-start +[1]: https://github.com/microsoft/fluent-ui-react/blob/master/MANIFESTO.md +[2]: https://github.com/microsoft/fluent-ui-react/issues/new/choose +[3]: https://github.com/microsoft/fluent-ui-react/blob/master/.github/CONTRIBUTING.md +[4]: https://github.com/microsoft/fluent-ui-react/blob/master/CHANGELOG.md +[5]: https://microsoft.github.io/fluent-ui-react -[100]: https://github.com/stardust-ui/react/labels/help%20wanted -[101]: https://github.com/stardust-ui/react/issues?q=is%3Aopen+RFC+label%3ARFC -[102]: https://github.com/stardust-ui/react/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+component%22 -[103]: https://github.com/stardust-ui/react/labels/good%20first%20issue +[100]: https://github.com/microsoft/fluent-ui-react/labels/help%20wanted +[101]: https://github.com/microsoft/fluent-ui-react/issues?q=is%3Aopen+RFC+label%3ARFC +[102]: https://github.com/microsoft/fluent-ui-react/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+component%22 +[103]: https://github.com/microsoft/fluent-ui-react/labels/good%20first%20issue [200]: https://github.com/Semantic-Org/Semantic-UI-React diff --git a/build/dangerjs/checkChangelog.ts b/build/dangerjs/checkChangelog.ts index 8451725fe6..2b0ce60404 100644 --- a/build/dangerjs/checkChangelog.ts +++ b/build/dangerjs/checkChangelog.ts @@ -21,7 +21,7 @@ const hasAddedLinesAfterVersionInChangelog = async (danger): Promise => const getMalformedChangelogEntries = async (danger): Promise => { // +- description @githubname ([#DDDD](https://github.com/stardust-ui/react/pull/DDDD)) - const validEntry = /^\+- .*@\S+ \(\[#\d+]\(https:\/\/github\.com\/stardust-ui\/react\/pull\/\d+\)\)$/ + const validEntry = /^\+- .*@\S+ \(\[#(\d+)]\(https:\/\/github\.com\/(?:stardust-ui\/react|microsoft\/fluent-ui-react)\/pull\/\1\)\)$/ const addedLines = await getAddedLinesFromChangelog(danger) diff --git a/build/dangerjs/detectNonApprovedDependencies/approvedPackages.ts b/build/dangerjs/detectNonApprovedDependencies/approvedPackages.ts index 24c9ceeb7d..3e22ab5b68 100644 --- a/build/dangerjs/detectNonApprovedDependencies/approvedPackages.ts +++ b/build/dangerjs/detectNonApprovedDependencies/approvedPackages.ts @@ -7,6 +7,7 @@ export default [ 'css-in-js-utils@3.0.0', 'downshift@3.2.6', 'downshift@3.2.10', + 'downshift@3.2.14', 'fast-loops@1.0.1', 'fast-memoize@2.5.1', 'fbjs@0.8.17', @@ -75,6 +76,7 @@ export default [ 'react-fela@10.5.0', 'react-fela@10.6.1', 'react-is@16.8.2', + 'react-is@16.9.0', 'react-resize-detector@4.2.0', 'react@16.8.3', 'resize-observer-polyfill@1.5.1', diff --git a/build/gulp/plugins/util/parseDefaultValue.ts b/build/gulp/plugins/util/parseDefaultValue.ts index 9414d3cf71..f8b9ee5220 100644 --- a/build/gulp/plugins/util/parseDefaultValue.ts +++ b/build/gulp/plugins/util/parseDefaultValue.ts @@ -20,7 +20,7 @@ const parseDefaultValue = ( return defaultValue } - if (_.isPlainObject(defaultValue)) { + if (_.isPlainObject(defaultValue) || _.isArray(defaultValue)) { return defaultValue } diff --git a/build/gulp/tasks/docs.ts b/build/gulp/tasks/docs.ts index 28dfa42c75..4e2017b9be 100644 --- a/build/gulp/tasks/docs.ts +++ b/build/gulp/tasks/docs.ts @@ -85,8 +85,8 @@ task( const componentsSrc = [ `${paths.posix.packageSrc('react')}/components/*/[A-Z]*.tsx`, + `${paths.posix.packageSrc('react-bindings')}/FocusZone/[A-Z]!(*.types).tsx`, `${paths.posix.packageSrc('react-component-ref')}/[A-Z]*.tsx`, - `${paths.posix.packageSrc('react')}/lib/accessibility/FocusZone/[A-Z]!(*.types).tsx`, ] const behaviorSrc = [`${paths.posix.packageSrc('accessibility')}/behaviors/*/[a-z]*Behavior.ts`] const examplesIndexSrc = `${paths.posix.docsSrc()}/examples/*/*/*/index.tsx` diff --git a/build/gulp/tasks/test-circulars/config.ts b/build/gulp/tasks/test-circulars/config.ts index 9071095c02..dcabab737c 100644 --- a/build/gulp/tasks/test-circulars/config.ts +++ b/build/gulp/tasks/test-circulars/config.ts @@ -16,4 +16,22 @@ export const cyclesToSkip = [ reactPackageDist('components/Reaction/Reaction.js'), reactPackageDist('components/Reaction/ReactionGroup.js'), ], + [ + reactPackageDist('components/Toolbar/ToolbarMenu.js'), + reactPackageDist('components/Toolbar/ToolbarMenuRadioGroup.js'), + reactPackageDist('components/Toolbar/ToolbarMenuItem.js'), + reactPackageDist('components/Toolbar/ToolbarMenu.js'), + ], + [ + reactPackageDist('components/Toolbar/ToolbarMenuItem.js'), + reactPackageDist('components/Toolbar/ToolbarMenu.js'), + reactPackageDist('components/Toolbar/ToolbarMenuRadioGroup.js'), + reactPackageDist('components/Toolbar/ToolbarMenuItem.js'), + ], + [ + reactPackageDist('components/Toolbar/ToolbarMenuRadioGroup.js'), + reactPackageDist('components/Toolbar/ToolbarMenuItem.js'), + reactPackageDist('components/Toolbar/ToolbarMenu.js'), + reactPackageDist('components/Toolbar/ToolbarMenuRadioGroup.js'), + ], ] diff --git a/build/gulp/tasks/test-projects/cra/App.tsx b/build/gulp/tasks/test-projects/cra/App.tsx index ad08bca953..7cd7c5e4be 100644 --- a/build/gulp/tasks/test-projects/cra/App.tsx +++ b/build/gulp/tasks/test-projects/cra/App.tsx @@ -8,6 +8,7 @@ import { Header, Icon, Image, + imageBehavior, Input, Popup, Provider, @@ -29,7 +30,7 @@ class App extends React.Component {
diff --git a/build/gulp/tasks/test-projects/typings/index.tsx b/build/gulp/tasks/test-projects/typings/index.tsx index c4c7c03b08..e898b877a8 100644 --- a/build/gulp/tasks/test-projects/typings/index.tsx +++ b/build/gulp/tasks/test-projects/typings/index.tsx @@ -3,7 +3,7 @@ import * as React from 'react' const App = () => ( - + ) diff --git a/build/gulp/tasks/test-vulns.ts b/build/gulp/tasks/test-vulns.ts deleted file mode 100644 index 5e141ebfe1..0000000000 --- a/build/gulp/tasks/test-vulns.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as fs from 'fs' -import { task } from 'gulp' -import * as path from 'path' -import debug from 'debug' - -import config from '../../../config' -import sh from '../sh' - -const { paths } = config - -const SCAN_RESULTS_DIR_NAME = '.vuln-scans' -const SCAN_RESULTS_DIR_PATH = paths.base(SCAN_RESULTS_DIR_NAME) - -const log = message => debug.log(message) -log.success = message => debug.log(`✔ ${message}`) - -const ensureDirExists = directoryPath => { - if (!fs.existsSync(directoryPath)) { - sh(`mkdir -p ${directoryPath}`) - } -} - -const getTodayScanFilePath = () => { - const now = new Date() - - const year = now.getUTCFullYear() - const month = now.getUTCMonth() + 1 - const date = now.getUTCDate() - - const fileName = `snyk-scanned-${year}-${month}-${date}` - - return path.resolve(SCAN_RESULTS_DIR_PATH, fileName) -} - -const recentlyChecked = () => { - const recentCheckFilePath = getTodayScanFilePath() - return fs.existsSync(recentCheckFilePath) -} - -const registerRecentSucessfulScan = async () => { - ensureDirExists(SCAN_RESULTS_DIR_PATH) - - const recentScanFilePath = getTodayScanFilePath() - await sh(`touch ${recentScanFilePath}`) -} - -/** - * The following strategy is used to perform vulnerabilites scan - * - check if there is marker of recent sucessful scan - * - if this marker exists, skip checks - * - if there is no marker, perform check - * - if check is successful, create successful check marker - */ -task('test:vulns', async () => { - if (recentlyChecked()) { - log.success('Vulnerabilities check was already performed recently, skipping..') - return - } - - log('Scanning dependency packages for vulnerabilities..') - await sh(`yarn snyk test`) - log.success('Vulnerability scan is successfully passed.') - - registerRecentSucessfulScan() -}) diff --git a/build/screener/screener.config.js b/build/screener/screener.config.js index f6060e19a8..84b1451210 100644 --- a/build/screener/screener.config.js +++ b/build/screener/screener.config.js @@ -36,6 +36,12 @@ module.exports = { ...(process.env.CI && { baseBranch: 'master', - failureExitCode: 0, + // Disable exit code to fail in Github Actions + // failureExitCode: 0, + // GITHUB_REF can be: + // - refs/heads/feature-branch-1 for "push" + // - refs/pull/2040/merge for "pull_request" + branch: process.env.GITHUB_REF.split('/')[2], + commit: process.env.GITHUB_SHA, }), } diff --git a/config.ts b/config.ts index c6e0026e67..69a5aafbef 100644 --- a/config.ts +++ b/config.ts @@ -9,7 +9,7 @@ const __DEV__ = env === 'development' const __NOW__ = !!process.env.NOW const __PERF__ = !!process.env.PERF const __PROD__ = env === 'production' -const __BASENAME__ = __PROD__ && !__NOW__ ? '/react/' : '/' +const __BASENAME__ = __PROD__ && !__NOW__ ? '/fluent-ui-react/' : '/' const __SKIP_ERRORS__ = !!process.env.SKIP_ERRORS diff --git a/docs/src/404.html b/docs/src/404.html index a35a5e6f9b..677a5258f0 100644 --- a/docs/src/404.html +++ b/docs/src/404.html @@ -2,7 +2,7 @@ - + diff --git a/docs/src/app.tsx b/docs/src/app.tsx index 98daa78545..8a13feb4e5 100644 --- a/docs/src/app.tsx +++ b/docs/src/app.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { hot } from 'react-hot-loader/root' -import { Provider, themes } from '@stardust-ui/react' +import { Provider, Debug, themes } from '@stardust-ui/react' import { mergeThemes } from 'src/lib' import { ThemeContext, ThemeContextData, themeContextDefaults } from './context/ThemeContext' @@ -40,7 +40,10 @@ class App extends React.Component { })} > - +
+ + +
diff --git a/docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx b/docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx index 7a527feb53..63e1a31336 100644 --- a/docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx +++ b/docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx @@ -22,6 +22,7 @@ type ComponentControlsProps = { showRtl: boolean showVariables: boolean showTransparent: boolean + toolbarAriaLabel?: string } const controlsTheme: ThemeInput = { @@ -60,6 +61,7 @@ const ComponentControls: React.FC = props => { onShowRtl, onShowTransparent, onShowVariables, + toolbarAriaLabel, ...rest } = props @@ -70,6 +72,7 @@ const ComponentControls: React.FC = props => { fluid pills accessibility={menuAsToolbarBehavior} + aria-label={toolbarAriaLabel || null} items={[ { key: 'show-code', diff --git a/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx b/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx index b1ff83dc6f..87af4b96e7 100644 --- a/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx +++ b/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx @@ -33,9 +33,12 @@ export interface ComponentExampleProps extends RouteComponentProps, ComponentSourceManagerRenderProps, ExampleContextValue { + error: Error | null + onError: (error: Error | null) => void title: React.ReactNode description?: React.ReactNode examplePath: string + toolbarAriaLabel?: string } interface ComponentExampleState { @@ -217,8 +220,6 @@ class ComponentExample extends React.Component this.props.examplePath.split('/')[1] - handleCodeApiChange = apiType => () => { this.props.handleCodeAPIChange(apiType) } @@ -431,9 +432,12 @@ class ComponentExample extends React.Component {children}} - {showCode || wasCodeChanged ? ( - - {({ element, error }) => ( - <> - - - - {element} - - - - - - - {showCode && ( -
- {this.renderSourceCode()} - {error && ( -
-                            {error.toString()}
-                          
- )} -
- )} - + + + + {showCode || wasCodeChanged ? ( + + ) : ( + React.createElement(component) + )} + + + + + + {showCode && ( +
+ {this.renderSourceCode()} + {error && ( +
+                    {error.toString()}
+                  
)} - - ) : ( - <> - - {React.createElement(component)} - - - +
)} {showVariables && ( @@ -580,14 +563,26 @@ class ComponentExample extends React.Component ( - - {exampleProps => ( - - {codeProps => } - - )} - -) +const ComponentExampleWithTheme = props => { + const exampleProps = React.useContext(ExampleContext) + + // This must be under ComponentExample: + // React handles setState() in hooks and classes differently: it performs strict equal check in hooks + const [error, setError] = React.useState(null) + + return ( + + {codeProps => ( + + )} + + ) +} export default withRouter(ComponentExampleWithTheme) diff --git a/docs/src/components/ComponentDoc/ComponentExample/ComponentExampleVariables.tsx b/docs/src/components/ComponentDoc/ComponentExample/ComponentExampleVariables.tsx index b3c29db88a..93cf30901b 100644 --- a/docs/src/components/ComponentDoc/ComponentExample/ComponentExampleVariables.tsx +++ b/docs/src/components/ComponentDoc/ComponentExample/ComponentExampleVariables.tsx @@ -1,11 +1,11 @@ import { - Checkbox, Grid, Header, Segment, ProviderContextPrepared, ThemeComponentVariablesPrepared, } from '@stardust-ui/react' +import { callable } from '@stardust-ui/react-bindings' import * as _ from 'lodash' import * as React from 'react' // @ts-ignore @@ -13,7 +13,6 @@ import { ThemeContext } from 'react-fela' import ComponentExampleVariable, { ComponentExampleVariableProps } from './ComponentExampleVariable' import { mergeThemeVariables } from 'src/lib/mergeThemes' -import callable from 'src/lib/callable' type ComponentExampleVariablesProps = { onChange: ComponentExampleVariableProps['onChange'] @@ -37,7 +36,7 @@ const ComponentExampleVariables: React.FunctionComponent< const { onChange, overriddenVariables, usedVariables } = props const { theme } = React.useContext(ThemeContext) - const [hideUnused, setHideUnused] = React.useState(true) + const [hideUnused] = React.useState(true) const componentVariables: ThemeComponentVariablesPrepared = _.pickBy( mergeThemeVariables(theme.componentVariables, overriddenVariables), @@ -68,12 +67,13 @@ const ComponentExampleVariables: React.FunctionComponent< return (
- setHideUnused(!data.checked)} - styles={{ float: 'right', top: '1.25rem', right: '1.25rem' }} - /> + {/* Temporarily disabled as the functionality in useEnhancedRenderer is broken */} + {/* setHideUnused(!data.checked)} */} + {/* styles={{ float: 'right', top: '1.25rem', right: '1.25rem' }} */} + {/* /> */} {_.map(filteredVariables, (componentVariables, componentName) => { const groupedVariables: Record = _.groupBy( diff --git a/docs/src/components/ComponentPlayground/componentGenerators.ts b/docs/src/components/ComponentPlayground/componentGenerators.ts index e1c87435e5..84106b0cd1 100644 --- a/docs/src/components/ComponentPlayground/componentGenerators.ts +++ b/docs/src/components/ComponentPlayground/componentGenerators.ts @@ -2,6 +2,7 @@ import { useSelectKnob, useStringKnob } from '@stardust-ui/docs-components' import { AvatarProps, BoxProps, + DialogProps, DividerProps, EmbedProps, IconProps, @@ -27,6 +28,10 @@ export const Box: KnobComponentGenerators = { children: () => null, } +export const Dialog: KnobComponentGenerators = { + footer: () => null, +} + export const Divider: KnobComponentGenerators = { // Workaround for `Divider` component that supports size in different way size: number, diff --git a/docs/src/components/ComponentPlayground/createHookGenerator.ts b/docs/src/components/ComponentPlayground/createHookGenerator.ts index bee3878af8..97dace5273 100644 --- a/docs/src/components/ComponentPlayground/createHookGenerator.ts +++ b/docs/src/components/ComponentPlayground/createHookGenerator.ts @@ -37,6 +37,15 @@ const createHookGenerator = (options: KnobGeneratorOptions): null | KnobDefiniti // TODO: add support for AutoControlled props const Component = Stardust[componentInfo.displayName] + + if (process.env.NODE_ENV !== 'production') { + if (!Component) { + throw new Error( + `Cannot find an export for "${componentInfo.displayName}", please check that it is exported from "@stardust-ui/react"`, + ) + } + } + const { autoControlledProps = [] } = Component if (autoControlledProps.indexOf(propDef.name) !== -1) { diff --git a/docs/src/components/DocsLayout.tsx b/docs/src/components/DocsLayout.tsx index cd950400f4..4d9f57341a 100644 --- a/docs/src/components/DocsLayout.tsx +++ b/docs/src/components/DocsLayout.tsx @@ -1,4 +1,4 @@ -import { Provider, themes, pxToRem } from '@stardust-ui/react' +import { Provider, themes, pxToRem, createTheme } from '@stardust-ui/react' import AnchorJS from 'anchor-js' import * as PropTypes from 'prop-types' import * as React from 'react' @@ -93,36 +93,42 @@ class DocsLayout extends React.Component { return ( <> ({ - ...(!p.items && treeItemStyle), - ...(p.items && treeSectionStyle), - }), - }, - HierarchicalTreeTitle: { - root: { - display: 'block', - width: '100%', + componentStyles: { + HierarchicalTreeItem: { + root: ({ variables: v, props: p }) => ({ + ...(!p.items && treeItemStyle), + ...(p.items && treeSectionStyle), + }), + }, + HierarchicalTreeTitle: { + root: { + display: 'block', + width: '100%', + }, + }, }, }, - }, - })} + 'DocsLayout', + ), + )} > diff --git a/docs/src/components/Editor/Editor.tsx b/docs/src/components/Editor/Editor.tsx index 3422004762..e9cd90ecc8 100644 --- a/docs/src/components/Editor/Editor.tsx +++ b/docs/src/components/Editor/Editor.tsx @@ -53,7 +53,7 @@ export interface EditorProps extends AceEditorProps { export const EDITOR_BACKGROUND_COLOR = '#2D2D2D' export const EDITOR_GUTTER_COLOR = '#272727' -class Editor extends React.Component { +class Editor extends React.PureComponent { editorRef = React.createRef() name = `docs-editor-${_.uniqueId()}` diff --git a/docs/src/components/ExternalExampleLayout.tsx b/docs/src/components/ExternalExampleLayout.tsx index 3b0f9ed0d8..e79aa302bc 100644 --- a/docs/src/components/ExternalExampleLayout.tsx +++ b/docs/src/components/ExternalExampleLayout.tsx @@ -23,69 +23,44 @@ type ExternalExampleLayoutProps = { }> } -type ExternalExampleLayoutState = { - renderId: number - themeName: string -} - -class ExternalExampleLayout extends React.Component< - ExternalExampleLayoutProps, - ExternalExampleLayoutState -> { - state = { - renderId: 0, - themeName: undefined, - } - - componentDidMount() { - window.resetExternalLayout = () => - this.setState(prevState => ({ renderId: prevState.renderId + 1 })) - - window.switchTheme = (themeName: string) => this.setState({ themeName }) - } +const ExternalExampleLayout: React.FC = props => { + const { exampleName, rtl } = props.match.params - render() { - const { exampleName } = this.props.match.params - const exampleFilename = exampleKebabNameToSourceFilename(exampleName) + const [error, setError] = React.useState(null) + const [renderId, setRenderId] = React.useState(0) + const [themeName, setThemeName] = React.useState() - const examplePath = _.find( - examplePaths, - path => exampleFilename === parseExamplePath(path).exampleName, - ) + React.useLayoutEffect(() => { + window.resetExternalLayout = () => setRenderId(prevNumber => prevNumber + 1) + window.switchTheme = setThemeName + }, []) - if (!examplePath) return + const exampleFilename = exampleKebabNameToSourceFilename(exampleName) + const examplePath = _.find( + examplePaths, + path => exampleFilename === parseExamplePath(path).exampleName, + ) - const exampleSource: ExampleSource = exampleSourcesContext(examplePath) + if (!examplePath) return - const { themeName } = this.state - const theme = (themeName && themes[themeName]) || {} + const exampleSource: ExampleSource = exampleSourcesContext(examplePath) + const theme = (themeName && themes[themeName]) || {} - return ( - - - - {({ element, error }) => ( - <> - {element} - {/* This block allows to see issues with examples as visual regressions. */} - {error &&
{error.toString()}
} - - )} -
-
-
- ) - } + return ( + + + + {/* This block allows to see issues with examples as visual regressions. */} + {error &&
{error.toString()}
} +
+
+ ) } export default ExternalExampleLayout diff --git a/docs/src/components/Sidebar/Sidebar.tsx b/docs/src/components/Sidebar/Sidebar.tsx index 6f60ea5b0f..dea7e39656 100644 --- a/docs/src/components/Sidebar/Sidebar.tsx +++ b/docs/src/components/Sidebar/Sidebar.tsx @@ -28,6 +28,8 @@ const pkg = require('../../../../packages/react/package.json') const componentMenu: ComponentMenuItem[] = require('docs/src/componentMenu') const behaviorMenu: ComponentMenuItem[] = require('docs/src/behaviorMenu') +const componentsBlackList = ['Debug', 'Design'] + class Sidebar extends React.Component { static propTypes = { match: PropTypes.object.isRequired, @@ -258,7 +260,7 @@ class Sidebar extends React.Component { const treeItemsByType = _.map(constants.typeOrder, nextType => { const items = _.chain([...componentMenu, ...behaviorMenu]) .filter(({ type }) => type === nextType) - .filter(({ displayName }) => displayName !== 'Design') + .filter(({ displayName }) => !_.includes(componentsBlackList, displayName)) .map(info => ({ key: info.displayName.concat(nextType), title: { content: info.displayName, as: NavLink, to: getComponentPathname(info) }, @@ -279,11 +281,21 @@ class Sidebar extends React.Component { title: { content: 'Chat Messages', as: NavLink, to: '/prototype-chat-messages' }, public: true, }, + { + key: 'customscrollbar', + title: { content: 'Custom Scrollbar', as: NavLink, to: '/prototype-custom-scrollbar' }, + public: true, + }, { key: 'customtoolbar', title: { content: 'Custom Styled Toolbar', as: NavLink, to: '/prototype-custom-toolbar' }, public: true, }, + { + key: 'editor-toolbar', + title: { content: 'Editor Toolbar', as: NavLink, to: '/prototype-editor-toolbar' }, + public: true, + }, { key: 'dropdowns', title: { content: 'Dropdowns', as: NavLink, to: '/prototype-dropdowns' }, diff --git a/docs/src/components/VariableResolver/useEnhancedRenderer.ts b/docs/src/components/VariableResolver/useEnhancedRenderer.ts index 09b0f35b98..a3a8adea58 100644 --- a/docs/src/components/VariableResolver/useEnhancedRenderer.ts +++ b/docs/src/components/VariableResolver/useEnhancedRenderer.ts @@ -1,10 +1,9 @@ +import { callable } from '@stardust-ui/react-bindings' import { ComponentSlotStylesPrepared, Renderer } from '@stardust-ui/react' import flat from 'flat' import * as _ from 'lodash' import * as React from 'react' -import { callable } from 'src/lib' - export type UsedVariables = Record> const variableRegex = /<>/g diff --git a/docs/src/examples/components/Alert/Rtl/AlertExampleChildren.rtl.tsx b/docs/src/examples/components/Alert/Rtl/AlertExampleChildren.rtl.tsx new file mode 100644 index 0000000000..25b9ca116e --- /dev/null +++ b/docs/src/examples/components/Alert/Rtl/AlertExampleChildren.rtl.tsx @@ -0,0 +1,6 @@ +import * as React from 'react' +import { Alert } from '@stardust-ui/react' + +const AlertExampleChildrenRtl = () => مرحبا العالم + +export default AlertExampleChildrenRtl diff --git a/docs/src/examples/components/Alert/Rtl/AlertExampleDismissAction.rtl.tsx b/docs/src/examples/components/Alert/Rtl/AlertExampleDismissAction.rtl.tsx new file mode 100644 index 0000000000..36f917b1bd --- /dev/null +++ b/docs/src/examples/components/Alert/Rtl/AlertExampleDismissAction.rtl.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import { Alert } from '@stardust-ui/react' + +const AlertExampleDismissActionRtl = () => ( + +) + +export default AlertExampleDismissActionRtl diff --git a/docs/src/examples/components/Alert/Rtl/index.tsx b/docs/src/examples/components/Alert/Rtl/index.tsx index 56b1579a6f..b1ac4a1e2c 100644 --- a/docs/src/examples/components/Alert/Rtl/index.tsx +++ b/docs/src/examples/components/Alert/Rtl/index.tsx @@ -6,6 +6,8 @@ import NonPublicSection from 'docs/src/components/ComponentDoc/NonPublicSection' const Rtl = () => ( + + ) diff --git a/docs/src/examples/components/Dialog/Content/DialogExampleFooter.shorthand.steps.ts b/docs/src/examples/components/Dialog/Content/DialogExampleFooter.shorthand.steps.ts new file mode 100644 index 0000000000..26d2fe6799 --- /dev/null +++ b/docs/src/examples/components/Dialog/Content/DialogExampleFooter.shorthand.steps.ts @@ -0,0 +1,7 @@ +import getScreenerSteps from '../commonScreenerSteps' + +const config: ScreenerTestsConfig = { + steps: getScreenerSteps(), +} + +export default config diff --git a/docs/src/examples/components/Dialog/Content/DialogExampleFooter.shorthand.tsx b/docs/src/examples/components/Dialog/Content/DialogExampleFooter.shorthand.tsx new file mode 100644 index 0000000000..1e18adbdf1 --- /dev/null +++ b/docs/src/examples/components/Dialog/Content/DialogExampleFooter.shorthand.tsx @@ -0,0 +1,28 @@ +import { Button, Dialog, Flex, Text } from '@stardust-ui/react' +import * as React from 'react' + +const DialogExampleFooter: React.FC = () => ( + } + footer={render => + render(null, (Component, props) => { + const { styles, ...rest } = props + + return ( + + + + + + + ) + }) + } + /> +) + +export default DialogExampleFooter diff --git a/docs/src/examples/components/Dialog/Content/DialogExampleHeaderAction.shorthand.tsx b/docs/src/examples/components/Dialog/Content/DialogExampleHeaderAction.shorthand.tsx index 1f4bcdf389..f21443b5ff 100644 --- a/docs/src/examples/components/Dialog/Content/DialogExampleHeaderAction.shorthand.tsx +++ b/docs/src/examples/components/Dialog/Content/DialogExampleHeaderAction.shorthand.tsx @@ -7,7 +7,9 @@ const DialogExampleHeaderAction: React.FC = () => { return ( setOpen(open)} + onOpen={() => setOpen(true)} + onCancel={() => setOpen(false)} + onConfirm={() => setOpen(false)} confirmButton="Confirm" content="Are you sure you want to confirm this action?" header="Action confirmation" diff --git a/docs/src/examples/components/Dialog/Content/index.tsx b/docs/src/examples/components/Dialog/Content/index.tsx index f983a28763..a3168a4b2a 100644 --- a/docs/src/examples/components/Dialog/Content/index.tsx +++ b/docs/src/examples/components/Dialog/Content/index.tsx @@ -15,6 +15,11 @@ const DialogContentExamples = () => ( description="A dialog can contain an action in the header." examplePath="components/Dialog/Content/DialogExampleHeaderAction" /> + ) diff --git a/docs/src/examples/components/Dialog/index.tsx b/docs/src/examples/components/Dialog/index.tsx index 47c0535868..4e8c1eb86f 100644 --- a/docs/src/examples/components/Dialog/index.tsx +++ b/docs/src/examples/components/Dialog/index.tsx @@ -4,12 +4,14 @@ import Content from './Content' import Types from './Types' import Rtl from './Rtl' import Variations from './Variations' +import Usage from './Usage' const DialogExamples = () => ( <> + ) diff --git a/docs/src/examples/components/HierarchicalTree/Types/HierarchicalTreeTitleCustomizationExample.shorthand.tsx b/docs/src/examples/components/HierarchicalTree/Types/HierarchicalTreeTitleCustomizationExample.shorthand.tsx index dbec2a16cc..bbe985b1e2 100644 --- a/docs/src/examples/components/HierarchicalTree/Types/HierarchicalTreeTitleCustomizationExample.shorthand.tsx +++ b/docs/src/examples/components/HierarchicalTree/Types/HierarchicalTreeTitleCustomizationExample.shorthand.tsx @@ -38,7 +38,7 @@ const titleRenderer = (Component, { content, open, hasSubtree, ...restProps }) = ) const TreeTitleCustomizationExample = () => ( - + ) export default TreeTitleCustomizationExample diff --git a/docs/src/examples/components/Provider/Performance/ProviderMergeThemes.perf.tsx b/docs/src/examples/components/Provider/Performance/ProviderMergeThemes.perf.tsx new file mode 100644 index 0000000000..58eace2796 --- /dev/null +++ b/docs/src/examples/components/Provider/Performance/ProviderMergeThemes.perf.tsx @@ -0,0 +1,43 @@ +import * as React from 'react' +import { mergeThemes, callable, ComponentStyleFunctionParam, themes } from '@stardust-ui/react' +import * as _ from 'lodash' + +/** + * Not a real performance test, just a temporary POC + */ +const providerMergeThemesPerf = () => { + const merged = mergeThemes(..._.times(100, n => themes.teams)) + const resolvedStyles = _.mapValues(merged.componentStyles, (componentStyle, componentName) => { + const compVariables = _.get(merged.componentVariables, componentName, callable({}))( + merged.siteVariables, + ) + const styleParam: ComponentStyleFunctionParam = { + displayName: componentName, + props: {}, + variables: compVariables, + theme: merged, + rtl: false, + disableAnimations: false, + } + return _.mapValues(componentStyle, (partStyle, partName) => { + if (partName === '_debug') { + // TODO: fix in code, happens only with mergeThemes(singleTheme) + return undefined + } + if (typeof partStyle !== 'function') { + console.log(componentName, partStyle, partName) + } + return partStyle(styleParam) + }) + }) + + return resolvedStyles +} + +const MergeThemesPerf = () => { + const resolvedStyles = providerMergeThemesPerf() + delete resolvedStyles.Button.root._debug + return
{JSON.stringify(resolvedStyles.Button.root, null, 2)}
+} + +export default MergeThemesPerf diff --git a/docs/src/examples/components/Provider/Performance/index.tsx b/docs/src/examples/components/Provider/Performance/index.tsx new file mode 100644 index 0000000000..89119eb6e7 --- /dev/null +++ b/docs/src/examples/components/Provider/Performance/index.tsx @@ -0,0 +1,16 @@ +import * as React from 'react' + +import ComponentPerfExample from 'docs/src/components/ComponentDoc/ComponentPerfExample' +import NonPublicSection from 'docs/src/components/ComponentDoc/NonPublicSection' + +const Performance = () => ( + + + +) + +export default Performance diff --git a/docs/src/examples/components/Provider/index.tsx b/docs/src/examples/components/Provider/index.tsx index 21105833cf..2f2aa224bc 100644 --- a/docs/src/examples/components/Provider/index.tsx +++ b/docs/src/examples/components/Provider/index.tsx @@ -2,11 +2,13 @@ import * as React from 'react' import Types from './Types' import Usage from './Usage' +import Performance from './Performance' const ProviderExamples = () => ( <> + ) diff --git a/docs/src/examples/components/SplitButton/Slots/SplitButtonIconAndContentExample.shorthand.tsx b/docs/src/examples/components/SplitButton/Slots/SplitButtonIconAndContentExample.shorthand.tsx index e467e46ca8..c62f4110ba 100644 --- a/docs/src/examples/components/SplitButton/Slots/SplitButtonIconAndContentExample.shorthand.tsx +++ b/docs/src/examples/components/SplitButton/Slots/SplitButtonIconAndContentExample.shorthand.tsx @@ -26,7 +26,23 @@ const items = [ ] const SplitButtonIconAndContentExampleShorthand = () => ( - + <> + alert('button was clicked')} + toggleButton={{ 'aria-label': 'more options' }} + /> + + ) export default SplitButtonIconAndContentExampleShorthand diff --git a/docs/src/examples/components/SplitButton/Slots/SplitButtonToggleButtonExample.shorthand.tsx b/docs/src/examples/components/SplitButton/Slots/SplitButtonToggleButtonExample.shorthand.tsx index b06d465d66..ece31db5b9 100644 --- a/docs/src/examples/components/SplitButton/Slots/SplitButtonToggleButtonExample.shorthand.tsx +++ b/docs/src/examples/components/SplitButton/Slots/SplitButtonToggleButtonExample.shorthand.tsx @@ -6,15 +6,28 @@ const SplitButtonExampleToggleButtonShorthand = () => { const [open, setOpen] = useBooleanKnob({ name: 'open' }) return ( - setOpen(open)} - /> + <> + setOpen(open)} + onMainButtonClick={() => alert('button was clicked')} + /> + + ) } diff --git a/docs/src/examples/components/SplitButton/Types/SplitButtonExample.shorthand.tsx b/docs/src/examples/components/SplitButton/Types/SplitButtonExample.shorthand.tsx index 600b1201a2..332cc631f2 100644 --- a/docs/src/examples/components/SplitButton/Types/SplitButtonExample.shorthand.tsx +++ b/docs/src/examples/components/SplitButton/Types/SplitButtonExample.shorthand.tsx @@ -2,13 +2,24 @@ import * as React from 'react' import { SplitButton } from '@stardust-ui/react' const SplitButtonExampleShorthand = () => ( - + <> + alert('button was clicked')} + /> + + ) export default SplitButtonExampleShorthand diff --git a/docs/src/examples/components/SplitButton/Types/SplitButtonExamplePrimary.shorthand.tsx b/docs/src/examples/components/SplitButton/Types/SplitButtonExamplePrimary.shorthand.tsx index 30097d1b6f..ca6159f095 100644 --- a/docs/src/examples/components/SplitButton/Types/SplitButtonExamplePrimary.shorthand.tsx +++ b/docs/src/examples/components/SplitButton/Types/SplitButtonExamplePrimary.shorthand.tsx @@ -2,14 +2,25 @@ import * as React from 'react' import { SplitButton } from '@stardust-ui/react' const SplitButtonExamplePrimaryShorthand = () => ( - + <> + alert('button was clicked')} + /> + + ) export default SplitButtonExamplePrimaryShorthand diff --git a/docs/src/examples/components/Toolbar/BestPractices/ToolbarBestPractices.tsx b/docs/src/examples/components/Toolbar/BestPractices/ToolbarBestPractices.tsx new file mode 100644 index 0000000000..60f1663494 --- /dev/null +++ b/docs/src/examples/components/Toolbar/BestPractices/ToolbarBestPractices.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' +import { Text } from '@stardust-ui/react' +import { link } from '../../../../utils/helpers' +import ComponentBestPractices from 'docs/src/components/ComponentBestPractices' + +const doList = [ + + Label each toolbar when the application contains more than one toolbar (using `aria-label` or + `aria-labelledby` props). Refer to{' '} + {link('toolbar(role)', 'https://www.w3.org/WAI/PF/aria/roles#toolbar')} for details. + , +] + +const ToolbarBestPractices: React.FunctionComponent<{}> = () => { + return +} + +export default ToolbarBestPractices diff --git a/docs/src/examples/components/Toolbar/Content/ToolbarExampleCustomContent.shorthand.tsx b/docs/src/examples/components/Toolbar/Content/ToolbarExampleCustomContent.shorthand.tsx index 3268c9d4fb..4d486f5a5d 100644 --- a/docs/src/examples/components/Toolbar/Content/ToolbarExampleCustomContent.shorthand.tsx +++ b/docs/src/examples/components/Toolbar/Content/ToolbarExampleCustomContent.shorthand.tsx @@ -3,6 +3,7 @@ import { Button, Text, Toolbar } from '@stardust-ui/react' const ToolbarExampleCustomContentShorthand = () => ( { return ( { return ( { return ( + builder + .click(`.${ToolbarItem.className}:nth-child(1)`) + .snapshot('Shows menu') + .keys(`.${ToolbarMenuItem.className}:nth-child(1)`, keys.rightArrow) + .snapshot('Opens submenu'), + ], +} + +export default config diff --git a/docs/src/examples/components/Toolbar/Content/ToolbarExampleMenuWithSubmenu.shorthand.tsx b/docs/src/examples/components/Toolbar/Content/ToolbarExampleMenuWithSubmenu.shorthand.tsx new file mode 100644 index 0000000000..33eec45fc8 --- /dev/null +++ b/docs/src/examples/components/Toolbar/Content/ToolbarExampleMenuWithSubmenu.shorthand.tsx @@ -0,0 +1,46 @@ +import { createCallbackLogFormatter } from '@stardust-ui/code-sandbox' +import { useLogKnob } from '@stardust-ui/docs-components' +import { Toolbar } from '@stardust-ui/react' +import * as React from 'react' + +const ToolbarExampleMenuWithSubmenuShorthand = () => { + const [menuOpen, setMenuOpen] = React.useState(false) + + const onMenuOpenChange = useLogKnob( + 'onMenuOpenChange', + (e, { menuOpen }) => setMenuOpen(menuOpen), + createCallbackLogFormatter(['menuOpen']), + ) + + return ( + + ) +} + +export default ToolbarExampleMenuWithSubmenuShorthand diff --git a/docs/src/examples/components/Toolbar/Content/ToolbarExampleOverflow.shorthand.tsx b/docs/src/examples/components/Toolbar/Content/ToolbarExampleOverflow.shorthand.tsx index 36f49fbb3a..c10edd5cea 100644 --- a/docs/src/examples/components/Toolbar/Content/ToolbarExampleOverflow.shorthand.tsx +++ b/docs/src/examples/components/Toolbar/Content/ToolbarExampleOverflow.shorthand.tsx @@ -5,57 +5,27 @@ import { Toolbar } from '@stardust-ui/react' const ToolbarExampleOverflow = () => { const icons = ['bold', 'italic', 'underline'] - const itemData = [ - ..._.times(40, i => ({ - key: `a${i}`, - content: `${icons[i % icons.length]} #${i}`, - icon: icons[i % icons.length], - })), - ] + const itemData = _.times(40, i => ({ + key: `b${i}`, + content: `${icons[i % icons.length]} #${i}`, + icon: icons[i % icons.length], + })) - const toolbarItems = itemData.map(item => ({ ...item, content: undefined })) - const overflowItems = itemData - - const overflowMenuKey = 'overflow-menu' - - const [overflowMenuOpen, setOverflowMenuOpen] = React.useState(false) + const toolbarItems = itemData.map(item => { + return { ...item, content: undefined } + }) + const [overflowOpen, setOverflowOpen] = React.useState(false) return ( { - let numberOfFits = measures.findIndex(measure => !measure.leftFits || !measure.rightFits) - - // if the first item which does not fit is the overflow menu, we need to remove one more regular item - if (numberOfFits < renderedItems.length) { - const firstCutItem = renderedItems[numberOfFits] - const firstCutItemIsOverflowMenu = - _.isObject(firstCutItem) && firstCutItem['key'] === overflowMenuKey - if (firstCutItemIsOverflowMenu) { - --numberOfFits - } - } - - // if there is nothing more to hide, stop the reduce - if (numberOfFits < 0) { - return null - } - - // console.log(`${numberOfFits}/${toolbarItems.length} fit`) - - return [ - ...toolbarItems.slice(0, numberOfFits), - { - key: overflowMenuKey, - icon: 'more', - menu: overflowItems.slice(numberOfFits), - menuOpen: overflowMenuOpen, - onMenuOpenChange: (_, { menuOpen }) => { - setOverflowMenuOpen(menuOpen) - }, - }, - ] + overflow + overflowOpen={overflowOpen} + onOverflowOpenChange={(e, { overflowOpen }) => { + setOverflowOpen(overflowOpen) }} + getOverflowItems={startIndex => itemData.slice(startIndex)} /> ) } diff --git a/docs/src/examples/components/Toolbar/Content/ToolbarExamplePopup.shorthand.tsx b/docs/src/examples/components/Toolbar/Content/ToolbarExamplePopup.shorthand.tsx index 0cc6089a42..6eac725372 100644 --- a/docs/src/examples/components/Toolbar/Content/ToolbarExamplePopup.shorthand.tsx +++ b/docs/src/examples/components/Toolbar/Content/ToolbarExamplePopup.shorthand.tsx @@ -31,6 +31,7 @@ const ToolbarExamplePopupShorthand = () => { const [fontColorActive, setFontColorActive] = React.useState(false) return ( { const [toDoListActive, setToDoListActive] = React.useState(false) return ( ( Toolbar item can open a popup. See Popup component for @@ -19,26 +20,36 @@ const Content = () => ( /> + @@ -53,6 +64,7 @@ const Content = () => ( diff --git a/docs/src/examples/components/Toolbar/Performance/CustomToolbar.perf.tsx b/docs/src/examples/components/Toolbar/Performance/CustomToolbar.perf.tsx new file mode 100644 index 0000000000..8ab5acefdd --- /dev/null +++ b/docs/src/examples/components/Toolbar/Performance/CustomToolbar.perf.tsx @@ -0,0 +1,564 @@ +import * as _ from 'lodash' +import * as React from 'react' +import { + Button, + Text, + Toolbar, + ShorthandCollection, + Status, + ToolbarItemShorthandKinds, + SizeValue, + ShorthandValue, + ComponentStyleFunctionParam, + ThemeInput, + ToolbarProps, + ToolbarItemProps, + ToolbarCustomItemProps, + ToolbarDividerProps, + StatusProps, + pxToRem, + Provider, + themes, + mergeThemes, + Tooltip, + tooltipAsLabelBehavior, +} from '@stardust-ui/react' + +type CustomStatusVariables = { + isRecordingIndicator?: boolean + + recordingIndicatorBorderColor?: string + recordingIndicatorBorderStyle?: string + recordingIndicatorBorderWidth?: string +} + +type CustomToolbarVariables = { + isCt?: boolean + + isCtItemDanger?: boolean + isCtItemPrimary?: boolean + isCtItemIconNoFill?: boolean + isCtItemIndicator?: boolean + isCtItemWithNotification?: boolean + + ctBorderRadius: string + ctBorderStyle: string + ctBorderWidth: string + ctHeight: string + + ctItemBackground: string + ctItemBackgroundHover: string + ctItemBorderColorFocus: string + ctItemColor: string + ctItemColorFocus: string + ctItemColorHover: string + + ctItemActiveColor: string + ctItemActiveBackground: string + ctItemActiveBackgroundOverlay: string + + ctItemDangerBackground: string + ctItemDangerColorHover: string + ctItemDangerBackgroundHover: string + + ctItemIndicatorPadding: string + + ctItemNotificationBackgroundColor: string + ctItemNotificationSize: string + + ctItemPrimaryBackground: string + ctItemPrimaryBackgroundHover: string + ctItemPrimaryColorHover: string +} + +const darkThemeOverrides: ThemeInput = { + componentVariables: { + Status: (siteVars): CustomStatusVariables => ({ + recordingIndicatorBorderColor: siteVars.colors.white, + recordingIndicatorBorderStyle: 'solid', + recordingIndicatorBorderWidth: '2px', + }), + + Toolbar: (siteVars): CustomToolbarVariables => ({ + ctBorderRadius: '4px', + ctBorderStyle: 'solid', + ctBorderWidth: '2px', + ctHeight: '4rem', + + ctItemBackground: siteVars.colorScheme.default.background1, + ctItemBackgroundHover: siteVars.colorScheme.brand.backgroundHover1, + ctItemBorderColorFocus: siteVars.colorScheme.default.borderFocus, + ctItemColor: siteVars.colorScheme.default.foreground, + ctItemColorFocus: siteVars.colorScheme.default.foregroundFocus, + ctItemColorHover: siteVars.colorScheme.default.foregroundHover, + + ctItemActiveBackground: siteVars.colorScheme.default.backgroundActive1, + // FIXME: use variables for colors! + ctItemActiveBackgroundOverlay: + 'linear-gradient(90deg,rgba(60,62,93,.6),rgba(60,62,93,0) 33%),linear-gradient(135deg,rgba(60,62,93,.6) 33%,rgba(60,62,93,0) 70%),linear-gradient(180deg,rgba(60,62,93,.6) 70%,rgba(60,62,93,0) 94%),linear-gradient(225deg,rgba(60,62,93,.6) 33%,rgba(60,62,93,0) 73%),linear-gradient(270deg,rgba(60,62,93,.6),rgba(60,62,93,0) 33%),linear-gradient(0deg,rgba(98,100,167,.75) 6%,rgba(98,100,167,0) 70%)', + ctItemActiveColor: siteVars.colorScheme.default.foregroundActive1, + + ctItemDangerBackground: siteVars.colorScheme.red.background2, + ctItemDangerBackgroundHover: siteVars.colorScheme.red.backgroundHover, + ctItemDangerColorHover: siteVars.colorScheme.red.foregroundHover, + + ctItemIndicatorPadding: pxToRem(8), + + ctItemNotificationBackgroundColor: siteVars.colors.red[400], + ctItemNotificationSize: pxToRem(8), + + ctItemPrimaryBackground: siteVars.colorScheme.default.background3, + ctItemPrimaryBackgroundHover: siteVars.colorScheme.brand.backgroundHover1, + ctItemPrimaryColorHover: siteVars.colorScheme.brand.foregroundHover1, + }), + }, + + componentStyles: { + Status: { + root: ({ + variables: v, + }: ComponentStyleFunctionParam) => ({ + ...(v.isRecordingIndicator && { + boxSizing: 'content-box', + borderColor: v.recordingIndicatorBorderColor, + borderStyle: v.recordingIndicatorBorderStyle, + borderWidth: v.recordingIndicatorBorderWidth, + }), + }), + }, + Toolbar: { + root: ({ + variables: v, + }: ComponentStyleFunctionParam) => ({ + ...(v.isCt && { + borderRadius: v.ctBorderRadius, + height: v.ctHeight, + overflow: 'hidden', + }), + }), + }, + + ToolbarCustomItem: { + root: ({ + props: p, + variables: v, + }: ComponentStyleFunctionParam) => ({ + ...(v.isCt && { + background: v.ctItemBackground, + borderStyle: v.ctBorderStyle, + borderWidth: v.ctBorderWidth, + height: v.ctHeight, + + ...(v.isCtItemPrimary && { background: v.ctItemPrimaryBackground }), + ...(v.isCtItemIndicator && { padding: v.ctItemIndicatorPadding }), + + ':focus-visible': { + background: v.ctItemBackgroundHover, + borderColor: v.ctItemBorderColorFocus, + color: v.ctItemColorFocus, + }, + }), + }), + }, + + ToolbarItem: { + root: ({ + props: p, + variables: v, + }: ComponentStyleFunctionParam) => { + return { + ...(v.isCt && { + alignItems: 'center', + display: 'flex', + justifyContent: 'center', + position: 'relative', + + background: v.ctItemBackground, + borderStyle: v.ctBorderStyle, + borderWidth: v.ctBorderWidth, + borderRadius: 0, + height: v.ctHeight, + minWidth: v.ctHeight, + color: v.ctItemColor, + + ...(p.active && + !v.isCtItemPrimary && { + // active intentionally before primary and danger, only affects regular items + color: v.ctItemActiveColor, + background: v.ctItemActiveBackground, + + '::before': { + content: `''`, + position: 'absolute', + top: `-${v.ctBorderWidth}`, + left: `-${v.ctBorderWidth}`, + bottom: `-${v.ctBorderWidth}`, + right: `-${v.ctBorderWidth}`, + background: v.ctItemActiveBackgroundOverlay, + + ':focus-visible': { + borderStyle: v.ctBorderStyle, + borderWidth: v.ctBorderWidth, + borderColor: v.ctItemBorderColorFocus, + }, + }, + }), + + ...(v.isCtItemDanger && { + background: v.ctItemDangerBackground, + }), + + ...(v.isCtItemPrimary && { + background: v.ctItemPrimaryBackground, + }), + + ':hover': { + color: v.ctItemColorHover, + background: v.ctItemBackgroundHover, + + ...(v.isCtItemDanger && { + color: v.ctItemDangerColorHover, + background: v.ctItemDangerBackgroundHover, + }), + + ...(v.isCtItemPrimary && { + color: v.ctItemPrimaryColorHover, + background: v.ctItemPrimaryBackgroundHover, + }), + }, + + ...(v.isCtItemWithNotification && { + '::after': { + content: `''`, + position: 'absolute', + width: v.ctItemNotificationSize, + height: v.ctItemNotificationSize, + borderRadius: '50%', + background: v.ctItemNotificationBackgroundColor, + transform: 'translateX(100%) translateY(-100%)', + }, + }), + + ':focus-visible': { + background: v.ctItemBackgroundHover, + borderColor: v.ctItemBorderColorFocus, + color: v.ctItemColorFocus, + + ...(v.isCtItemDanger && { + color: v.ctItemDangerColorHover, + background: v.ctItemDangerBackgroundHover, + }), + + ...(v.isCtItemPrimary && { + color: v.ctItemPrimaryColorHover, + background: v.ctItemPrimaryBackgroundHover, + }), + }, + }), + + ...(v.isCtItemIconNoFill && { + '& .ui-icon__filled': { + display: 'none', + }, + '& .ui-icon__outline': { + display: 'block', + }, + '&:hover .ui-icon__filled': { + display: 'none', + }, + '&:hover .ui-icon__outline': { + display: 'block', + }, + }), + } + }, + }, + + ToolbarDivider: { + root: ({ + props: p, + variables: v, + }: ComponentStyleFunctionParam) => ({ + ...(v.isCt && { + margin: 0, + }), + }), + }, + }, +} + +const tooltips = { + videoOn: 'Turn camera off', + videoOff: 'Turn camera on', + micOn: 'Mute', + micOff: 'Unmute', + share: 'Share', + shareStop: 'Stop sharing', + endCall: 'Hang up', + moreActions: 'More actions', + chat: 'Show conversation', + addParticipants: 'Add participants', + pptNext: 'Navigate forward', + pptPrevious: 'Navigate back', +} + +interface CustomToolbarProps { + layout?: 'standard' | 'desktop-share' | 'powerpoint-presenter' + + isRecording?: boolean + + cameraActive?: boolean + onCameraChange?: (state: boolean) => void + + micActive?: boolean + onMicChange?: (state: boolean) => void + + screenShareActive?: boolean + onScreenShareChange?: (state: boolean) => void + + sidebarSelected: false | 'chat' | 'participant-add' + onSidebarChange?: (state: false | 'chat' | 'participant-add') => void + + chatHasNotification?: boolean + + pptSlide?: string + onPptPrevClick?: () => void + onPptNextClick?: () => void + + onEndCallClick?: () => void +} + +type CustomToolbarLayout = ( + props: CustomToolbarProps, +) => ShorthandCollection + +const commonLayout: CustomToolbarLayout = props => + [ + props.isRecording && { + key: 'recording', + kind: 'custom' as ToolbarItemShorthandKinds, + focusable: true, + content: ( + + ), + variables: { isCtItemPrimary: true, isCtItemIndicator: true }, + }, + + { + key: 'timer-custom', + kind: 'custom' as ToolbarItemShorthandKinds, + focusable: true, + content: 10:45, + variables: { isCtItemPrimary: true, isCtItemIndicator: true }, + }, + + { key: 'timer-divider', kind: 'divider' as ToolbarItemShorthandKinds }, + + { + tooltip: props.cameraActive ? tooltips.videoOn : tooltips.videoOff, + active: props.cameraActive, + icon: { + name: props.cameraActive ? 'call-video' : 'call-video-off', + size: 'large' as SizeValue, + }, + key: 'camera', + onClick: () => _.invoke(props, 'onCameraChange', !props.cameraActive), + variables: { isCtItemPrimary: true }, + }, + + { + tooltip: props.micActive ? tooltips.micOn : tooltips.micOff, + active: props.micActive, + icon: { + name: props.micActive ? 'mic' : 'mic-off', + size: 'large' as SizeValue, + }, + key: 'mic', + onClick: () => _.invoke(props, 'onMicChange', !props.micActive), + variables: { isCtItemPrimary: true }, + }, + + { + tooltip: props.screenShareActive ? tooltips.shareStop : tooltips.share, + active: props.screenShareActive, + icon: { + name: props.screenShareActive ? 'call-control-close-tray' : 'call-control-present-new', + size: 'large' as SizeValue, + }, + key: 'screen-share', + onClick: () => _.invoke(props, 'onScreenShareChange', !props.screenShareActive), + variables: { isCtItemPrimary: true }, + }, + + { + tooltip: tooltips.moreActions, + key: 'more', + icon: { + name: 'more', + size: 'large' as SizeValue, + }, + onClick: () => _.invoke(props, 'onMoreClick'), + variables: { isCtItemPrimary: true }, + }, + ].filter(Boolean) + +const sidebarButtons: CustomToolbarLayout = props => [ + { + tooltip: tooltips.chat, + active: props.sidebarSelected === 'chat', + icon: { + name: 'chat', + outline: true, + size: 'large' as SizeValue, + }, + key: 'chat', + onClick: () => + _.invoke(props, 'onSidebarChange', props.sidebarSelected === 'chat' ? false : 'chat'), + variables: { isCtItemWithNotification: props.chatHasNotification, isCtItemIconNoFill: true }, + }, + { + tooltip: tooltips.addParticipants, + active: props.sidebarSelected === 'participant-add', + icon: { + name: 'participant-add', + outline: true, + size: 'large' as SizeValue, + }, + key: 'participant-add', + onClick: () => + _.invoke( + props, + 'onSidebarChange', + props.sidebarSelected === 'participant-add' ? false : 'participant-add', + ), + variables: { isCtItemIconNoFill: true }, + }, +] + +const layoutItems: ShorthandValue = { + endCall: props => ({ + tooltip: tooltips.endCall, + key: 'end-call', + icon: { + name: 'call-end', + size: 'large', + }, + onClick: () => _.invoke(props, 'onEndCallClick'), + variables: { isCtItemDanger: true }, + }), +} + +const layouts: Record = { + standard: props => [...commonLayout(props), ...sidebarButtons(props), layoutItems.endCall(props)], + + 'desktop-share': props => [ + ...commonLayout(props), + ...sidebarButtons(props), + { key: 'divider-sidebar', kind: 'divider' }, + { + key: 'stop-sharing', + kind: 'custom', + content: