}
-export type { SystemStyleObject } from '@chakra-ui/styled-system'
+type ElementType =
+ | {
+ [K in keyof IntrinsicElementAttributes]: P extends IntrinsicElementAttributes[K]
+ ? K
+ : never
+ }[keyof IntrinsicElementAttributes]
+ | Component
+
+export type As = ElementType
+
+/**
+ * Extract the props of a Vue element or component
+ */
+export type PropsOf = T & {
+ as?: As
+}
+
+export type HTMLChakraProps = Omit<
+ PropsOf,
+ T extends 'svg'
+ ? 'ref' | 'children' | keyof StyleProps
+ : 'ref' | keyof StyleProps
+> &
+ ChakraProps & { as?: As }
diff --git a/packages/system/tests/__snapshots__/system.test.ts.snap b/packages/system/tests/__snapshots__/system.test.ts.snap
index 2f43ce36..472830ff 100644
--- a/packages/system/tests/__snapshots__/system.test.ts.snap
+++ b/packages/system/tests/__snapshots__/system.test.ts.snap
@@ -1,6 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should be render properly 1`] = `
+exports[`as prop render Vue Component like :as="RouterLink" 1`] = `
+
+
+ as RouterLink
+
+
+`;
+
+exports[`as prop render dom elements like as="h1" 1`] = `
+
+
+ as h1
+
+
+`;
+
+exports[`as prop resolve global vue components like as="router-link" 1`] = `
+
+
+ as router-link
+
+
+`;
+
+exports[`chakra() works should be render properly 1`] = `
) =>
- render({
- components: {
- chakra: defineComponent({
- setup(_, { slots }) {
- return () => h(chakra('span', {}), {}, slots)
- },
- }),
- },
- template: `child-element`,
- ...options,
+describe('chakra() works', () => {
+ const renderComponent = (options?: Record) =>
+ render({
+ components: {
+ chakra: defineComponent({
+ setup(_, { slots }) {
+ return () => h(chakra('span', {}), {}, slots)
+ },
+ }),
+ },
+ template: `child-element`,
+ ...options,
+ })
+
+ it('should be render properly', () => {
+ const { asFragment } = renderComponent()
+ expect(asFragment()).toMatchSnapshot()
})
-it('should be render properly', () => {
- const { asFragment } = renderComponent()
- expect(asFragment()).toMatchSnapshot()
+ it('should be render default slot', () => {
+ renderComponent()
+ expect(screen.getByText('child-element')).toBeInTheDocument()
+ })
})
-it('should be render default slot', () => {
- renderComponent()
- expect(screen.getByText('child-element')).toBeInTheDocument()
+describe('as prop', () => {
+ const FakeRouterLink = defineComponent({
+ props: ['to'],
+ setup(props, { slots }) {
+ return () => h('a', { href: props.to }, { default: () => slots })
+ },
+ })
+ it('render dom elements like as="h1"', () => {
+ const { container, asFragment } = render({
+ components: {
+ chakra: defineComponent({
+ setup(_, { slots }) {
+ return () => h(chakra('h1', {}), {}, slots)
+ },
+ }),
+ },
+ template: `as h1`,
+ })
+
+ expect(asFragment()).toMatchSnapshot()
+ expect(container.querySelector('h1')).toBeInTheDocument()
+ })
+
+ it('resolve global vue components like as="router-link"', () => {
+ const { container, asFragment } = render(
+ {
+ components: {
+ chakra: defineComponent({
+ props: ['as'],
+ setup(props, { slots, attrs }) {
+ return () => h(chakra(props.as), {}, slots)
+ },
+ }),
+ },
+ template: `as router-link`,
+ },
+ // testing-library options
+ {
+ global: {
+ components: {
+ FakeRouterLink,
+ },
+ },
+ }
+ )
+
+ expect(container.querySelector('a')).toBeInTheDocument()
+ expect(asFragment()).toMatchSnapshot()
+ })
+
+ it('render Vue Component like :as="RouterLink"', () => {
+ const { container, asFragment } = render({
+ components: {
+ chakra: defineComponent({
+ setup(_, { slots }) {
+ return () =>
+ h(
+ chakra(FakeRouterLink, { to: 'https://vue.chakra-ui.com/' }),
+ {},
+ slots
+ )
+ },
+ }),
+ },
+ template: `as RouterLink`,
+ })
+
+ expect(container.querySelector('a')).toBeInTheDocument()
+ expect(asFragment()).toMatchSnapshot()
+ })
})
diff --git a/packages/test-utils/src/render.ts b/packages/test-utils/src/render.ts
index 0bb0cc81..f9ae7ddd 100644
--- a/packages/test-utils/src/render.ts
+++ b/packages/test-utils/src/render.ts
@@ -1,3 +1,4 @@
+import { ComponentOptions } from '@vue/runtime-core'
import theme from '@chakra-ui/vue-theme'
import '@testing-library/jest-dom'
import '@testing-library/jest-dom/extend-expect'
@@ -15,7 +16,11 @@ const useDefaultProviders = () => {
provide('$chakraIcons', {})
}
-type UI = Parameters[0]
+export type TestRenderProps = {
+ [key: string]: any
+ inlineAttrs?: string
+} & Partial
+
type A11yOptions = { axeOptions?: RunOptions }
export interface RenderResult extends vtl.RenderResult {
asFragment: (innerHTML?: string) => DocumentFragment
@@ -23,7 +28,7 @@ export interface RenderResult extends vtl.RenderResult {
/** Render component instance */
export const render = (
- component: Component | any,
+ component: Component,
...rest: any | undefined
): RenderResult => {
const utils = vtl.render(
@@ -31,7 +36,7 @@ export const render = (
name: 'ChakraUIVueTestContainer',
setup(_, { slots }) {
useDefaultProviders()
- return () => h(component, slots)
+ return () => h(component as any, slots)
},
}),
...rest
@@ -100,7 +105,7 @@ export function getElementStyles(selector: string) {
* @example
* ```ts
* it('passes a11y test', async () => {
- * await testA11Y(MyComponent, options);
+ * await testA11Y(render(MyComponent));
* });
*
* // sometimes we need to perform interactions first to render conditional UI
@@ -114,11 +119,14 @@ export function getElementStyles(selector: string) {
* ```
*/
export const testA11y = async (
- ui: UI | Element,
- { axeOptions, ...options }: A11yOptions = {}
+ ui: vtl.RenderResult | Element,
+ { axeOptions }: A11yOptions = {}
) => {
- const container = vtl.render(ui, options).container
- const results = await axe(container, axeOptions)
+ let template = ui as Element
+ if ('container' in ui) {
+ template = ui.container
+ }
+ const results = await axe(template, axeOptions)
expect(results).toHaveNoViolations()
}
diff --git a/packages/theme-tools/src/create-breakpoints.ts b/packages/theme-tools/src/create-breakpoints.ts
index 9aea32eb..fd2d3837 100644
--- a/packages/theme-tools/src/create-breakpoints.ts
+++ b/packages/theme-tools/src/create-breakpoints.ts
@@ -14,11 +14,12 @@ export type Breakpoints = T & { base: '0em' }
export const createBreakpoints = (
config: T
): Breakpoints => {
- warn(
- [
+ warn({
+ condition: true,
+ message: [
`[chakra-ui]: createBreakpoints(...) will be deprecated pretty soon`,
`simply pass the breakpoints as an object. Remove the createBreakpoint(..) call`,
- ].join('')
- )
+ ].join(''),
+ })
return { base: '0em', ...config }
}
diff --git a/packages/theme/src/components/index.ts b/packages/theme/src/components/index.ts
index 46dd567f..081ce7b4 100644
--- a/packages/theme/src/components/index.ts
+++ b/packages/theme/src/components/index.ts
@@ -7,6 +7,8 @@ import Button from './button'
import Checkbox from './checkbox'
import CloseButton from './close-button'
import Code from './code'
+import Container from './container'
+import Divider from './divider'
import Drawer from './drawer'
import Editable from './editable'
import Form from './form'
@@ -15,6 +17,7 @@ import Heading from './heading'
import Input from './input'
import Kbd from './kbd'
import Link from './link'
+import List from './list'
import Menu from './menu'
import Modal from './modal'
import NumberInput from './number-input'
@@ -23,15 +26,18 @@ import Popover from './popover'
import Progress from './progress'
import Radio from './radio'
import Select from './select'
+import Skeleton from './skeleton'
import SkipLink from './skip-link'
import Slider from './slider'
import Spinner from './spinner'
import Stat from './stat'
import Switch from './switch'
+import Table from './table'
import Tabs from './tabs'
import Tag from './tag'
import Textarea from './textarea'
import Tooltip from './tooltip'
+import FormError from './form-error'
export default {
Accordion,
@@ -43,6 +49,8 @@ export default {
Checkbox,
CloseButton,
Code,
+ Container,
+ Divider,
Drawer,
Editable,
Form,
@@ -51,6 +59,7 @@ export default {
Input,
Kbd,
Link,
+ List,
Menu,
Modal,
NumberInput,
@@ -59,13 +68,16 @@ export default {
Progress,
Radio,
Select,
+ Skeleton,
SkipLink,
Slider,
Spinner,
Stat,
Switch,
+ Table,
Tabs,
Tag,
Textarea,
Tooltip,
+ FormError,
}
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 244c3b80..a9a6d64c 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -1,2 +1,3 @@
export * from './vue-utils'
export * from './layout'
+export * from './props'
diff --git a/packages/utils/src/props.ts b/packages/utils/src/props.ts
new file mode 100644
index 00000000..a21159ba
--- /dev/null
+++ b/packages/utils/src/props.ts
@@ -0,0 +1,13 @@
+import { ThemingProps } from '@chakra-ui/vue-system'
+import { PropType } from 'vue'
+
+export const vueThemingProps = {
+ colorScheme: String as PropType,
+ variant: String as PropType,
+ size: String as PropType,
+ styleConfig: String as PropType,
+}
+
+export const SNA = [Number, String, Array]
+export const SAO = [String, Array, Object]
+export const SNAO = [Number, String, Array, Object]
diff --git a/packages/utils/src/vue-utils.ts b/packages/utils/src/vue-utils.ts
index 1ad54414..594f195e 100644
--- a/packages/utils/src/vue-utils.ts
+++ b/packages/utils/src/vue-utils.ts
@@ -1,4 +1,4 @@
-import { inject, InjectionKey, provide } from 'vue'
+import { inject, InjectionKey, provide, isVNode, Slots } from 'vue'
export interface CreateContextOptions {
/**
@@ -48,3 +48,18 @@ export function createContext(options: CreateContextOptions = {}) {
return [Provider, useContext] as CreateContextReturn
}
+
+/**
+ * Gets only the valid children of a component,
+ * and ignores any nullish or falsy child.
+ *
+ * @param slots vue slots
+ *
+ * see https://github.com/vuejs/vue-next/blob/HEAD/packages/runtime-core/src/helpers/renderSlot.ts
+ */
+export function getValidChildren(slots: Slots | null) {
+ const slotArray = slots?.default?.() || []
+ return slotArray.filter((child) => {
+ return isVNode(child)
+ })
+}
diff --git a/playground/src/App.vue b/playground/src/App.vue
index 3ad814ee..1f9d2f25 100644
--- a/playground/src/App.vue
+++ b/playground/src/App.vue
@@ -2,7 +2,9 @@
-
+
+
+
diff --git a/playground/src/components/Sidebar.vue b/playground/src/components/Sidebar.vue
index 025e1755..1644d43e 100644
--- a/playground/src/components/Sidebar.vue
+++ b/playground/src/components/Sidebar.vue
@@ -3,16 +3,58 @@ import { chakra } from '@chakra-ui/vue-system'
import { defineComponent, h } from 'vue'
import { RouterLink } from 'vue-router'
-
const Stories = defineComponent({
props: ['stories'],
inheritAttrs: false,
setup(props) {
return () => {
- return h(chakra.nav, {
- overflowY: 'scroll',
- w: '250px'
- }, () => [
+ const chakraLogo = h(
+ chakra(RouterLink),
+ {
+ to: '/',
+ _hover: { color: 'blue.400' },
+ },
+ () =>
+ h(chakra.img, {
+ w: '120px',
+ mt: 4,
+ src:
+ 'https://res.cloudinary.com/xtellar/image/upload/v1584242872/chakra-ui/chakra-ui-vue.png',
+ })
+ )
+
+ const storyTitle = (story) =>
+ h(chakra.h3, { mt: 2, mb: 0, fontWeight: 'bold' }, () => story.name)
+
+ const storyItem = (story) => {
+ if (story.path === '/') {
+ return chakraLogo
+ } else
+ return h(
+ chakra(RouterLink),
+ {
+ to: story.path,
+ _hover: { color: 'blue.400' },
+ },
+ () => story.name
+ )
+ }
+
+ const liItem = (story) =>
+ h(
+ chakra.li,
+ {
+ pl: 2,
+ fontSize: '0.8rem',
+ key: story.path,
+ },
+ () => [
+ story.children ? storyTitle(story) : storyItem(story),
+ story.children && h(Stories, { stories: story.children }),
+ ]
+ )
+
+ return h(chakra.nav, { overflowY: 'scroll', w: '250px' }, () =>
h(
chakra.ul,
{
@@ -21,31 +63,9 @@ const Stories = defineComponent({
w: '175px',
listStyleType: 'none',
},
- () => props.stories
- .filter(story => story.path !== '/*')
- .map(story =>
- h(
- chakra.li,
- {
- pl: 2,
- fontSize: '0.8rem',
- key: story.path,
- },
- story.children
- ? h(chakra.h3, { mt: 2, mb: 0, fontWeight: 'bold' }, () => story.name)
- : [
- h(chakra(RouterLink), {
- to: story.path,
- _hover: { color: 'blue.400' }
- }, story.path === '/'
- ? () => [h(chakra.img, { w: '120px', mt: 4, src: 'https://res.cloudinary.com/xtellar/image/upload/v1584242872/chakra-ui/chakra-ui-vue.png' })]
- : () => story.name)
- ],
- story.children && h(Stories, { stories: story.children })
- )
- ),
+ () => props.stories.map(liItem)
)
- ])
+ )
}
},
})
diff --git a/tooling/auto-import/package.json b/tooling/auto-import/package.json
index 9c6b38dc..36d0b5e5 100644
--- a/tooling/auto-import/package.json
+++ b/tooling/auto-import/package.json
@@ -37,8 +37,9 @@
"vue": "^3.0.5"
},
"devDependencies": {
- "vite": "^2.0.1",
- "vite-plugin-components": "^0.6.6",
+ "@chakra-ui/vue-next": "1.0.0-alpha.2",
+ "vite": "2.1.5",
+ "vite-plugin-components": "^0.8.3",
"vue": ">=3.0.5"
}
}
diff --git a/tooling/auto-import/src/index.ts b/tooling/auto-import/src/index.ts
index b8b828eb..a210edd1 100644
--- a/tooling/auto-import/src/index.ts
+++ b/tooling/auto-import/src/index.ts
@@ -1,9 +1,10 @@
-import kebabCase from 'lodash.kebabcase'
+import * as ChakraComponenets from '@chakra-ui/vue-next'
export const componentResolver = (name: string) => {
- if (kebabCase(name).startsWith('c-'))
+ if (name in ChakraComponenets) {
return {
importName: name,
path: `@chakra-ui/vue-next`,
+ }
}
}
diff --git a/tooling/auto-import/tests/vue-auto-import.test.ts b/tooling/auto-import/tests/vue-auto-import.test.ts
index 8f525c53..9bb0d5e5 100644
--- a/tooling/auto-import/tests/vue-auto-import.test.ts
+++ b/tooling/auto-import/tests/vue-auto-import.test.ts
@@ -2,14 +2,23 @@ import { componentResolver } from '../src'
it('should resolve chakra components with import name and path', () => {
const components = [
- 'CColorModeProvider',
+ 'CCloseButton',
'CBox',
- 'CPortal'
+ 'CPortal',
+ 'CHStack',
+ 'CVStack',
+ 'CBox',
+ 'CCircle',
+ 'CSquare',
+ 'CKbd',
]
const path = '@chakra-ui/vue-next'
components.forEach((templateComponent: string) => {
- expect(componentResolver(templateComponent)).toEqual({ importName: templateComponent, path })
- });
-})
\ No newline at end of file
+ expect(componentResolver(templateComponent)).toEqual({
+ importName: templateComponent,
+ path,
+ })
+ })
+})
diff --git a/tsconfig.json b/tsconfig.json
index f4a54683..09d5e73b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -29,7 +29,7 @@
"playground/**/*.tsx",
"playground/**/*.vue",
],
- "exclude": ["node_modules", "./@types"],
+ "exclude": ["node_modules", "./@types", "dist"],
"ts-node": {
"compilerOptions": {
"module": "CommonJS",
diff --git a/website/package.json b/website/package.json
index 7ef39191..efcb39bd 100644
--- a/website/package.json
+++ b/website/package.json
@@ -14,25 +14,27 @@
},
"dependencies": {
"@emotion/css": "^11.1.3",
- "@emotion/server": "^11.0.0",
- "@vueuse/head": "^0.3.1",
"feather-icons-paths": "^1.0.8",
"prism-theme-vars": "^0.1.4",
"vue": ">=3.0.5",
- "vue-router": "^4.0.4"
+ "vue-router": "^4.0.6"
},
"devDependencies": {
+ "@emotion/server": "^11.0.0",
"@iconify/json": "^1.1.312",
- "@vitejs/plugin-vue": "^1.1.4",
- "@vue/compiler-sfc": "^3.0.6",
- "@vue/server-renderer": "^3.0.6",
- "markdown-it-prism": "^2.1.4",
+ "@vitejs/plugin-vue": "^1.2.1",
+ "@vue/compiler-sfc": "^3.0.11",
+ "@vue/server-renderer": "^3.0.11",
+ "@vueuse/head": "^0.5.1",
+ "markdown-it-prism": "^2.1.6",
"prismjs": "^1.23.0",
"typescript": "^4.1.3",
- "vite": "^2.0.1",
- "vite-plugin-icons": "^0.3.2",
+ "vite": "2.1.5",
+ "vite-plugin-components": "^0.8.3",
+ "vite-plugin-icons": "^0.3.3",
"vite-plugin-md": "^0.6.0",
- "vite-plugin-pages": "^0.5.1",
- "vite-ssg": "^0.8.11"
+ "vite-plugin-mdx-vue": "^1.1.3",
+ "vite-plugin-pages": "^0.9.2",
+ "vite-ssg": "^0.9.2"
}
}
diff --git a/website/src/assets/custom-theme.ts b/website/src/assets/custom-theme.ts
index c7261012..cbdd97f3 100644
--- a/website/src/assets/custom-theme.ts
+++ b/website/src/assets/custom-theme.ts
@@ -15,6 +15,10 @@ export default extendTheme({
800: '#21533B',
900: '#143223',
},
+ discord: {
+ primary: '#7289DA',
+ accessible: '#5570D2',
+ },
},
fonts: {
heading: `Inter, sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`,
diff --git a/website/src/components/Navbar.vue b/website/src/components/Navbar.vue
index eb81aca0..e4443f3f 100644
--- a/website/src/components/Navbar.vue
+++ b/website/src/components/Navbar.vue
@@ -76,8 +76,8 @@
aria-label="Join Discord channel"
target="_blank"
rel="noopener noreferrer"
- href="https://github.com/chakra-ui/chakra-ui-vue"
- icon="message-circle"
+ href="https://discord.gg/sq2Kp6x"
+ icon="discord"
/>
diff --git a/website/src/components/home/DiscordHero.vue b/website/src/components/home/DiscordHero.vue
new file mode 100644
index 00000000..28b9656a
--- /dev/null
+++ b/website/src/components/home/DiscordHero.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ Connect with the Chakra community
+
+
+ Feel free to ask questions, report issues, and meet new people.
+
+
+
+
+
+ Join the #Chakra Discord!
+
+
+
+
+
+
diff --git a/website/src/components/home/Footer.vue b/website/src/components/home/Footer.vue
index e9ab730a..a89f96d2 100644
--- a/website/src/components/home/Footer.vue
+++ b/website/src/components/home/Footer.vue
@@ -30,11 +30,11 @@
aria-label="Join Discord channel"
target="_blank"
rel="noopener noreferrer"
- href="https://github.com/chakra-ui/chakra-ui-vue"
+ href="https://discord.gg/sq2Kp6x"
:_hover="{ color: 'vue.500' }"
mx="4"
>
-
+