diff --git a/.eslintrc.js b/.eslintrc.js index 66b25a11..875ff2fe 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,15 +3,32 @@ module.exports = { env: { node: true }, - 'extends': [ + plugins: ['testing-library'], + extends: [ 'plugin:vue/essential', '@vue/standard', - '@nuxtjs' + '@nuxtjs', + 'plugin:testing-library/recommended', + 'plugin:testing-library/vue' ], rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', - 'curly': 'off' + curly: 'off', + 'testing-library/no-debug': 'error', + 'testing-library/prefer-screen-queries': 'error', + 'testing-library/await-fire-event': 'error', + indent: [ + 'error', + 2, + { + SwitchCase: 1, + ignoredNodes: [ + 'TemplateLiteral' + ] + } + ], + 'template-curly-spacing': 0 }, parserOptions: { parser: 'babel-eslint' @@ -23,10 +40,7 @@ module.exports = { }, overrides: [ { - files: [ - '**/__tests__/*.{j,t}s?(x)', - '**/tests/*.{j,t}s?(x)' - ], + files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/*.{j,t}s?(x)'], env: { jest: true } diff --git a/.github/workflows/nodejs.yml b/.github/workflows/release.yml similarity index 100% rename from .github/workflows/nodejs.yml rename to .github/workflows/release.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..72f63328 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Build & Test Components + +on: + # Trigger the workflow on push or pull request, + # but only for the master branch + push: + branches: + - develop + pull_request: + branches: + - develop + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [12.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn + - run: yarn bootstrap + - run: yarn lint + - run: yarn build --if-present + - run: yarn test + env: + CI: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index d7c5dce2..84fa0012 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ packages/*/node_modules lerna-debug.log .now config/.env -packages/nuxt-chakra/.github \ No newline at end of file +packages/nuxt-chakra/.github +packages/chakra-ui-docs/static/sw.js \ No newline at end of file diff --git a/.storybook/config.js b/.storybook/config.js index 8b085535..264296e6 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -1,9 +1,10 @@ import { configure, addDecorator, addParameters } from '@storybook/vue'; import Vue from 'vue' +import VueLive from 'vue-live' +import Lorem from 'vue-lorem-ipsum' import Chakra, { CThemeProvider, CColorModeProvider, CReset } from '../packages/chakra-ui-core/src' import Canvas from './components/Canvas.vue' import theme from '../packages/chakra-ui-core/src/lib/theme' -import icons from '../packages/chakra-ui-core/src/lib/internal-icons' import storyBookTheme from './theme' import { @@ -81,6 +82,10 @@ addDecorator(() => ({ components: { CThemeProvider, CColorModeProvider, CReset, Canvas } })); +// For playground +Vue.use(VueLive) + +Vue.component('Lorem', Lorem) function loadStories() { const req = require.context('../packages/chakra-ui-core/src', true, /\.stories\.(js|mdx)$/); diff --git a/babel.config.js b/babel.config.js index 28460893..3a4bb808 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,10 +3,5 @@ module.exports = { '@vue/app', '@babel/preset-env', '@vue/babel-preset-jsx' - ], - env: { - test: { - plugins: ['transform-es2015-modules-commonjs'] - } - } + ] } diff --git a/jest.config.js b/jest.config.js index e03e1bb7..d2c950c2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -29,5 +29,6 @@ module.exports = { watchPlugins: [ 'jest-watch-typeahead/filename', 'jest-watch-typeahead/testname' - ] + ], + testEnvironmentOptions: { resources: 'usable' } } diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 00000000..29037a62 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~/*": ["./*"], + "@/*": ["./*"], + "~~/*": ["./*"], + "@@/*": ["./*"] + } + }, + "exclude": ["node_modules", ".nuxt", "dist"] +} diff --git a/package.json b/package.json index f5e69eea..579d0661 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "mixpanel-browser": "^2.36.0", "node-fetch": "^2.6.0", "node-sass": "^4.13.1", - "nuxt": "^2.12.2", + "nuxt": "^2.13.3", "portal-vue": "^2.1.6", "prismjs": "^1.19.0", "register-service-worker": "^1.6.2", @@ -141,13 +141,14 @@ "@testing-library/user-event": "^10.0.0", "@testing-library/vue": "^4.1.0", "@vue/devtools": "^5.3.3", - "babel-eslint": "^10.0.1", + "babel-eslint": "^10.1.0", "bundlesize": "^0.18.0", "cross-env": "^7.0.2", "eslint-config-prettier": "^6.10.0", "eslint-loader": "^3.0.3", "eslint-plugin-nuxt": ">=0.4.2", "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-testing-library": "^3.3.1", "jest": "^25.1.0", "prettier": "^1.19.1", "rimraf": "^3.0.2", diff --git a/packages/chakra-ui-core/CHANGELOG.md b/packages/chakra-ui-core/CHANGELOG.md index 022adb81..d13ed926 100644 --- a/packages/chakra-ui-core/CHANGELOG.md +++ b/packages/chakra-ui-core/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 0.6.0 + +### Minor Changes + +- 38460db: Resolves performance issues my changing the underlying styling api. + Lots of optimizations in low-level primitives that yield better runtime performance for Chakra UI Vue. + ## 0.5.10 ### Patch Changes diff --git a/packages/chakra-ui-core/package.json b/packages/chakra-ui-core/package.json index 9166a5b1..af133a05 100644 --- a/packages/chakra-ui-core/package.json +++ b/packages/chakra-ui-core/package.json @@ -1,7 +1,7 @@ { "name": "@chakra-ui/vue", - "version": "0.5.10", - "description": "Build Accessible and Responsive Vue.js applications with ease", + "version": "0.6.0", + "description": "Build Accessible and Responsive Vue.js websites and applications with speed ⚡️", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "unpkg": "dist/umd/index.min.js", diff --git a/packages/chakra-ui-core/src/CAccordion/CAccordion.js b/packages/chakra-ui-core/src/CAccordion/CAccordion.js index 45b2f9b8..7b0cf20a 100644 --- a/packages/chakra-ui-core/src/CAccordion/CAccordion.js +++ b/packages/chakra-ui-core/src/CAccordion/CAccordion.js @@ -21,12 +21,9 @@ * @see WAI-ARIA https://www.w3.org/TR/wai-aria-practices-1.2/#accordion */ -import { baseProps } from '../config' -import { forwardProps, cloneVNodes, useId, isDef } from '../utils' -import styleProps from '../config/props' +import { forwardProps, cloneVNodes, useId, isDef, createStyledAttrsMixin, createWatcher } from '../utils' import { iconProps } from '../CIcon/utils/icon.props' -import CBox from '../CBox' import CPseudoBox from '../CPseudoBox' import CCollapse from '../CCollapse' import CIcon from '../CIcon' @@ -40,11 +37,10 @@ import CIcon from '../CIcon' * @extends CBox * @see Docs https://vue.chakra-ui.com/accordion */ - const CAccordion = { name: 'CAccordion', + mixins: [createStyledAttrsMixin('CAccordion')], props: { - ...baseProps, allowMultiple: Boolean, allowToggle: Boolean, index: { @@ -101,6 +97,7 @@ const CAccordion = { ...vnode.componentOptions.propsData, isOpen: this.getExpandCondition(this._index, index) }, + attrs: vnode.data.attrs || {}, on: { change: (isExpanded) => { if (this.allowMultiple) { @@ -136,13 +133,10 @@ const CAccordion = { return clone }) - return h(CBox, { - props: { - ...forwardProps(this.$props) - }, - attrs: { - 'data-chakra-component': 'CAccordion' - } + return h('div', { + class: this.className, + attrs: this.computedAttrs, + on: this.computedListeners }, clones) } } @@ -158,8 +152,8 @@ const CAccordion = { const CAccordionItem = { name: 'CAccordionItem', + mixins: [createStyledAttrsMixin('CAccordionItem', true)], props: { - ...styleProps, isOpen: { type: Boolean, default: null @@ -168,10 +162,7 @@ const CAccordionItem = { type: Boolean, default: false }, - id: { - type: String, - default: useId() - }, + id: String, isDisabled: { type: Boolean, default: false @@ -208,11 +199,20 @@ const CAccordionItem = { this.isExpanded = value } }, + _id () { + return this.id || useId() + }, headerId () { - return `accordion-header-${this.id}` + return `accordion-header-${this._id}` }, panelId () { - return `accordion-panel-${this.id}` + return `accordion-panel-${this._id}` + }, + componentStyles () { + return { + borderTopWidth: '1px', + _last: { borderBottomWidth: '1px' } + } } }, methods: { @@ -225,14 +225,13 @@ const CAccordionItem = { }, render (h) { return h(CPseudoBox, { + class: this.className, props: { ...forwardProps(this.$props), borderTopWidth: '1px', _last: { borderBottomWidth: '1px' } }, - attrs: { - 'data-chakra-component': 'CAccordionItem' - } + attrs: this.computedAttrs }, [ this.$scopedSlots.default({ isExpanded: this._isExpanded, @@ -253,18 +252,28 @@ const CAccordionItem = { */ const CAccordionHeader = { name: 'CAccordionHeader', + inheritAttrs: false, inject: ['$AccordionContext'], - props: styleProps, computed: { context () { return this.$AccordionContext() + }, + computedAttrs () { + return this.$data.attrs$ + } + }, + data () { + return { + attrs$: {} } }, + watch: { + $attrs: createWatcher('attrs$') + }, render (h) { const { isExpanded, panelId, headerId, isDisabled, onToggle } = this.context return h(CPseudoBox, { - props: { - ...forwardProps(this.$props), + attrs: { as: 'button', display: 'flex', alignItems: 'center', @@ -275,17 +284,17 @@ const CAccordionHeader = { py: 2, _focus: { boxShadow: 'outline' }, _hover: { bg: 'blackAlpha.50' }, - _disabled: { opacity: '0.4', cursor: 'not-allowed' } - }, - attrs: { + _disabled: { opacity: '0.4', cursor: 'not-allowed' }, id: headerId, type: 'button', disabled: isDisabled, 'aria-disabled': isDisabled, - 'aria-expanded': isExpanded, + 'aria-expanded': isExpanded ? 'true' : 'false', 'aria-controls': panelId, + ...this.computedAttrs, 'data-chakra-component': 'CAccordionHeader' }, + on: this.computedListeners, nativeOn: { click: (e) => { onToggle() @@ -295,7 +304,6 @@ const CAccordionHeader = { }, this.$slots.default) } } - /** * CAccordionPanel component * @@ -307,30 +315,33 @@ const CAccordionHeader = { */ const CAccordionPanel = { name: 'CAccordionPanel', + inheritAttrs: false, inject: ['$AccordionContext'], - props: baseProps, computed: { context () { return this.$AccordionContext() + }, + computedAttrs () { + return this.$attrs } }, render (h) { const { isExpanded, panelId, headerId } = this.context - return h(CCollapse, { props: { - ...forwardProps(this.$props), - isOpen: isExpanded, - pt: 2, - px: 4, - pb: 5 + isOpen: isExpanded }, + on: this.computedListeners, attrs: { + pt: 2, + px: 4, + pb: 5, + ...this.computedAttrs, id: panelId, - 'data-chakra-component': 'CAccordionPanel', 'aria-labelledby': headerId, 'aria-hidden': !isExpanded, - role: 'region' + role: 'region', + 'data-chakra-component': 'CAccordionPanel' } }, this.$slots.default) } @@ -347,31 +358,32 @@ const CAccordionPanel = { */ const CAccordionIcon = { name: 'CAccordionIcon', + mixins: [createStyledAttrsMixin('CAccordionIcon')], inject: ['$AccordionContext'], - props: { - ...baseProps, - ...iconProps - }, + props: iconProps, computed: { context () { return this.$AccordionContext() + }, + componentStyles () { + const { isExpanded, isDisabled } = this.context + return { + opacity: isDisabled ? 0.4 : 1, + transform: isExpanded ? 'rotate(-180deg)' : null, + transition: 'transform 0.2s', + transformOrigin: 'center' + } } }, render (h) { - const { isExpanded, isDisabled } = this.context return h(CIcon, { + class: this.className, props: { - ...forwardProps(this.$props), size: this.size || '1.25em', - name: this.name || 'chevron-down', - opacity: isDisabled ? 0.4 : 1, - transform: isExpanded ? 'rotate(-180deg)' : null, - transition: 'transform 0.2s', - transformOrigin: 'center' + name: this.name || 'chevron-down' }, - attrs: { - 'data-chakra-component': 'CAccordionIcon' - } + attrs: this.computedAttrs, + on: this.computedListeners }) } } diff --git a/packages/chakra-ui-core/src/CAccordion/CAccordion.stories.js b/packages/chakra-ui-core/src/CAccordion/CAccordion.stories.js index ca906410..1949c5bb 100644 --- a/packages/chakra-ui-core/src/CAccordion/CAccordion.stories.js +++ b/packages/chakra-ui-core/src/CAccordion/CAccordion.stories.js @@ -32,7 +32,6 @@ storiesOf('UI | Accordion', module) tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - ` diff --git a/packages/chakra-ui-core/src/CAccordion/tests/CAccordion.test.js b/packages/chakra-ui-core/src/CAccordion/tests/CAccordion.test.js index f6031289..ecb002ff 100644 --- a/packages/chakra-ui-core/src/CAccordion/tests/CAccordion.test.js +++ b/packages/chakra-ui-core/src/CAccordion/tests/CAccordion.test.js @@ -1,5 +1,5 @@ import { CAccordion, CAccordionItem, CAccordionHeader, CAccordionPanel, CAccordionIcon } from '..' -import { render, userEvent, fireEvent } from '@/tests/test-utils' +import { render, userEvent, screen } from '@/tests/test-utils' const renderComponent = (props) => { const base = { @@ -10,9 +10,8 @@ const renderComponent = (props) => { } it('should render correctly', () => { - const { asFragment } = renderComponent( - { - template: ` + const { asFragment } = renderComponent({ + template: ` Section 1 title @@ -20,13 +19,12 @@ it('should render correctly', () => { ` - } - ) + }) expect(asFragment()).toMatchSnapshot() }) it('uncontrolled: It opens the accordion panel', () => { - const { getByTestId } = renderComponent({ + renderComponent({ template: ` @@ -35,14 +33,13 @@ it('uncontrolled: It opens the accordion panel', () => { ` - } - ) - const button = getByTestId('button') + }) + const button = screen.getByTestId('button') expect(button).toHaveAttribute('aria-expanded', 'true') }) -it('uncontrolled: toggles the accordion on click', () => { - const { getByText } = renderComponent({ +it('uncontrolled: toggles the accordion on click', async () => { + renderComponent({ template: ` @@ -52,19 +49,17 @@ it('uncontrolled: toggles the accordion on click', () => { ` }) - const trigger = getByText('Trigger') - - userEvent.click(trigger) - expect(trigger).toHaveAttribute('aria-expanded', 'true') + await userEvent.click(screen.getByText('Trigger')) + expect(screen.getByText('Trigger')).toHaveAttribute('aria-expanded', 'true') // you can't toggle an accordion without passing `allowToggle` - userEvent.click(trigger) - expect(trigger).toHaveAttribute('aria-expanded', 'true') + await userEvent.click(screen.getByText('Trigger')) + expect(screen.getByText('Trigger')).toHaveAttribute('aria-expanded', 'true') }) // test the only one accordion can be visible + is not togglable -it('only one accordion can be visible + is not togglable', () => { - const { getByText } = renderComponent({ +it('only one accordion can be visible + is not togglable', async () => { + renderComponent({ template: ` @@ -78,21 +73,19 @@ it('only one accordion can be visible + is not togglable', () => { ` }) - const firstAccordion = getByText('First section') - - userEvent.click(firstAccordion) - expect(firstAccordion).toHaveAttribute('aria-expanded', 'true') + await userEvent.click(screen.getByText('First section')) + expect(screen.getByText('First section')).toHaveAttribute('aria-expanded', 'true') - userEvent.click(firstAccordion) - expect(firstAccordion).toHaveAttribute('aria-expanded', 'true') + await userEvent.click(screen.getByText('First section')) + expect(screen.getByText('First section')).toHaveAttribute('aria-expanded', 'true') }) // test the only one accordion can be visible + is togglable -it('only one accordion can be visible + is togglable', () => { - const { getByText } = renderComponent({ +it('only one accordion can be visible + is togglable', async () => { + renderComponent({ template: ` - + First section Panel 1 @@ -102,21 +95,19 @@ it('only one accordion can be visible + is togglable', () => { ` }) - const firstAccordion = getByText('First section') - // TODO: Why its not working? - // fireEvent.click(firstAccordion) - // expect(firstAccordion).toHaveAttribute('aria-expanded', 'false') + await userEvent.click(screen.getByText('First section')) + expect(screen.getByText('First section')).toHaveAttribute('aria-expanded', 'false') - userEvent.click(firstAccordion) - expect(firstAccordion).toHaveAttribute('aria-expanded', 'true') + await userEvent.click(screen.getByText('First section')) + expect(screen.getByText('First section')).toHaveAttribute('aria-expanded', 'true') }) // test that multiple accordions can be opened + is togglable -it('multiple accordions can be opened + is togglable', () => { - const { getByText } = renderComponent({ +it('multiple accordions can be opened + is togglable', async () => { + renderComponent({ template: ` - + First section Panel 1 @@ -128,19 +119,16 @@ it('multiple accordions can be opened + is togglable', () => { ` }) - const firstAccordion = getByText('First section') - const secondAccordion = getByText('Second section') + expect(screen.getByText('First section')).toHaveAttribute('aria-expanded', 'true') + expect(screen.getByText('Second section')).toHaveAttribute('aria-expanded', 'true') - userEvent.click(firstAccordion) - expect(firstAccordion).toHaveAttribute('aria-expanded', 'true') - - userEvent.click(secondAccordion) - expect(firstAccordion).toHaveAttribute('aria-expanded', 'true') + await userEvent.click(screen.getByText('First section')) + expect(screen.getByText('First section')).toHaveAttribute('aria-expanded', 'false') }) // it has the proper aria attributes it('has the proper aria attributes', () => { - const { getByText, getByTestId } = renderComponent({ + renderComponent({ template: ` @@ -149,16 +137,48 @@ it('has the proper aria attributes', () => { ` }) - const button = getByText('Section 1 title') - const panel = getByTestId('panel1') + const button = screen.getByText('Section 1 title') + const panel = screen.getByTestId('panel1') expect(button).toHaveAttribute('aria-controls') expect(button).toHaveAttribute('aria-expanded') expect(panel).toHaveAttribute('aria-labelledby') }) -// test that tab moves focus to the next focusable element -it('tab moves focus to the next focusable element', () => { - const { getByText } = renderComponent({ +it('tab moves focus to the next focusable element', async () => { + renderComponent({ + template: ` + + + First section + Panel 1 + + + Second section + Panel 2 + + + Last section + Panel 3 + + ` + }) + + const first = screen.getByText('First section') + const second = screen.getByText('Second section') + const last = screen.getByText('Last section') + + await userEvent.tab() + expect(first).toHaveFocus() + + await userEvent.tab() + expect(second).toHaveFocus() + + await userEvent.tab() + expect(last).toHaveFocus() +}) + +it('shift+tab moves focus to the previous focusable element', async () => { + renderComponent({ template: ` @@ -176,23 +196,25 @@ it('tab moves focus to the next focusable element', () => { ` }) - const first = getByText('First section') - const second = getByText('Second section') - const last = getByText('Last section') + const first = screen.getByText('First section') + const second = screen.getByText('Second section') + const last = screen.getByText('Last section') - userEvent.tab() + await userEvent.tab() expect(first).toHaveFocus() - userEvent.tab() + await userEvent.tab() expect(second).toHaveFocus() - userEvent.tab() + await userEvent.tab() expect(last).toHaveFocus() + + await userEvent.tab({ shift: true }) // shift+tab + expect(second).toHaveFocus() }) -// test that aria-contols for button is same as id for panel it('aria-contols for button is same as id for panel', () => { - const { getByText, getByTestId } = renderComponent({ + renderComponent({ template: ` @@ -202,14 +224,13 @@ it('aria-contols for button is same as id for panel', () => { ` }) - const button = getByText('Section 1 title') - const panel = getByTestId('panel1') + const button = screen.getByText('Section 1 title') + const panel = screen.getByTestId('panel1') expect(button.getAttribute('aria-controls')).toEqual(panel.getAttribute('id')) }) -// test that aria-expanded is true/false when accordion is open/closed it('aria-expanded is true/false when accordion is open/closed', () => { - const { getByText } = renderComponent({ + renderComponent({ template: ` @@ -223,13 +244,12 @@ it('aria-expanded is true/false when accordion is open/closed', () => { ` }) - const button = getByText('Section 1 title') + const button = screen.getByText('Section 1 title') expect(button).toHaveAttribute('aria-expanded', 'true') }) -// test that panel has role=region and aria-labelledby it('panel has role=region and aria-labelledby', () => { - const { getByTestId } = renderComponent({ + renderComponent({ template: ` @@ -238,34 +258,8 @@ it('panel has role=region and aria-labelledby', () => { ` }) - const panel = getByTestId('panel1') + const panel = screen.getByTestId('panel1') expect(panel).toHaveAttribute('aria-labelledby') expect(panel).toHaveAttribute('role', 'region') }) - -// eslint-disable-next-line no-undef -xit('arrow up & down moves focus to next/previous accordion', () => { - const { getByText } = renderComponent({ - template: ` - - - Section 1 title - Panel 1 - - - Section 2 title - Panel 2 - - ` - }) - - const first = getByText('Section 1 title') - const second = getByText('Section 2 title') - - fireEvent.keyDown(first, { key: 'ArrowDown', keyCode: 40 }) - expect(second).toHaveFocus() - - fireEvent.keyDown(second, { key: 'ArrowUp', keyCode: 38 }) - expect(first).toHaveFocus() -}) diff --git a/packages/chakra-ui-core/src/CAccordion/tests/__snapshots__/CAccordion.test.js.snap b/packages/chakra-ui-core/src/CAccordion/tests/__snapshots__/CAccordion.test.js.snap index 9d4740d2..986172f8 100644 --- a/packages/chakra-ui-core/src/CAccordion/tests/__snapshots__/CAccordion.test.js.snap +++ b/packages/chakra-ui-core/src/CAccordion/tests/__snapshots__/CAccordion.test.js.snap @@ -3,24 +3,24 @@ exports[`should render correctly 1`] = `
+ +
+ +`; + exports[`should render correctly 1`] = `
diff --git a/packages/chakra-ui-core/src/CCode/tests/CCode.test.js b/packages/chakra-ui-core/src/CCode/tests/CCode.test.js index d7151b2d..ffe67e55 100644 --- a/packages/chakra-ui-core/src/CCode/tests/CCode.test.js +++ b/packages/chakra-ui-core/src/CCode/tests/CCode.test.js @@ -1,5 +1,5 @@ import CCode from '..' -import { render } from '@/tests/test-utils' +import { render, screen } from '@/tests/test-utils' const renderComponent = (props) => { const base = { @@ -16,7 +16,7 @@ it('should render correctly', () => { }) it('should display children', () => { - const { getByText } = renderComponent() + renderComponent() - expect(getByText('content')).toBeInTheDocument() + expect(screen.getByText('content')).toBeInTheDocument() }) diff --git a/packages/chakra-ui-core/src/CCode/tests/__snapshots__/CCode.test.js.snap b/packages/chakra-ui-core/src/CCode/tests/__snapshots__/CCode.test.js.snap index 040ede46..5aac1e53 100644 --- a/packages/chakra-ui-core/src/CCode/tests/__snapshots__/CCode.test.js.snap +++ b/packages/chakra-ui-core/src/CCode/tests/__snapshots__/CCode.test.js.snap @@ -3,7 +3,7 @@ exports[`should render correctly 1`] = ` content diff --git a/packages/chakra-ui-core/src/CCollapse/CCollapse.js b/packages/chakra-ui-core/src/CCollapse/CCollapse.js index f1b20b24..789244d3 100644 --- a/packages/chakra-ui-core/src/CCollapse/CCollapse.js +++ b/packages/chakra-ui-core/src/CCollapse/CCollapse.js @@ -10,9 +10,8 @@ */ import { CAnimateHeight } from '../CTransition' -import { forwardProps } from '../utils' - import CBox from '../CBox' +import { extractListeners } from '../utils' /** * CCollapse component @@ -24,8 +23,8 @@ import CBox from '../CBox' * @see Docs https://vue.chakra-ui.com/collpse */ const CCollapse = { - name: 'CCollapse', - extends: CBox, + name: 'Collapse', + functional: true, props: { isOpen: Boolean, duration: { @@ -43,31 +42,48 @@ const CCollapse = { default: true } }, - render (h) { - const children = this.$slots.default + render (h, { slots, props, data, listeners, ...rest }) { + // Get children + const children = slots().default + + // Handle events + const nonNativeEvents = { + start: (e) => { + const emitStart = listeners.start + if (emitStart) { + emitStart('start', e) + } + }, + finish: (e) => { + const emitFinish = listeners.finish + if (emitFinish) { + emitFinish('finish', e) + } + } + } + const { native, nonNative } = extractListeners({ listeners }, nonNativeEvents) return h(CAnimateHeight, { + ...rest, props: { - isOpen: this.isOpen, - duration: this.duration, - enterEasing: this.easing, - leaveEasing: this.easing, - initialHeight: this.startingHeight, - finalHeight: this.endingHeight, - animateOpacity: this.animateOpacity - }, - on: { - enter: e => this.$emit('start', e), - leave: e => this.$emit('finish', e) + isOpen: props.isOpen, + duration: props.duration, + enterEasing: props.easing, + leaveEasing: props.easing, + initialHeight: props.startingHeight, + finalHeight: props.endingHeight, + animateOpacity: props.animateOpacity }, + on: nonNative, + nativeOn: native, attrs: { 'data-chakra-component': 'CCollapse' } }, [h(CBox, { props: { - ...forwardProps(this.$props), - overflow: 'hidden' - } + as: props.as + }, + attrs: data.attrs }, children)]) } } diff --git a/packages/chakra-ui-core/src/CCollapse/CCollapse.stories.js b/packages/chakra-ui-core/src/CCollapse/CCollapse.stories.js index 2c2ebcea..a80f741b 100644 --- a/packages/chakra-ui-core/src/CCollapse/CCollapse.stories.js +++ b/packages/chakra-ui-core/src/CCollapse/CCollapse.stories.js @@ -6,8 +6,8 @@ storiesOf('UI | Collapse', module) components: { CButton, CCollapse, CBox }, template: `
- Collapse - + Collapse + Lorem ipsum dolor sit, amet consectetur adipisicing elit. Vitae officia rem mollitia molestias eveniet, reiciendis perspiciatis minima deleniti iure voluptates laborum vel accusamus enim officiis dolorum necessitatibus, animi perferendis reprehenderit! @@ -18,13 +18,18 @@ storiesOf('UI | Collapse', module) return { showCollapsed: true } + }, + methods: { + handleFinished (e) { + console.log('Collapse complete!', e) + } } })) .add('Changing the startingHeight', () => ({ components: { CButton, CCollapse, CBox }, template: `
- Collapse + Collapse Lorem ipsum dolor sit, amet consectetur adipisicing elit. Vitae officia rem mollitia molestias eveniet, reiciendis perspiciatis minima deleniti iure voluptates laborum vel accusamus enim officiis dolorum necessitatibus, animi perferendis reprehenderit! diff --git a/packages/chakra-ui-core/src/CCollapse/tests/__snapshots__/CCollapse.test.js.snap b/packages/chakra-ui-core/src/CCollapse/tests/__snapshots__/CCollapse.test.js.snap index 8eda490f..71b504a2 100644 --- a/packages/chakra-ui-core/src/CCollapse/tests/__snapshots__/CCollapse.test.js.snap +++ b/packages/chakra-ui-core/src/CCollapse/tests/__snapshots__/CCollapse.test.js.snap @@ -3,11 +3,11 @@ exports[`should render correctly 1`] = `
content diff --git a/packages/chakra-ui-core/src/CControlBox/CControlBox.js b/packages/chakra-ui-core/src/CControlBox/CControlBox.js index 11843c4e..5a761c13 100644 --- a/packages/chakra-ui-core/src/CControlBox/CControlBox.js +++ b/packages/chakra-ui-core/src/CControlBox/CControlBox.js @@ -16,9 +16,7 @@ import { css } from 'emotion' import __css from '@styled-system/css' -import { tx, forwardProps } from '../utils' -import { baseProps } from '../config' - +import { tx } from '../utils' import CBox from '../CBox' // Default ControlBox props types @@ -34,8 +32,10 @@ const PseudoPropTypes = [Object, Array] */ const CControlBox = { name: 'CControlBox', + functional: true, inject: ['$chakraTheme'], props: { + as: [String, Object], type: { type: String, default: 'checkbox' @@ -44,11 +44,6 @@ const CControlBox = { type: [Number, String, Array], default: 'auto' }, - _hover: PseudoPropTypes, - _invalid: PseudoPropTypes, - _disabled: PseudoPropTypes, - _focus: PseudoPropTypes, - _checked: PseudoPropTypes, _child: { type: PseudoPropTypes, default: () => ({ opacity: 0 }) @@ -59,58 +54,62 @@ const CControlBox = { }, _checkedAndDisabled: PseudoPropTypes, _checkedAndFocus: PseudoPropTypes, - _checkedAndHover: PseudoPropTypes, - ...baseProps + _checkedAndHover: PseudoPropTypes }, - computed: { - theme () { - return this.$chakraTheme() - }, - className () { - const checkedAndDisabled = `input[type=${this.type}]:checked:disabled + &, input[type=${this.type}][aria-checked=mixed]:disabled + &` - const checkedAndHover = `input[type=${this.type}]:checked:hover:not(:disabled) + &, input[type=${this.type}][aria-checked=mixed]:hover:not(:disabled) + &` - const checkedAndFocus = `input[type=${this.type}]:checked:focus + &, input[type=${this.type}][aria-checked=mixed]:focus + &` - const disabled = `input[type=${this.type}]:disabled + &` - const focus = `input[type=${this.type}]:focus + &` - const hover = `input[type=${this.type}]:hover:not(:disabled):not(:checked) + &` - const checked = `input[type=${this.type}]:checked + &, input[type=${this.type}][aria-checked=mixed] + &` - const invalid = `input[type=${this.type}][aria-invalid=true] + &` + render (h, { props, data, injections, slots }) { + const { attrs } = data + + // Inject theme + const theme = injections.$chakraTheme() + + // Parse child styles + const checkedAndDisabled = `input[type=${props.type}]:checked:disabled + &, input[type=${props.type}][aria-checked=mixed]:disabled + &` + const checkedAndHover = `input[type=${props.type}]:checked:hover:not(:disabled) + &, input[type=${props.type}][aria-checked=mixed]:hover:not(:disabled) + &` + const checkedAndFocus = `input[type=${props.type}]:checked:focus + &, input[type=${props.type}][aria-checked=mixed]:focus + &` + const disabled = `input[type=${props.type}]:disabled + &` + const focus = `input[type=${props.type}]:focus + &` + const hover = `input[type=${props.type}]:hover:not(:disabled):not(:checked) + &` + const checked = `input[type=${props.type}]:checked + &, input[type=${props.type}][aria-checked=mixed] + &` + const invalid = `input[type=${props.type}][aria-invalid=true] + &` + + const basePseudoAttrs = (attrs && ({ + [focus]: tx(attrs._focus), + [hover]: tx(attrs._hover), + [disabled]: tx(attrs._disabled), + [invalid]: tx(attrs._invalid) + })) || {} + + const controlBoxStyleObject = __css({ + ...basePseudoAttrs, + [checkedAndDisabled]: tx(props._checkedAndDisabled), + [checkedAndFocus]: tx(props._checkedAndFocus), + [checkedAndHover]: tx(props._checkedAndHover), + '& > *': tx(props._child), + [checked]: { + ...attrs && tx(attrs._checked), + '& > *': tx(props._checkedAndChild) + } + })(theme) + + const className = css(controlBoxStyleObject) - const controlBoxStyleObject = __css({ - [focus]: tx(this._focus), - [hover]: tx(this._hover), - [disabled]: tx(this._disabled), - [invalid]: tx(this._invalid), - [checkedAndDisabled]: tx(this._checkedAndDisabled), - [checkedAndFocus]: tx(this._checkedAndFocus), - [checkedAndHover]: tx(this._checkedAndHover), - '& > *': tx(this._child), - [checked]: { - ...tx(this._checked), - '& > *': tx(this._checkedAndChild) - } - })(this.theme) - return css(controlBoxStyleObject) - } - }, - render (h) { return h(CBox, { - class: [this.className], - props: { + ...data, + class: [className], + props: { as: props.as }, + attrs: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', transition: 'all 120ms', flexShrink: '0', - width: this.size, - height: this.size, - ...forwardProps(this.$props) - }, - attrs: { + width: props.size, + height: props.size, 'aria-hidden': 'true', + ...attrs, 'data-chakra-component': 'CControlBox' } - }, this.$slots.default) + }, slots().default) } } diff --git a/packages/chakra-ui-core/src/CControlBox/CControlBox.stories.js b/packages/chakra-ui-core/src/CControlBox/CControlBox.stories.js index 2b983f19..bd561229 100644 --- a/packages/chakra-ui-core/src/CControlBox/CControlBox.stories.js +++ b/packages/chakra-ui-core/src/CControlBox/CControlBox.stories.js @@ -67,3 +67,40 @@ storiesOf('UI | ControlBox', module) ` })) + .add('Functional control box', () => ({ + components: { CControlBox, CVisuallyHidden, CBox, CIcon }, + template: ` + + + + + + + + + + + + Checkbox Label + + + `, + methods: { + handleChange (e) { + console.log('changed', e) + } + } + })) diff --git a/packages/chakra-ui-core/src/CControlBox/tests/CControlBox.test.js b/packages/chakra-ui-core/src/CControlBox/tests/CControlBox.test.js index 9e375198..4ea7a3c4 100644 --- a/packages/chakra-ui-core/src/CControlBox/tests/CControlBox.test.js +++ b/packages/chakra-ui-core/src/CControlBox/tests/CControlBox.test.js @@ -1,5 +1,5 @@ import { CVisuallyHidden, CControlBox, CBox } from '../..' -import { render, userEvent } from '@/tests/test-utils' +import { render, userEvent, screen } from '@/tests/test-utils' const renderComponent = (props) => { const inlineAttrs = (props && props.inlineAttrs) || '' @@ -20,9 +20,9 @@ const renderComponent = (props) => { rounded="full" border-color="inherit" :_checked="{ - bg: 'green.500', - borderColor: 'green.500' - }" + bg: 'green.500', + borderColor: 'green.500' + }" :_hover="{ borderColor: 'gray.300' }" :_focus="{ boxShadow: 'outline' }" :_disabled="{ opacity: '40%' }" @@ -43,9 +43,9 @@ it('should render correctly', () => { }) test('Uncontrolled radio - should be checked always', async () => { - const { getByTestId } = renderComponent({ type: 'radio' }) - const control = getByTestId('control') - const hiddenControl = getByTestId('hiddenControl') + renderComponent({ type: 'radio' }) + const control = screen.getByTestId('control') + const hiddenControl = screen.getByTestId('hiddenControl') // click the first time, it's checked await userEvent.click(control) @@ -57,9 +57,9 @@ test('Uncontrolled radio - should be checked always', async () => { }) test('Uncontrolled checkbox - should toggle', async () => { - const { getByTestId } = renderComponent({ type: 'checkbox' }) - const control = getByTestId('control') - const hiddenControl = getByTestId('hiddenControl') + renderComponent({ type: 'checkbox' }) + const control = screen.getByTestId('control') + const hiddenControl = screen.getByTestId('hiddenControl') // click the first time, it's checked await userEvent.click(control) @@ -72,9 +72,9 @@ test('Uncontrolled checkbox - should toggle', async () => { test('controlled checkbox - v-model works', async () => { const inlineAttrs = ':checked="checked"' - const { getByTestId } = renderComponent({ inlineAttrs, type: 'checkbox', data: () => ({ checked: true }) }) - const control = getByTestId('control') - const hiddenControl = getByTestId('hiddenControl') + renderComponent({ inlineAttrs, type: 'checkbox', data: () => ({ checked: true }) }) + const control = screen.getByTestId('control') + const hiddenControl = screen.getByTestId('hiddenControl') // click the first time, it's checked expect(hiddenControl).toBeChecked() @@ -86,9 +86,9 @@ test('controlled checkbox - v-model works', async () => { test('controlled radio - v-model works', async () => { const inlineAttrs = ':checked="checked"' - const { getByTestId } = renderComponent({ inlineAttrs, type: 'radio', data: () => ({ checked: true }) }) - const control = getByTestId('control') - const hiddenControl = getByTestId('hiddenControl') + renderComponent({ inlineAttrs, type: 'radio', data: () => ({ checked: true }) }) + const control = screen.getByTestId('control') + const hiddenControl = screen.getByTestId('hiddenControl') // click the first time, it's checked expect(hiddenControl).toBeChecked() diff --git a/packages/chakra-ui-core/src/CControlBox/tests/__snapshots__/CControlBox.test.js.snap b/packages/chakra-ui-core/src/CControlBox/tests/__snapshots__/CControlBox.test.js.snap index dd62539d..fa1f1a22 100644 --- a/packages/chakra-ui-core/src/CControlBox/tests/__snapshots__/CControlBox.test.js.snap +++ b/packages/chakra-ui-core/src/CControlBox/tests/__snapshots__/CControlBox.test.js.snap @@ -3,12 +3,12 @@ exports[`should render correctly 1`] = `