Skip to content
This repository was archived by the owner on Sep 20, 2024. It is now read-only.

Commit 0ce1f56

Browse files
committed
feat(icon-button): adds icon button component and improves a11y test
1 parent 12a93fa commit 0ce1f56

File tree

14 files changed

+220
-82
lines changed

14 files changed

+220
-82
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,14 @@
1717
"playground:build": "yarn install && yarn build && yarn bootstrap && NODE_ENV=production vite build playground --config ./vite.config.ts",
1818
"test": "cross-env NODE_ENV=test jest --config jest.config.js",
1919
"lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix",
20-
2120
"docs:dev": "vitepress dev docs",
2221
"docs:build": "vitepress build docs",
2322
"docs:serve": "vitepress serve docs",
24-
2523
"core": "yarn workspace @chakra-ui/vue-next",
2624
"nuxt": "yarn workspace @chakra-ui/nuxt-next",
2725
"system": "yarn workspace @chakra-ui/vue-system",
2826
"utils": "yarn workspace @chakra-ui/vue-utils",
2927
"theme": "yarn workspace @chakra-ui/vue-theme",
30-
3128
"c-alert": "yarn workspace @chakra-ui/c-alert",
3229
"c-theme-provider": "yarn workspace @chakra-ui/c-theme-provider",
3330
"c-box": "yarn workspace @chakra-ui/c-box",
@@ -49,6 +46,7 @@
4946
"@testing-library/user-event": "^12.6.2",
5047
"@testing-library/vue": "^6.3.4",
5148
"@types/jest": "^26.0.20",
49+
"@types/jest-axe": "^3.5.1",
5250
"@types/recursive-readdir": "^2.2.0",
5351
"@typescript-eslint/eslint-plugin": "^2.34.0",
5452
"@typescript-eslint/parser": "4.0.1",
@@ -58,6 +56,7 @@
5856
"@vue/eslint-config-typescript": "^5.1.0",
5957
"@vuedx/typecheck": "^0.4.1",
6058
"@vuedx/typescript-plugin-vue": "^0.4.1",
59+
"axe-core": "^4.1.2",
6160
"babel-jest": "^26.6.3",
6261
"chokidar": "^3.5.1",
6362
"concurrently": "^5.3.0",
@@ -78,6 +77,7 @@
7877
"husky": "^4.3.8",
7978
"hygen": "^6.0.4",
8079
"jest": "^26.6.3",
80+
"jest-axe": "^4.1.0",
8181
"jest-transform-stub": "^2.0.0",
8282
"lerna": "^3.22.1",
8383
"lint-staged": "^10.5.3",
Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
<template>
2-
<c-button-group variant="outline" is-attached>
3-
<c-button color-scheme="blue">Save</c-button>
4-
<c-button>Cancel</c-button>
5-
</c-button-group>
2+
<chakra.div>
3+
<c-button-group
4+
shadow="xl"
5+
variant="outline"
6+
:color-scheme="color"
7+
is-attached
8+
>
9+
<c-button variant="solid">Save</c-button>
10+
<c-button>Cancel</c-button>
11+
</c-button-group>
12+
</chakra.div>
613
</template>
14+
15+
<script setup lang="ts">
16+
import { onMounted, ref } from 'vue'
17+
18+
const color = ref('yellow')
19+
</script>

packages/c-button/examples/with-button-icon.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@
88
<c-button mr="3" right-icon="star" color-scheme="blue">
99
Base button
1010
</c-button>
11+
<chakra.p mt="3" font-weight="bold">IconButton</chakra.p>
12+
<c-icon-button icon="star" mr="3" size="xs" color-scheme="teal" />
13+
<c-icon-button icon="star" mr="3" size="sm" color-scheme="teal" />
14+
<c-icon-button icon="star" mr="3" size="md" color-scheme="teal" />
15+
<c-icon-button icon="star" mr="3" size="lg" color-scheme="teal" />
1116
</div>
1217
</template>

packages/c-button/src/button-group.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const props = {
4444
styleConfig: String as PropType<ComponentThemeConfig>,
4545
}
4646

