mirror of
https://github.com/directus/directus.git
synced 2026-02-17 12:41:40 -05:00
Migrate existing (finished) base components
This commit is contained in:
4
src/components/v-button/index.ts
Normal file
4
src/components/v-button/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import VButton from './v-button.vue';
|
||||
|
||||
export { VButton };
|
||||
export default VButton;
|
||||
86
src/components/v-button/v-button.readme.md
Normal file
86
src/components/v-button/v-button.readme.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Button
|
||||
|
||||
```html
|
||||
<v-button>Click me!</v-button>
|
||||
```
|
||||
|
||||
## Sizes
|
||||
|
||||
The button component supports the following sizes through the use of props:
|
||||
|
||||
* x-small
|
||||
* small
|
||||
* (default)
|
||||
* large
|
||||
* x-large
|
||||
|
||||
Alternatively, you can force the font-size through the `size` prop:
|
||||
|
||||
```html
|
||||
<v-button :size="64">Click me!</v-button>
|
||||
```
|
||||
|
||||
## Colors
|
||||
|
||||
You can set the color, background color, hover color, and background hover color with the `color`, `background-color`, `hover-color`, and `hover-background-color` props respectively:
|
||||
|
||||
```html
|
||||
<v-button
|
||||
color="--red"
|
||||
background-color="--red-50"
|
||||
hover-color="--white"
|
||||
hover-background-color="--red"
|
||||
>
|
||||
Click me
|
||||
</v-button>
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
The only event that can be added to the button is the `click` event:
|
||||
|
||||
```html
|
||||
<v-button @click="sayHi">Hello!</v-button>
|
||||
```
|
||||
|
||||
## Loading
|
||||
|
||||
The button has a loading state that can be enabled with the `loading` prop. By default, the button will render a `v-spinner`. You can override what's being shown during the loading state by using the `#loading` slot:
|
||||
|
||||
```html
|
||||
<v-button>
|
||||
<template #loading>
|
||||
... Almost done ...
|
||||
</template>
|
||||
</v-button>
|
||||
```
|
||||
|
||||
The loading slot is rendered _on top_ of the content that was there before. Make sure that your loading content doesn't exceed the size of the default state content. This restriction is put in place to prevent jumps when going from and to the loading state.
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Description | Default |
|
||||
|--------------------------|---------------------------------------------------------------------------|-------------------------------------------|
|
||||
| `block` | Enable ull width (display block) | `false` |
|
||||
| `icon` | Remove padding / min-width. Meant to be used with just an icon as content | `false` |
|
||||
| `outlined` | No background | `false` |
|
||||
| `rounded` | Enable rounded corners | `false` |
|
||||
| `color` | Text / icon color | `--button-primary-text-color` |
|
||||
| `hover-color` | Text / icon color on hover | `--button-primary-text-color` |
|
||||
| `background-color` | Button color | `--button-primary-background-color` |
|
||||
| `hover-background-color` | Button color on hover | `--button-primary-background-color-hover` |
|
||||
| `type` | HTML `type` attribute | `button` |
|
||||
| `disabled` | Disabled state | `false` |
|
||||
| `loading` | Loading state | `false` |
|
||||
| `width` | Width in px | -- |
|
||||
| `size` | Size of the text in the button in px | -- |
|
||||
| `x-small` | Render extra small | `false` |
|
||||
| `small` | Render small | `false` |
|
||||
| `large` | Render large | `false` |
|
||||
| `x-large` | Render extra large | `false` |
|
||||
|
||||
## Slots
|
||||
|
||||
| Slot | Description |
|
||||
|-----------|----------------------------------------------|
|
||||
| `loading` | Content that's rendered during loading state |
|
||||
279
src/components/v-button/v-button.story.ts
Normal file
279
src/components/v-button/v-button.story.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
import { withKnobs, text, boolean, number, optionsKnob as options } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import Vue from 'vue';
|
||||
import VButton from './v-button.vue';
|
||||
import VIcon from '../v-icon/';
|
||||
import markdown from './v-button.readme.md';
|
||||
|
||||
Vue.component('v-button', VButton);
|
||||
Vue.component('v-icon', VIcon);
|
||||
|
||||
export default {
|
||||
title: 'Components / Button',
|
||||
component: VButton,
|
||||
decorators: [withKnobs],
|
||||
parameters: {
|
||||
notes: markdown
|
||||
}
|
||||
};
|
||||
|
||||
export const withText = () => ({
|
||||
methods: { onClick: action('click') },
|
||||
props: {
|
||||
text: {
|
||||
default: text('Text in button', 'Click me')
|
||||
},
|
||||
block: {
|
||||
default: boolean('Block', false, 'Button')
|
||||
},
|
||||
rounded: {
|
||||
default: boolean('Rounded', false, 'Button')
|
||||
},
|
||||
icon: {
|
||||
default: boolean('Icon mode', false, 'Button')
|
||||
},
|
||||
outlined: {
|
||||
default: boolean('Outlined', false, 'Button')
|
||||
},
|
||||
type: {
|
||||
default: text('Type attribute', 'button', 'Button')
|
||||
},
|
||||
loading: {
|
||||
default: boolean('Loading', false, 'Button')
|
||||
},
|
||||
width: {
|
||||
default: number('Width', 0, {}, 'Button')
|
||||
},
|
||||
size: {
|
||||
default: options(
|
||||
'Size',
|
||||
{
|
||||
'Extra Small': 'xSmall',
|
||||
Small: 'small',
|
||||
'(default)': 'default',
|
||||
Large: 'large',
|
||||
'Extra Large': 'xLarge'
|
||||
},
|
||||
'default',
|
||||
{
|
||||
display: 'select'
|
||||
},
|
||||
'Button'
|
||||
)
|
||||
},
|
||||
disabled: {
|
||||
default: boolean('Disabled', false, 'Button')
|
||||
},
|
||||
color: {
|
||||
default: text('Color', '--button-primary-text-color', 'Colors')
|
||||
},
|
||||
backgroundColor: {
|
||||
default: text('Background Color', '--button-primary-background-color', 'Colors')
|
||||
},
|
||||
hoverColor: {
|
||||
default: text('Color (hover)', '--white', 'Colors')
|
||||
},
|
||||
hoverBackgroundColor: {
|
||||
default: text('Background Color (hover)', '--black', 'Colors')
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<v-button
|
||||
:block="block"
|
||||
:rounded="rounded"
|
||||
:outlined="outlined"
|
||||
:icon="icon"
|
||||
:color="color"
|
||||
:background-color="backgroundColor"
|
||||
:hover-color="hoverColor"
|
||||
:hover-background-color="hoverBackgroundColor"
|
||||
:type="type"
|
||||
:disabled="disabled"
|
||||
:loading="loading"
|
||||
:width="width"
|
||||
:x-small="size === 'xSmall'"
|
||||
:small="size === 'small'"
|
||||
:large="size === 'large'"
|
||||
:x-large="size === 'xLarge'"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ text }}
|
||||
</v-button>
|
||||
`
|
||||
});
|
||||
|
||||
export const withIcon = () => ({
|
||||
methods: { onClick: action('click') },
|
||||
props: {
|
||||
iconName: {
|
||||
default: text('Material Icon', 'add')
|
||||
},
|
||||
block: {
|
||||
default: boolean('Block', false, 'Button')
|
||||
},
|
||||
rounded: {
|
||||
default: boolean('Rounded', true, 'Button')
|
||||
},
|
||||
icon: {
|
||||
default: boolean('Icon mode', true, 'Button')
|
||||
},
|
||||
outlined: {
|
||||
default: boolean('Outlined', false, 'Button')
|
||||
},
|
||||
type: {
|
||||
default: text('Type attribute', 'button', 'Button')
|
||||
},
|
||||
loading: {
|
||||
default: boolean('Loading', false, 'Button')
|
||||
},
|
||||
width: {
|
||||
default: number('Width', 0, {}, 'Button')
|
||||
},
|
||||
size: {
|
||||
default: options(
|
||||
'Size',
|
||||
{
|
||||
'Extra Small': 'xSmall',
|
||||
Small: 'small',
|
||||
'(default)': 'default',
|
||||
Large: 'large',
|
||||
'Extra Large': 'xLarge'
|
||||
},
|
||||
'default',
|
||||
{
|
||||
display: 'select'
|
||||
},
|
||||
'Button'
|
||||
)
|
||||
},
|
||||
iconSize: {
|
||||
default: options(
|
||||
'Size (Icon)',
|
||||
{
|
||||
'Extra Small': 'xSmall',
|
||||
Small: 'small',
|
||||
'(default)': 'default',
|
||||
Large: 'large',
|
||||
'Extra Large': 'xLarge'
|
||||
},
|
||||
'default',
|
||||
{
|
||||
display: 'select'
|
||||
},
|
||||
'Button'
|
||||
)
|
||||
},
|
||||
disabled: {
|
||||
default: boolean('Disabled', false, 'Button')
|
||||
},
|
||||
color: {
|
||||
default: text('Color', '--button-primary-text-color', 'Colors')
|
||||
},
|
||||
backgroundColor: {
|
||||
default: text('Background Color', '--button-primary-background-color', 'Colors')
|
||||
},
|
||||
hoverColor: {
|
||||
default: text('Color (hover)', '--white', 'Colors')
|
||||
},
|
||||
hoverBackgroundColor: {
|
||||
default: text('Background Color (hover)', '--black', 'Colors')
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<v-button
|
||||
:block="block"
|
||||
:rounded="rounded"
|
||||
:outlined="outlined"
|
||||
:icon="icon"
|
||||
:color="color"
|
||||
:background-color="backgroundColor"
|
||||
:hover-color="hoverColor"
|
||||
:hover-background-color="hoverBackgroundColor"
|
||||
:type="type"
|
||||
:disabled="disabled"
|
||||
:loading="loading"
|
||||
:width="width"
|
||||
:x-small="size === 'xSmall'"
|
||||
:small="size === 'small'"
|
||||
:large="size === 'large'"
|
||||
:x-large="size === 'xLarge'"
|
||||
@click="onClick"
|
||||
>
|
||||
<v-icon
|
||||
:name="iconName"
|
||||
:x-small="iconSize === 'xSmall'"
|
||||
:small="iconSize === 'small'"
|
||||
:large="iconSize === 'large'"
|
||||
:x-large="iconSize === 'xLarge'"
|
||||
/>
|
||||
</v-button>
|
||||
`
|
||||
});
|
||||
|
||||
export const sizes = () => `
|
||||
<div>
|
||||
<v-button x-small>Extra small</v-button>
|
||||
<v-button small>Small</v-button>
|
||||
<v-button>Default</v-button>
|
||||
<v-button large>Large</v-button>
|
||||
<v-button x-large>Extra Large</v-button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export const colors = () => `
|
||||
<div>
|
||||
<v-button
|
||||
color="--red"
|
||||
background-color="--red-50"
|
||||
hover-color="--white"
|
||||
hover-background-color="--red"
|
||||
>
|
||||
Delete
|
||||
</v-button>
|
||||
<v-button
|
||||
color="--white"
|
||||
background-color="--green"
|
||||
hover-background-color="--green-800"
|
||||
>
|
||||
Save
|
||||
</v-button>
|
||||
<v-button
|
||||
color="--white"
|
||||
background-color="--amber"
|
||||
hover-background-color="--amber-800"
|
||||
>
|
||||
Warn
|
||||
</v-button>
|
||||
<v-button
|
||||
color="--blue-grey-800"
|
||||
background-color="--blue-grey-50"
|
||||
hover-color="--red"
|
||||
hover-background-color="--white"
|
||||
>
|
||||
Hover
|
||||
</v-button>
|
||||
<v-button
|
||||
color="--blue-grey-800"
|
||||
background-color="transparent"
|
||||
hover-color="--black"
|
||||
hover-background-color="--blue-grey-100"
|
||||
>
|
||||
Transparent
|
||||
</v-button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export const customLoading = () => ({
|
||||
props: {
|
||||
loading: {
|
||||
default: boolean('Loading', true)
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<v-button :loading="loading">
|
||||
Hello, World!
|
||||
<template #loading>
|
||||
..Loading..
|
||||
</template>
|
||||
</v-button>`
|
||||
});
|
||||
157
src/components/v-button/v-button.test.ts
Normal file
157
src/components/v-button/v-button.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-spinner', VSpinner);
|
||||
|
||||
import VButton from './v-button.vue';
|
||||
import VSpinner from '../v-spinner/';
|
||||
|
||||
describe('Button', () => {
|
||||
it('Renders the provided markup in the default slow', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
slots: {
|
||||
default: 'Click me'
|
||||
}
|
||||
});
|
||||
|
||||
expect(component.text()).toContain('Click me');
|
||||
});
|
||||
|
||||
it('Adds the outline class for outline buttons', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
propsData: {
|
||||
outlined: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(component.classes()).toContain('outlined');
|
||||
});
|
||||
|
||||
it('Adds the block class for block buttons', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
propsData: {
|
||||
block: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(component.classes()).toContain('block');
|
||||
});
|
||||
|
||||
it('Adds the rounded class for rounded buttons', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
propsData: {
|
||||
rounded: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(component.classes()).toContain('rounded');
|
||||
});
|
||||
|
||||
it('Adds the icon class for icon buttons', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
propsData: {
|
||||
icon: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(component.classes()).toContain('icon');
|
||||
});
|
||||
|
||||
it('Adds the loading class for loading buttons', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
propsData: {
|
||||
loading: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(component.classes()).toContain('loading');
|
||||
});
|
||||
|
||||
it('Sets the correct CSS variables for custom colors', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
propsData: {
|
||||
color: '--red',
|
||||
hoverColor: '--blue',
|
||||
backgroundColor: '--green',
|
||||
hoverBackgroundColor: '--yellow'
|
||||
}
|
||||
});
|
||||
|
||||
expect((component.vm as any).styles['--_v-button-color']).toBe('var(--red)');
|
||||
expect((component.vm as any).styles['--_v-button-hover-color']).toBe('var(--blue)');
|
||||
expect((component.vm as any).styles['--_v-button-background-color']).toBe('var(--green)');
|
||||
expect((component.vm as any).styles['--_v-button-hover-background-color']).toBe(
|
||||
'var(--yellow)'
|
||||
);
|
||||
});
|
||||
|
||||
describe('Sizes', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
propsData: {
|
||||
color: '--blue-grey',
|
||||
name: 'person'
|
||||
}
|
||||
});
|
||||
|
||||
test('Extra Small', () => {
|
||||
component.setProps({
|
||||
xSmall: true,
|
||||
small: false,
|
||||
large: false,
|
||||
xLarge: false
|
||||
});
|
||||
component.vm.$nextTick(() => expect(component.classes()).toContain('x-small'));
|
||||
});
|
||||
|
||||
test('Small', () => {
|
||||
component.setProps({
|
||||
xSmall: false,
|
||||
small: true,
|
||||
large: false,
|
||||
xLarge: false
|
||||
});
|
||||
component.vm.$nextTick(() => expect(component.classes()).toContain('small'));
|
||||
});
|
||||
|
||||
test('Large', () => {
|
||||
component.setProps({
|
||||
xSmall: false,
|
||||
small: false,
|
||||
large: true,
|
||||
xLarge: false
|
||||
});
|
||||
component.vm.$nextTick(() => expect(component.classes()).toContain('large'));
|
||||
});
|
||||
|
||||
test('Extra Large', () => {
|
||||
component.setProps({
|
||||
xSmall: false,
|
||||
small: false,
|
||||
large: false,
|
||||
xLarge: true
|
||||
});
|
||||
component.vm.$nextTick(() => expect(component.classes()).toContain('x-large'));
|
||||
});
|
||||
|
||||
it('Sets the correct custom width', () => {
|
||||
const component = mount(VButton, {
|
||||
localVue,
|
||||
propsData: {
|
||||
width: 56
|
||||
}
|
||||
});
|
||||
|
||||
expect((component.vm as any).styles.width).toBe('56px');
|
||||
});
|
||||
});
|
||||
});
|
||||
236
src/components/v-button/v-button.vue
Normal file
236
src/components/v-button/v-button.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<button
|
||||
class="v-button"
|
||||
:class="[sizeClass, { block, rounded, icon, outlined, loading }]"
|
||||
:type="type"
|
||||
:style="styles"
|
||||
:disabled="disabled"
|
||||
@click="!loading ? $emit('click') : null"
|
||||
>
|
||||
<span class="content" :class="{ invisible: loading }"><slot /></span>
|
||||
<div class="spinner">
|
||||
<slot v-if="loading" name="loading">
|
||||
<v-spinner :x-small="xSmall" :small="small" />
|
||||
</slot>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { createComponent, reactive, computed, Ref } from '@vue/composition-api';
|
||||
import parseCSSVar from '@/utils/parse-css-var';
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
block: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rounded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
outlined: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '--button-primary-text-color'
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '--button-primary-background-color'
|
||||
},
|
||||
hoverColor: {
|
||||
type: String,
|
||||
default: '--button-primary-text-color'
|
||||
},
|
||||
hoverBackgroundColor: {
|
||||
type: String,
|
||||
default: '--button-primary-background-color-hover'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'button'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
xSmall: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
small: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
large: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
xLarge: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
interface Styles {
|
||||
'--_v-button-color': string;
|
||||
'--_v-button-background-color': string;
|
||||
'--_v-button-hover-color': string;
|
||||
'--_v-button-hover-background-color': string;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
const styles = computed<Styles>(() => {
|
||||
let styles: Styles = {
|
||||
'--_v-button-color': parseCSSVar(props.color),
|
||||
'--_v-button-background-color': parseCSSVar(props.backgroundColor),
|
||||
'--_v-button-hover-color': parseCSSVar(props.hoverColor),
|
||||
'--_v-button-hover-background-color': parseCSSVar(props.hoverBackgroundColor)
|
||||
};
|
||||
|
||||
if (props.width && +props.width > 0) {
|
||||
styles.width = props.width + 'px';
|
||||
}
|
||||
|
||||
return styles;
|
||||
});
|
||||
|
||||
const sizeClass = computed<string | null>(() => {
|
||||
if (props.xSmall) return 'x-small';
|
||||
if (props.small) return 'small';
|
||||
if (props.large) return 'large';
|
||||
if (props.xLarge) return 'x-large';
|
||||
return null;
|
||||
});
|
||||
|
||||
return { styles, sizeClass };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-button {
|
||||
--_v-button-height: 44px;
|
||||
|
||||
color: var(--_v-button-color);
|
||||
background-color: var(--_v-button-background-color);
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: var(--weight-bold);
|
||||
cursor: pointer;
|
||||
border: var(--input-border-width) solid var(--_v-button-background-color);
|
||||
|
||||
font-size: 14px;
|
||||
padding: 0 19px;
|
||||
min-width: 78px;
|
||||
height: var(--_v-button-height);
|
||||
|
||||
transition: var(--fast) var(--transition);
|
||||
transition-property: background-color border;
|
||||
|
||||
position: relative;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:not(.loading):not(:disabled):hover {
|
||||
color: var(--_v-button-hover-color);
|
||||
background-color: var(--_v-button-hover-background-color);
|
||||
border: var(--input-border-width) solid var(--_v-button-hover-background-color);
|
||||
}
|
||||
|
||||
&.block {
|
||||
display: block;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
&.rounded {
|
||||
border-radius: calc(var(--button-height) / 2);
|
||||
}
|
||||
|
||||
&.outlined {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--button-primary-background-color-disabled);
|
||||
border: var(--input-border-width) solid var(--button-primary-background-color-disabled);
|
||||
color: var(--button-primary-text-color-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.x-small {
|
||||
--_v-button-height: 28px;
|
||||
font-size: 12px;
|
||||
padding: 0 12px;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
&.small {
|
||||
--_v-button-height: 36px;
|
||||
font-size: 14px;
|
||||
padding: 0 16px;
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
&.large {
|
||||
--_v-button-height: var(--button-height);
|
||||
font-size: var(--button-font-size);
|
||||
padding: 0 23px;
|
||||
min-width: 92px;
|
||||
}
|
||||
|
||||
&.x-large {
|
||||
--_v-button-height: 58px;
|
||||
font-size: 18px;
|
||||
padding: 0 32px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
&.icon {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
width: var(--_v-button-height);
|
||||
}
|
||||
|
||||
.content,
|
||||
.spinner {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
top: -1;
|
||||
|
||||
&.invisible {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user