diff --git a/packages/docs/modules/page-config/blocks/api/component-parser/index.ts b/packages/docs/modules/page-config/blocks/api/component-parser/index.ts index 69a5fd3cc9..ec9ce173b2 100644 --- a/packages/docs/modules/page-config/blocks/api/component-parser/index.ts +++ b/packages/docs/modules/page-config/blocks/api/component-parser/index.ts @@ -107,7 +107,7 @@ function convertComponentPropToApiDocs(propName: T, propOption name: propName, global: false, description: '', - type: types.join(' | '), + types: types.join(' | '), required: !!propOptionsRecord[propName].required, default: getDefaultValue(propOptionsRecord[propName], types), } as any diff --git a/packages/docs/page-config/navigationRoutes.ts b/packages/docs/page-config/navigationRoutes.ts index 5c0d2f9dad..21c758df11 100644 --- a/packages/docs/page-config/navigationRoutes.ts +++ b/packages/docs/page-config/navigationRoutes.ts @@ -267,9 +267,16 @@ export const navigationRoutes: NavigationRoute[] = [ name: "color-input", displayName: "Color Input", }, - + { category: 'Layout', + name: 'layout', + displayName: 'Layout', + meta: { + badge: navigationBadge.new('1.7.2'), + } + }, + { name: 'aspect-ratio', displayName: 'Aspect Ratio', meta: { diff --git a/packages/docs/page-config/ui-elements/layout/examples/Absolute.vue b/packages/docs/page-config/ui-elements/layout/examples/Absolute.vue new file mode 100644 index 0000000000..e7b0cb9be4 --- /dev/null +++ b/packages/docs/page-config/ui-elements/layout/examples/Absolute.vue @@ -0,0 +1,50 @@ + + + \ No newline at end of file diff --git a/packages/docs/page-config/ui-elements/layout/examples/AllSlots.vue b/packages/docs/page-config/ui-elements/layout/examples/AllSlots.vue new file mode 100644 index 0000000000..f43b73bd49 --- /dev/null +++ b/packages/docs/page-config/ui-elements/layout/examples/AllSlots.vue @@ -0,0 +1,94 @@ + + + \ No newline at end of file diff --git a/packages/docs/page-config/ui-elements/layout/examples/Default.vue b/packages/docs/page-config/ui-elements/layout/examples/Default.vue new file mode 100644 index 0000000000..1144a6285a --- /dev/null +++ b/packages/docs/page-config/ui-elements/layout/examples/Default.vue @@ -0,0 +1,47 @@ + + + \ No newline at end of file diff --git a/packages/docs/page-config/ui-elements/layout/examples/MobileFriendly.vue b/packages/docs/page-config/ui-elements/layout/examples/MobileFriendly.vue new file mode 100644 index 0000000000..934edf5793 --- /dev/null +++ b/packages/docs/page-config/ui-elements/layout/examples/MobileFriendly.vue @@ -0,0 +1,52 @@ + + + \ No newline at end of file diff --git a/packages/docs/page-config/ui-elements/layout/examples/Order.vue b/packages/docs/page-config/ui-elements/layout/examples/Order.vue new file mode 100644 index 0000000000..9e03351ff0 --- /dev/null +++ b/packages/docs/page-config/ui-elements/layout/examples/Order.vue @@ -0,0 +1,92 @@ + + + \ No newline at end of file diff --git a/packages/docs/page-config/ui-elements/layout/index.ts b/packages/docs/page-config/ui-elements/layout/index.ts new file mode 100644 index 0000000000..65e37edcc2 --- /dev/null +++ b/packages/docs/page-config/ui-elements/layout/index.ts @@ -0,0 +1,67 @@ +export default definePageConfig({ + blocks: [ + block.title('Layout'), + block.paragraph('Component is used for building App layout. It is based on CSS Grid and provides a simple API for building complex layouts.'), + block.paragraph(` +VaLayout component is used in pair with [VaSidebar](/ui-elements/sidebar)[[target=_blank]], [VaNavbar](/ui-elements/navbar)[[target=_blank]], VaFooter components. + `), + + block.example('Default', { + title: 'Default usage', + description: 'Default usage of Layout component.', + }), + block.example('AllSlots', { + title: 'All slots', + description: 'Layout component has 4 slots: top, right, bottom, left, content (or default). You can use them to place your content.', + }), + block.example('Order', { + title: 'Rendering order', + description: 'You can use order area attribute to change order of slots. By default, order is 0. If order of one area is higher than another, it will be rendered on top of it. For example, if you set `top` order to `0` and `left` to `1`, left will take area from `top`. It is easier to play with it in example bellow.', + }), + block.example('Absolute', { + title: 'Absolute', + description: 'You can use absolute area attribute to make area absolute. It will be rendered on top of other areas and overflow them. Absolute respect order prop.', + }), + block.example('MobileFriendly', { + title: 'Mobile friendly', + description: 'It is recommended to make `left` and `right` areas absolute on mobile devices. You can use `absolute` prop to do it in pair with [useBreakpoint](http://localhost:3000/services/breakpoints)[[target=_blank]] composable.', + }), + + block.subtitle('Accessibility'), + block.paragraph(` +Layout component is not handling accessibility by default. + +You should always wrap content in slot with \`
\` to make it accessible. +Make sure you have \`main\` role on your main content in \`content\` slot, +\`aside\` in \`left\`, \`right\` slots, \`header\` in \`top\` slot and \`footer\` in \`bottom\` slot. +More about landmarks you can read [here](https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/)[[target=_blank]]. + +Notice that if you're using components like VaSidebar, VaHeader the correct role will be added automatically. + `), + + block.api('VaLayout', { + props: { + top: 'AreaConfig for top slot', + right: 'AreaConfig for right slot', + bottom: 'AreaConfig for bottom slot', + left: 'AreaConfig for left slot', + } + }, + { + props: { + top: { + types: `{ absolute?: boolean, order?: number }` + }, + bottom: { + types: `{ absolute?: boolean, order?: number }` + }, + left: { + types: `{ absolute?: boolean, order?: number }` + }, + right: { + types: `{ absolute?: boolean, order?: number }` + } + } + }) + ] +}) \ No newline at end of file diff --git a/packages/docs/page-config/ui-elements/layout/translations/en.json b/packages/docs/page-config/ui-elements/layout/translations/en.json new file mode 100644 index 0000000000..03553211af --- /dev/null +++ b/packages/docs/page-config/ui-elements/layout/translations/en.json @@ -0,0 +1,3 @@ +{ + "title": "Layout" +} \ No newline at end of file diff --git a/packages/nuxt/src/config/components.ts b/packages/nuxt/src/config/components.ts index afbb1b11d8..00ab8c3fa5 100644 --- a/packages/nuxt/src/config/components.ts +++ b/packages/nuxt/src/config/components.ts @@ -43,6 +43,7 @@ export default [ 'VaInfiniteScroll', 'VaInnerLoading', 'VaInput', + 'VaLayout', 'VaList', 'VaListItem', 'VaListItemLabel', diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index c247b1e0b0..5381318123 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -50,6 +50,8 @@ export * from './va-icon' export * from './va-image' export * from './va-infinite-scroll' export * from './va-inner-loading' +export * from './va-input' +export * from './va-layout' export * from './va-list' export * from './va-modal' export * from './va-navbar' diff --git a/packages/ui/src/components/va-layout/VaLayout.demo.vue b/packages/ui/src/components/va-layout/VaLayout.demo.vue new file mode 100644 index 0000000000..01901ff843 --- /dev/null +++ b/packages/ui/src/components/va-layout/VaLayout.demo.vue @@ -0,0 +1,366 @@ + + + + + diff --git a/packages/ui/src/components/va-layout/VaLayout.vue b/packages/ui/src/components/va-layout/VaLayout.vue new file mode 100644 index 0000000000..1cd960e0d5 --- /dev/null +++ b/packages/ui/src/components/va-layout/VaLayout.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/packages/ui/src/components/va-layout/hooks/useGridTemplateArea.ts b/packages/ui/src/components/va-layout/hooks/useGridTemplateArea.ts new file mode 100644 index 0000000000..559406ac6b --- /dev/null +++ b/packages/ui/src/components/va-layout/hooks/useGridTemplateArea.ts @@ -0,0 +1,46 @@ +import { computed } from 'vue' +import { type LayoutProps } from './useLayout' + +const areaIndexes = { + top: [0, 1, 2], + left: [0, 3, 6], + right: [2, 5, 8], + bottom: [6, 7, 8], +} + +export type AreaName = 'top' | 'left' | 'right' | 'bottom' +const areaElements = (['left', 'right', 'top', 'bottom'] as const) + +export const useGridTemplateArea = (props: LayoutProps) => { + const sort = () => { + return [...areaElements].sort((a, b) => { + return (props[a].order ?? 0) - (props[b].order ?? 0) + }) + } + + const applyTemplate = (template: string[], areaIndexes: number[], areaName: AreaName) => { + areaIndexes.forEach((index) => { + template[index] = areaName + }) + } + + return computed(() => { + const sorted = sort() + + const template = [ + '.', '.', '.', + '.', '.', '.', + '.', '.', '.', + ].map(() => 'content') + + sorted.forEach((areaName) => { + applyTemplate(template, areaIndexes[areaName], areaName) + }) + + return [ + '"' + template.slice(0, 3).join(' ') + '"', + '"' + template.slice(3, 6).join(' ') + '"', + '"' + template.slice(6, 9).join(' ') + '"', + ].join(' ') + }) +} diff --git a/packages/ui/src/components/va-layout/hooks/useLayout.ts b/packages/ui/src/components/va-layout/hooks/useLayout.ts new file mode 100644 index 0000000000..92d3882a99 --- /dev/null +++ b/packages/ui/src/components/va-layout/hooks/useLayout.ts @@ -0,0 +1,30 @@ +import { ExtractPropTypes, PropType } from 'vue' + +// Unwrap type, e.g.: removes name from type alias and returns the type +type UnwrapType = true extends boolean ? T : never + +type AreaConfig = UnwrapType<{ + absolute?: boolean, + order?: number, +}> + +export const useLayoutProps = { + top: { + type: Object as PropType, + default: () => ({}), + }, + right: { + type: Object as PropType, + default: () => ({}), + }, + left: { + type: Object as PropType, + default: () => ({}), + }, + bottom: { + type: Object as PropType, + default: () => ({}), + }, +} + +export type LayoutProps = ExtractPropTypes diff --git a/packages/ui/src/components/va-layout/index.ts b/packages/ui/src/components/va-layout/index.ts new file mode 100644 index 0000000000..a3e35bdef3 --- /dev/null +++ b/packages/ui/src/components/va-layout/index.ts @@ -0,0 +1,4 @@ +import { withConfigTransport } from '../../services/config-transport' +import _VaLayout from './VaLayout.vue' + +export const VaLayout = withConfigTransport(_VaLayout) diff --git a/packages/ui/src/services/vue-plugin/components.ts b/packages/ui/src/services/vue-plugin/components.ts index dd9113928a..d7f8ae0031 100644 --- a/packages/ui/src/services/vue-plugin/components.ts +++ b/packages/ui/src/services/vue-plugin/components.ts @@ -44,6 +44,7 @@ export { VaInfiniteScroll, VaInnerLoading, VaInput, + VaLayout, VaList, VaListItem, VaListItemLabel,