Skip to content

Commit

Permalink
feat(grid): add <SGrid> component
Browse files Browse the repository at this point in the history
  • Loading branch information
kiaking committed Jul 12, 2023
1 parent 4546cab commit a1f6cb5
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

When you need to change the overall grid layout depending on the screen size, use plain CSS instead of using props. Those props are there for convenience, and you may always CSS to define complex layout structure.

```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 a1f6cb5

Please sign in to comment.