47-
interface ButtonGroupContext extends ThemingProps {
47+
type ButtonGroupContext = () => ThemingProps & {
4848
isDisabled?: boolean
4949
}
5050

@@ -59,12 +59,12 @@ const CButtonGroup = defineComponent({
5959
name: 'CButtonGroup',
6060
props,
6161
setup(props, { attrs, slots }) {
62-
ButtonGroupProvider({
62+
ButtonGroupProvider(() => ({
6363
size: props.size,
6464
colorScheme: props.colorScheme,
6565
variant: props.variant,
6666
isDisabled: props.isDisabled,
67-
})
67+
}))
6868

6969
const styles = computed(() => {
7070
let groupStyles: SystemStyleObject = {
@@ -92,7 +92,7 @@ const CButtonGroup = defineComponent({
9292
h(
9393
chakra('div', { label: 'button__group' }),
9494
{
95-
__css: styles.value,
95+
__css: { ...styles.value },
9696
role: 'group',
9797
...attrs,
9898
},

packages/c-button/src/button.ts

Lines changed: 20 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,11 @@
11
import { defineComponent, PropType, computed, cloneVNode, h } from 'vue'
2-
import {
3-
chakra,
4-
DOMElements,
5-
useStyleConfig,
6-
ThemingProps,
7-
} from '@chakra-ui/vue-system'
8-
import {
9-
SystemCSSProperties,
10-
SystemStyleObject,
11-
} from '@chakra-ui/styled-system'
12-
import { ComponentThemeConfig } from '@chakra-ui/vue-theme'
13-
import { dataAttr, mergeWith } from '@chakra-ui/vue-utils'
2+
import { chakra, useStyleConfig, ThemingProps } from '@chakra-ui/vue-system'
3+
import { SystemStyleObject } from '@chakra-ui/styled-system'
4+
import { dataAttr, filterUndefined, mergeWith } from '@chakra-ui/vue-utils'
145
import { useButtonGroup } from './button-group'
156
import { CIcon } from '@chakra-ui/c-icon'
167
import { CSpinner } from '@chakra-ui/c-spinner'
17-
18-
type ButtonTypes = 'button' | 'reset' | 'submit'
19-
20-
const props = {
21-
as: {
22-
type: String as PropType<DOMElements>,
23-
default: 'button',
24-
},
25-
isLoading: Boolean as PropType<boolean>,
26-
isActive: Boolean as PropType<boolean>,
27-
isDisabled: Boolean as PropType<boolean>,
28-
loadingText: String as PropType<string>,
29-
isFullWidth: Boolean as PropType<boolean>,
30-
type: String as PropType<ButtonTypes>,
31-
leftIcon: String as PropType<string>,
32-
rightIcon: String as PropType<string>,
33-
colorScheme: String as PropType<string>,
34-
variant: {
35-
type: String as PropType<string>,
36-
default: 'solid',
37-
},
38-
size: {
39-
type: String as PropType<string>,
40-
default: 'md',
41-
},
42-
styleConfig: String as PropType<ComponentThemeConfig>,
43-
44-
/** Not sure if the SystemCSSProperties is the right prop type for this */
45-
iconSpacing: {
46-
type: [String, Number, Array] as PropType<
47-
SystemCSSProperties['marginRight']
48-
>,
49-
default: '0.5rem',
50-
},
51-
}
8+
import { BUTTON_PROPS } from './button.utils'
529

5310
const CButtonSpinner = defineComponent({
5411
name: 'CButtonSpinner',
@@ -116,21 +73,26 @@ const CButtonIcon = defineComponent({
11673
*/
11774
const CButton = defineComponent({
11875
name: 'CButton',
119-
props,
76+
props: BUTTON_PROPS,
12077
setup(props, { attrs, slots }) {
121-
const themingProps = computed<ThemingProps>(() => ({
122-
colorScheme: props.colorScheme,
123-
variant: props.variant,
124-
size: props.size,
125-
styleConfig: props.styleConfig,
126-
}))
78+
const themingProps = computed<ThemingProps>(() =>
79+
filterUndefined({
80+
colorScheme: props.colorScheme,
81+
variant: props.variant,
82+
size: props.size,
83+
styleConfig: props.styleConfig,
84+
})
85+
)
12786

12887
const group = useButtonGroup()
129-
const styles = useStyleConfig('Button', { ...group, ...themingProps.value })
88+
const styles = useStyleConfig('Button', {
89+
...group?.(),
90+
...themingProps.value,
91+
})
13092

13193
const _focus = mergeWith({}, styles.value?.['_focus'] ?? {}, { zIndex: 1 })
13294

133-
const buttonStyles: SystemStyleObject = {
95+
const buttonStyles = computed<SystemStyleObject>(() => ({
13496
display: 'inline-flex',
13597
appearance: 'none',
13698
alignItems: 'center',
@@ -144,7 +106,7 @@ const CButton = defineComponent({
144106
width: props.isFullWidth ? '100%' : 'auto',
145107
...styles.value,
146108
...(!!group && { _focus }),
147-
}
109+
}))
148110

149111
return () =>
150112
h(
@@ -156,7 +118,7 @@ const CButton = defineComponent({
156118
type: props.as === 'button' ? undefined : props.type,
157119
dataActive: dataAttr(props.isActive),
158120
dataLoading: dataAttr(props.isLoading),
159-
...buttonStyles,
121+
...buttonStyles.value,
160122
...attrs,
161123
},
162124
() => [

packages/c-button/src/button.utils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { SystemCSSProperties } from '@chakra-ui/styled-system'
2+
import { DOMElements } from '@chakra-ui/vue-system'
3+
import { ComponentThemeConfig } from '@chakra-ui/vue-theme'
4+
import { PropType } from 'vue'
5+
6+
type ButtonTypes = 'button' | 'reset' | 'submit'
7+
8+
export const BUTTON_PROPS = {
9+
as: {
10+
type: String as PropType<DOMElements>,
11+
default: 'button',
12+
},
13+
isLoading: Boolean as PropType<boolean>,
14+
isActive: Boolean as PropType<boolean>,
15+
isDisabled: Boolean as PropType<boolean>,
16+
loadingText: String as PropType<string>,
17+
isFullWidth: Boolean as PropType<boolean>,
18+
type: String as PropType<ButtonTypes>,
19+
leftIcon: String as PropType<string>,
20+
rightIcon: String as PropType<string>,
21+
colorScheme: String as PropType<string>,
22+
variant: String as PropType<string>,
23+
size: String as PropType<string>,
24+
styleConfig: String as PropType<ComponentThemeConfig>,
25+
26+
/** Not sure if the SystemCSSProperties is the right prop type for this */
27+
iconSpacing: {
28+
type: [String, Number, Array] as PropType<
29+
SystemCSSProperties['marginRight']
30+
>,
31+
default: '0.5rem',
32+
},
33+
}

packages/c-button/src/icon-button.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { h, ComponentObjectPropsOptions, defineComponent, PropType } from 'vue'
2+
import CButton from './button'
3+
import { BUTTON_PROPS } from './button.utils'
4+
import { CIcon } from '@chakra-ui/c-icon'
5+
6+
const IconButtonProps: ComponentObjectPropsOptions = {
7+
...BUTTON_PROPS,
8+
icon: String as PropType<string>,
9+
isRound: Boolean as PropType<boolean>,
10+
ariaLabel: {
11+
type: String as PropType<string>,
12+
required: true,
13+
},
14+
}
15+
16+
/**
17+
* CIconButton
18+
*
19+
* IconButton composes the Button component except that it renders only an icon.
20+
*/
21+
const CIconButton = defineComponent({
22+
name: 'CIconButton',
23+
props: IconButtonProps,
24+
setup(props, { attrs }) {
25+
return () =>
26+
h(
27+
CButton,
28+
{
29+
padding: 0,
30+
rounded: props.isRound ? 'rounded' : 'md',
31+
'aria-label': props.ariaLabel,
32+
...props,
33+
...attrs,
34+
},
35+
() => [
36+
h(CIcon, {
37+
name: props.icon,
38+
}),
39+
]
40+
)
41+
},
42+
})
43+
44+
export default CIconButton

packages/c-button/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as CButton } from './button'
22
export { default as CButtonGroup } from './button-group'
3+
export { default as CIconButton } from './icon-button'

packages/c-button/tests/__snapshots__/c-button-group.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ exports[`should render properly 1`] = `
77
role="group"
88
>
99
<button
10-
class="chakra-button css-kb8k9p"
10+
class="chakra-button css-byqix5"
1111
>
1212
<!---->
1313
<!---->
1414
Save
1515
<!---->
1616
</button>
1717
<button
18-
class="chakra-button css-1vxhs68"
18+
class="chakra-button css-1twjehy"
1919
>
2020
<!---->
2121
<!---->

packages/c-button/tests/c-button-group.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1-
import { render } from '../../test-utils/src'
1+
import { defineComponent } from 'vue'
2+
import { render, testA11y } from '../../test-utils/src'
23
import { CButtonGroup, CButton } from '../src'
34

45
const renderComponent = (props?: any) => {
5-
const base = {
6+
const base = defineComponent({
67
components: {
78
CButtonGroup,
89
CButton,
910
},
1011
template: `
11-
<c-button-group variant="outline">
12-
<c-button color-scheme="blue">Save</c-button>
13-
<c-button>Cancel</c-button>
14-
</c-button-group>
12+
<c-button-group variant="outline">
13+
<c-button color-scheme="blue">Save</c-button>
14+
<c-button>Cancel</c-button>
15+
</c-button-group>
1516
`,
1617
...props,
17-
}
18+
})
1819
return render(base)
1920
}
2021

22+
it('should have no a11y violations', async () => {
23+
await testA11y(renderComponent())
24+
})
25+
2126
it('should render properly', () => {
2227
const { asFragment } = renderComponent()
2328
expect(asFragment()).toMatchSnapshot()

0 commit comments

Comments
 (0)