Skip to content

Commit

Permalink
feat(grid): add <SGrid> component (#313)
Browse files Browse the repository at this point in the history
Co-authored-by: Cue <[email protected]>
  • Loading branch information
kiaking and cuebit authored Jul 19, 2023
1 parent f628b8e commit ec92c7b
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function sidebar(): DefaultTheme.SidebarItem[] {
{ text: 'SButtonGroup', link: '/components/button-group' },
{ text: 'SCard', link: '/components/card' },
{ text: 'SFragment', link: '/components/fragment' },
{ text: 'SGrid', link: '/components/grid' },
{ text: 'SInputAddon', link: '/components/input-addon' },
{ text: 'SInputCheckbox', link: '/components/input-checkbox' },
{ text: 'SInputCheckboxes', link: '/components/input-checkboxes' },
Expand Down
6 changes: 6 additions & 0 deletions docs/.vitepress/theme/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@ textarea {
.max-w-192 { max-width: 192px; }
.max-w-256 { max-width: 256px; }

.h-64 { height: 64px; }

.text-14 { font-size: 14px !important; }

.bg-info { background-color: var(--c-info); }

.rounded-6 { border-radius: 6px; }
128 changes: 128 additions & 0 deletions docs/components/grid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<script setup lang="ts">
import SGrid from 'sefirot/components/SGrid.vue'
import SGridItem from 'sefirot/components/SGridItem.vue'
</script>

# SGrid

`<SGrid>` is a utility component to handle CSS grid layout.

<Showcase
path="/components/SGrid.vue"
story="/stories-components-sgrid-01-playground-story-vue"
>
<SGrid cols="4" gap="24">
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
</SGrid>
</Showcase>
## Usage

Use `<SGrid>` and `<SGridItem>` component to construct the grid structure.

```vue
<script setup lang="ts">
import SGrid from '@globalbrain/sefirot/lib/components/SGrid.vue'
import SGridItem from '@globalbrain/sefirot/lib/components/SGridItem.vue'
</script>
<template>
<SGrid cols="4">
<SGridItem span="2">
<div>...</div>
</SGridItem>
<SGridItem span="1">
<div>...</div>
</SGridItem>
</SGrid>
</template>
```

You have the flexibility to define the `:cols` and `:gap` props when using the `<SGrid>` component, allowing you to have full control over the grid layout.

The `cols` prop serves as a shorthand for the `grid-template-columns` CSS property. Similarly, the `gap` prop acts as a shorthand for the `gap` CSS property.

Notably, the `gap` prop automatically appends the required `px` unit to the value, so there is no need to include it explicitly.

```ts
interface Props {
cols?: string | number
gap?: string | number
}
```

```vue-html
<SGrid cols="4" gap="48">
...
</SGrid>
```

Once you have defined the grid layout, you can use the `span` prop on the `<SGridItem>` component to define the number of columns that the item should span. The `span` prop serves as a shorthand for the `grid-column` CSS property.

```ts
interface Props {
span?: string | number
}
```

```vue-html
<SGrid cols="4" gap="48">
<SGridItem span="2">...</SGridItem>
<SGridItem span="2">...</SGridItem>
</SGrid>
```

## Responsive design

If you need to adjust the overall grid layout based on different screen sizes or other conditions, it is recommended to use CSS instead of props. The props are provided for convenience, but for more complex layout structures, CSS should be used.

```vue
<template>
<SGrid class="grid">
<SGridItem class="name">...</SGridItem>
<SGridItem class="age">...</SGridItem>
<SGridItem class="email">...</SGridItem>
</SGrid>
</template>
<style scoped>
.grid {
grid-template-columns: 1fr;
gap: 16px;
}
@media (min-width: 768px) {
.grid {
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.email {
grid-column: span 2;
}
}
</style>
```
27 changes: 27 additions & 0 deletions lib/components/SGrid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
cols?: string | number
gap?: string | number
}>()
const styles = computed(() => {
return {
gridTemplateColumns: `repeat(${props.cols ?? 1}, minmax(0, 1fr))`,
gap: `${props.gap ?? 0}px`
}
})
</script>

<template>
<div class="SGrid" :style="styles">
<slot />
</div>
</template>

<style scoped lang="postcss">
.SGrid {
display: grid;
}
</style>
14 changes: 14 additions & 0 deletions lib/components/SGridItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
defineProps<{
span?: string | number
}>()
</script>

<template>
<div
class="SGridItem"
:style="{ gridColumn: span ? `span ${span}` : undefined }"
>
<slot />
</div>
</template>
12 changes: 12 additions & 0 deletions lib/mixins/Grid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type App } from 'vue'
import SGrid from '../components/SGrid.vue'
import SGridItem from '../components/SGridItem.vue'

export function mixin(app: App): void {
app.mixin({
components: {
SGrid,
SGridItem
}
})
}
40 changes: 40 additions & 0 deletions stories/components/SGrid.01_Playground.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
import SGrid from 'sefirot/components/SGrid.vue'
import SGridItem from 'sefirot/components/SGridItem.vue'
const title = 'Components / SGrid / 01. Playground'
const docs = '/components/grid'
</script>

<template>
<Story :title="title" source="Not available" auto-props-disabled>
<Board :title="title" :docs="docs">
<SGrid cols="4" gap="24">
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
<SGridItem>
<div class="h-64 rounded-6 bg-info" />
</SGridItem>
</SGrid>
</Board>
</Story>
</template>
6 changes: 6 additions & 0 deletions stories/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ body {
.font-600 { font-weight: 600; }
.font-700 { font-weight: 700; }

.bg-info { background-color: var(--c-info); }

.rounded-6 { border-radius: 6px; }

.flex {
display: flex;
}
Expand All @@ -61,3 +65,5 @@ body {
.max-w-192 { max-width: 192px; }
.max-w-256 { max-width: 256px; }
.max-w-512 { max-width: 512px; }

.h-64 { height: 64px; }
19 changes: 19 additions & 0 deletions tests/components/SGrid.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { mount } from '@vue/test-utils'
import SGrid from 'sefirot/components/SGrid.vue'
import SGridItem from 'sefirot/components/SGridItem.vue'

describe('components/SGrid', () => {
describe('SGrid', () => {
test('renders `SGrid` element', () => {
const wrapper = mount(SGrid)
expect(wrapper.find('.SGrid').exists()).toBe(true)
})
})

describe('SGridItem', () => {
test('renders `SGridItem` element', () => {
const wrapper = mount(SGridItem)
expect(wrapper.find('.SGridItem').exists()).toBe(true)
})
})
})

0 comments on commit ec92c7b

Please sign in to comment.