mirror of
https://github.com/directus/directus.git
synced 2026-01-27 05:28:06 -05:00
[WIP] Card component (#115)
* Start on card component * Add stories for card * Add card actions component * Right align actions * Update storybook * Update readme, tweak dialog card spacing * Fix tests * Move type styles into mixins
This commit is contained in:
4
src/components/v-card/index.ts
Normal file
4
src/components/v-card/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import VCard from './v-card.vue';
|
||||
|
||||
export { VCard };
|
||||
export default VCard;
|
||||
167
src/components/v-card/readme.md
Normal file
167
src/components/v-card/readme.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Card
|
||||
|
||||
Renders a card. A card is nothing but a v-sheet with predefined building blocks to enforce consistency.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<v-card>
|
||||
<v-card-title>Hello, world!</v-card-title>
|
||||
<v-card-subtitle>This is a card</v-card-subtitle>
|
||||
<v-card-text>Consectetur enim ullamco sint sit deserunt proident consectetur.</v-card-test>
|
||||
<v-card-actions>
|
||||
<v-button>Save</v-button>
|
||||
<v-card-actions>
|
||||
</v-card>
|
||||
```
|
||||
|
||||
Cards can be used to consistently style dialogs:
|
||||
|
||||
```html
|
||||
<v-dialog>
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Show dialog</v-button>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-card-title>Are you sure you want to delete 1 item?</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-button secondary outlined>Cancel</v-button>
|
||||
<v-button>Yes</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
```
|
||||
|
||||
## Props
|
||||
| Prop | Description | Default |
|
||||
|------------|---------------------------------------------------|---------|
|
||||
| `disabled` | Disable the card, prevents all cursor interaction | `false` |
|
||||
| `tile` | Render without rounded corners | `false` |
|
||||
|
||||
## Events
|
||||
n/a
|
||||
|
||||
## Slots
|
||||
| Slot | Description | Data |
|
||||
|-----------|-------------|------|
|
||||
| _default_ | | |
|
||||
|
||||
## CSS Variables
|
||||
| Variable | Default |
|
||||
|-----------------------------|--------------------|
|
||||
| `--v-card-min-width` | `none` |
|
||||
| `--v-card-max-width` | `400px` |
|
||||
| `--v-card-min-height` | `none` |
|
||||
| `--v-card-max-height` | `none` |
|
||||
| `--v-card-padding` | `16px` |
|
||||
| `--v-card-background-color` | `var(--highlight)` |
|
||||
|
||||
---
|
||||
|
||||
# Card Title
|
||||
|
||||
Functional component that enforces consistent styling.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<v-card-title>Hello, world!</v-card-title>
|
||||
```
|
||||
|
||||
## Props
|
||||
n/a
|
||||
|
||||
## Events
|
||||
n/a
|
||||
|
||||
## Slots
|
||||
| Slot | Description | Data |
|
||||
|-----------|-------------|------|
|
||||
| _default_ | | |
|
||||
|
||||
## CSS Variables
|
||||
n/a
|
||||
|
||||
---
|
||||
|
||||
# Card Subtitle
|
||||
|
||||
Functional component that enforces consistent styling.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<v-card-subtitle>Hello from the subtitle</v-card-subtitle>
|
||||
```
|
||||
|
||||
## Props
|
||||
n/a
|
||||
|
||||
## Events
|
||||
n/a
|
||||
|
||||
## Slots
|
||||
| Slot | Description | Data |
|
||||
|-----------|-------------|------|
|
||||
| _default_ | | |
|
||||
|
||||
## CSS Variables
|
||||
n/a
|
||||
|
||||
---
|
||||
|
||||
# Card Text
|
||||
|
||||
Functional component that enforces consistent styling.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<v-card-text>Nisi anim deserunt Lorem reprehenderit laborum.</v-card-text>
|
||||
```
|
||||
|
||||
## Props
|
||||
n/a
|
||||
|
||||
## Events
|
||||
n/a
|
||||
|
||||
## Slots
|
||||
| Slot | Description | Data |
|
||||
|-----------|-------------|------|
|
||||
| _default_ | | |
|
||||
|
||||
## CSS Variables
|
||||
n/a
|
||||
|
||||
---
|
||||
|
||||
# Card Actions
|
||||
|
||||
Functional component that enforces consistent styling.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<v-card-actions>
|
||||
<v-button />
|
||||
<v-button />
|
||||
</v-card-actions>
|
||||
```
|
||||
|
||||
## Props
|
||||
n/a
|
||||
|
||||
## Events
|
||||
n/a
|
||||
|
||||
## Slots
|
||||
| Slot | Description | Data |
|
||||
|-----------|-------------|------|
|
||||
| _default_ | | |
|
||||
|
||||
## CSS Variables
|
||||
n/a
|
||||
|
||||
---
|
||||
15
src/components/v-card/v-card-actions.vue
Normal file
15
src/components/v-card/v-card-actions.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template functional>
|
||||
<div class="v-card-actions"><slot /></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: var(--v-card-padding);
|
||||
|
||||
& ::v-deep > .v-button + .v-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
src/components/v-card/v-card-subtitle.vue
Normal file
15
src/components/v-card/v-card-subtitle.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template function>
|
||||
<div class="v-card-subtitle"><slot /></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixins/type-styles';
|
||||
|
||||
.v-card-subtitle {
|
||||
margin-top: -16px;
|
||||
padding: 16px;
|
||||
padding-top: 0;
|
||||
|
||||
@include type-card-subtitle;
|
||||
}
|
||||
</style>
|
||||
14
src/components/v-card/v-card-text.vue
Normal file
14
src/components/v-card/v-card-text.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template functional>
|
||||
<div class="v-card-text"><slot /></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixins/type-styles';
|
||||
|
||||
.v-card-text {
|
||||
padding: var(--v-card-padding);
|
||||
padding-top: 0;
|
||||
|
||||
@include type-card-text;
|
||||
}
|
||||
</style>
|
||||
17
src/components/v-card/v-card-title.vue
Normal file
17
src/components/v-card/v-card-title.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template functional>
|
||||
<div class="v-card-title"><slot /></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixins/type-styles';
|
||||
|
||||
.v-card-title {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: var(--v-card-padding);
|
||||
word-break: break-all;
|
||||
|
||||
@include type-card-title;
|
||||
}
|
||||
</style>
|
||||
93
src/components/v-card/v-card.story.ts
Normal file
93
src/components/v-card/v-card.story.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { withKnobs, boolean } from '@storybook/addon-knobs';
|
||||
import Vue from 'vue';
|
||||
import VCard from './v-card.vue';
|
||||
import VCardTitle from './v-card-title.vue';
|
||||
import VCardSubtitle from './v-card-subtitle.vue';
|
||||
import VCardText from './v-card-text.vue';
|
||||
import VCardActions from './v-card-actions.vue';
|
||||
import markdown from './readme.md';
|
||||
import withPadding from '../../../.storybook/decorators/with-padding';
|
||||
import { defineComponent, ref } from '@vue/composition-api';
|
||||
|
||||
Vue.component('v-card', VCard);
|
||||
Vue.component('v-card-title', VCardTitle);
|
||||
Vue.component('v-card-subtitle', VCardSubtitle);
|
||||
Vue.component('v-card-text', VCardText);
|
||||
Vue.component('v-card-actions', VCardActions);
|
||||
|
||||
export default {
|
||||
title: 'Components / Card',
|
||||
decorators: [withKnobs, withPadding],
|
||||
parameters: {
|
||||
notes: markdown
|
||||
}
|
||||
};
|
||||
|
||||
export const basic = () =>
|
||||
defineComponent({
|
||||
props: {
|
||||
disabled: {
|
||||
default: boolean('Disabled', false)
|
||||
},
|
||||
tile: {
|
||||
default: boolean('Tile', false)
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<v-card :disabled="disabled" :tile="tile">
|
||||
<v-card-title>Hello World!</v-card-title>
|
||||
<v-card-subtitle>This is the subtitle</v-card-subtitle>
|
||||
<v-card-text>Black ray-bans, you know she's with the band. Such a sight to see and it's all for me. Heaven is jealous of our love, angels are crying from up above. Turned the bedroom into a fair (a fair!) It’s in the palm of your hand now baby.</v-card-text>
|
||||
</v-card>
|
||||
`
|
||||
});
|
||||
|
||||
export const withImage = () =>
|
||||
defineComponent({
|
||||
template: `
|
||||
<v-card :disabled="disabled" :tile="tile">
|
||||
<img src="https://images.unsplash.com/photo-1581587118469-a117038c0249?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixlib=rb-1.2.1&q=80&w=800" width="800" height="600" style="width: 100%; display: block; height: auto; "/>
|
||||
<v-card-title>Hello World!</v-card-title>
|
||||
<v-card-subtitle>This is the subtitle</v-card-subtitle>
|
||||
<v-card-text>Black ray-bans, you know she's with the band. Such a sight to see and it's all for me. Heaven is jealous of our love, angels are crying from up above. Turned the bedroom into a fair (a fair!) It’s in the palm of your hand now baby.</v-card-text>
|
||||
</v-card>
|
||||
`
|
||||
});
|
||||
|
||||
export const withActions = () =>
|
||||
defineComponent({
|
||||
template: `
|
||||
<v-card :disabled="disabled" :tile="tile">
|
||||
<v-card-title>Hello World!</v-card-title>
|
||||
<v-card-subtitle>This is the subtitle</v-card-subtitle>
|
||||
<v-card-text>Black ray-bans, you know she's with the band. Such a sight to see and it's all for me. Heaven is jealous of our love, angels are crying from up above. Turned the bedroom into a fair (a fair!) It’s in the palm of your hand now baby.</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button small secondary>Click me</v-button>
|
||||
<v-button small>Click me</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
`
|
||||
});
|
||||
|
||||
export const asDialog = () =>
|
||||
defineComponent({
|
||||
setup() {
|
||||
const active = ref(false);
|
||||
return { active };
|
||||
},
|
||||
template: `
|
||||
<v-dialog v-model="active">
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Show dialog</v-button>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>Are you sure you want to quit?</v-card-title>
|
||||
<v-card-text>All unsaved changes will be lost.</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button secondary @click="active = !active">Cancel</v-button>
|
||||
<v-button @click="active = !active">Quit</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
`
|
||||
});
|
||||
13
src/components/v-card/v-card.test.ts
Normal file
13
src/components/v-card/v-card.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import VCard from './v-card.vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
describe('Components / Card', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VCard, { localVue });
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
61
src/components/v-card/v-card.vue
Normal file
61
src/components/v-card/v-card.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="v-card" :class="{ disabled, tile }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tile: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-card {
|
||||
--v-card-min-width: none;
|
||||
--v-card-max-width: 400px;
|
||||
--v-card-min-height: none;
|
||||
--v-card-max-height: none;
|
||||
--v-card-padding: 16px;
|
||||
--v-card-background-color: var(--highlight);
|
||||
|
||||
min-width: var(--v-card-min-width);
|
||||
max-width: var(--v-card-max-width);
|
||||
min-height: var(--v-card-min-height);
|
||||
max-height: var(--v-card-max-height);
|
||||
background-color: var(--v-card-background-color);
|
||||
border-radius: var(--input-border-radius);
|
||||
|
||||
& > :first-child {
|
||||
border-top-left-radius: var(--input-border-radius);
|
||||
border-top-right-radius: var(--input-border-radius);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
|
||||
& > * {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
&.tile {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -68,6 +68,11 @@ export default defineComponent({
|
||||
transition: opacity var(--medium) var(--transition);
|
||||
pointer-events: none;
|
||||
|
||||
.v-card {
|
||||
--v-card-min-width: 400px;
|
||||
--v-card-padding: 24px;
|
||||
}
|
||||
|
||||
.v-sheet {
|
||||
--v-sheet-padding: 24px;
|
||||
}
|
||||
|
||||
@@ -76,3 +76,26 @@
|
||||
font-family: var(--family-sans-serif);
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
@mixin type-card-title {
|
||||
color: var(--heading-text-color);
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
line-height: 2rem;
|
||||
letter-spacing: 0.0125em;
|
||||
}
|
||||
|
||||
@mixin type-card-subtitle {
|
||||
color: var(--foreground-color-secondary);
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.375rem;
|
||||
letter-spacing: 0.007em;
|
||||
}
|
||||
|
||||
@mixin type-card-text {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.375rem;
|
||||
letter-spacing: 0.007em;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ import VueCompositionAPI, { ref } from '@vue/composition-api';
|
||||
import DrawerDetail from './drawer-detail.vue';
|
||||
import * as GroupableComposition from '@/compositions/groupable/groupable';
|
||||
import VIcon from '@/components/v-icon';
|
||||
import TransitionExpand from '@/components/transition/expand';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
localVue.component('transition-expand', TransitionExpand);
|
||||
|
||||
describe('Drawer Detail', () => {
|
||||
it('Uses the useGroupable composition', () => {
|
||||
|
||||
Reference in New Issue
Block a user