Skip to content
Snippets Groups Projects
Commit e5eba783 authored by Kasper Seweryn's avatar Kasper Seweryn 🥞
Browse files

Add button states and use css vars for colors

parent 6ebe80b0
No related branches found
No related tags found
1 merge request!1Implement all components
Pipeline #23276 failed with stages
in 4 minutes and 1 second
......@@ -8,7 +8,7 @@ end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{html,js,vue,css,scss,json,yml,ts}]
[*.{html,js,vue,css,scss,json,yml,ts,md}]
indent_style = space
indent_size = 2
......
......@@ -14,6 +14,7 @@ export default defineConfig({
text: "Components",
items: [
{ text: "Button", link: "/components/button/" },
{ text: "Loader", link: "/components/loader/" },
]
}
]
......
<script setup>
import { FwButton } from '~/components'
// const click = () => new Promise(resolve => setTimeout(resolve, 1000))
const click = () => new Promise((r, resolve) => setTimeout(resolve, 1000))
</script>
# Button
## Button colors
### Primary button
```html
<fw-button>
Primary button
</fw-button>
```
<fw-button>
Primary button
</fw-button>
### Secondary button
```html
<fw-button secondary>
Secondary button
</fw-button>
```
<fw-button secondary>
Secondary button
</fw-button>
### Destructive button
```html
<fw-button destructive>
Destructive button
</fw-button>
```
<fw-button destructive>
Destructive button
</fw-button>
## Button types
### Filled
```html
<fw-button>
Filled button
</fw-button>
```
<fw-button>
Filled button
</fw-button>
### Outline
```html
<fw-button outline>
Outline button
</fw-button>
```
<fw-button outline>
Outline button
</fw-button>
## Button states
### Disabled
```html
<fw-button disabled>
Disabled button
</fw-button>
```
<fw-button disabled>
Disabled button
</fw-button>
### Active
```html
<fw-button is-active>
Active button
</fw-button>
```
<fw-button is-active>
Active button
</fw-button>
### Loading
```html
<fw-button is-loading>
Loading button
</fw-button>
```
<fw-button is-loading>
Loading button
</fw-button>
&nbsp;
#### Promise handling in @click
When a function passed to `@click` returns a promise, the button will automatically toggle it's loading state on, and when promise resolves/rejects, it will toggle it off.
::: danger
The `<fw-button>` component does not have any promise rejection mechanism implemented. Make sure, that the `@click` handler never rejects.
:::
```vue
<script setup lang="ts">
const click = () => new Promise(resolve => setTimeout(resolve, 1000))
</script>
<fw-button text="Test button" />
<template>
<fw-button @click="click">
Click me
</fw-button>
</template>
```
<fw-button @click="click">
Click me
</fw-button>
<script setup>
import { FwLoader } from '~/components'
</script>
<style>
.docs-loader-container div[style^=width] {
border: 1px solid #666;
height: 2em;
}
</style>
# Loader
```html
<div style="width: 50%">
<fw-loader />
</div>
```
<div class="docs-loader-container">
<div style="width: 50%">
<fw-loader />
</div>
</div>
## No container
By default the `<fw-loader />` component creates a container that is 100% height of component's parent. Sometimes we do not want such behavior. In those cases we can disable this using `:container="false"` property. The loader will then be centered in the middle of the first parent that has `position: relative` set.
```html
<div style="position: relative">
<div style="width: 50%">
<fw-loader :container="false" />
</div>
</div>
```
<div class="docs-loader-container">
<div style="position: relative">
<div style="width: 50%">
<fw-loader :container="false" />
</div>
</div>
</div>
<script setup lang="ts">
defineProps<{ text: string }>()
import { type TypeProps, useType } from '~/composables/useType'
import { FwLoader } from '~/components'
import { ref, computed } from 'vue'
interface Props extends TypeProps {
outline?: boolean
isActive?: boolean
isLoading?: boolean
onClick?: (...args: any[]) => void | Promise<void>
// TODO (wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
primary?: boolean
secondary?: boolean
destructive?: boolean
}
const props = defineProps<Props>()
const type = useType(props)
const internalLoader = ref(false)
const isLoading = computed(() => props.isLoading || internalLoader.value)
const click = async (...args: any[]) => {
internalLoader.value = true
try {
await props.onClick?.(...args)
} finally {
internalLoader.value = false
}
}
</script>
<template>
<button class="fw button is-primary">{{ text }}</button>
<button
class="funkwhale input button"
:class="[type, { 'is-active': isActive, 'is-outline': outline, 'is-loading': isLoading }]"
@click="click"
>
<span>
<slot />
</span>
<fw-loader
v-if="isLoading"
:container="false"
/>
</button>
</template>
<style lang="scss">
......
.fw.button {
background-color: transparent;
font-family: $font-main;
height: 34px;
line-height: 16px;
font-size: 1em;
padding: 8px;
border-radius: var(--fw-border-radius);
border: 1px solid var(--fw-grey-600);
color: var(--fw-grey-600);
min-width: 136px;
font-weight: 900;
justify-content: center;
.funkwhale {
&.button {
position: relative;
&.center {
display: block;
margin: 0 auto;
}
&[disabled] {
opacity: 1;
font-weight: normal;
color: black;
pointer-events: none;
}
&.is-focused,
&:focus {
box-shadow: none !important;
}
}
// Primary Buttons
.funkwhale.button.is-primary {
border: 0;
background-color: var(--fw-primary);
color: #fff;
&[disabled] {
background-color: var(--fw-blue-100);
color: var(--fw-blue-900);
}
background-color: var(--fw-bg-color);
color: var(--fw-text-color);
&.is-hovered,
&:hover {
background-color: var(--fw-blue-600);
}
border: 1px solid var(--fw-bg-color);
&.is-active,
&:active {
background-color: var(--fw-blue-700);
}
}
font-family: $font-main;
font-weight: 900;
// Secondary Buttons
min-width: 8.5rem;
height: 2.125rem;
.funkwhale.button.is-secondary {
border: 0;
background-color: var(--fw-grey-200);
&[disabled] {
background-color: var(--fw-grey-100);
color: var(--fw-grey-800);
}
line-height: 1rem;
font-size: 1rem;
&.is-hovered,
&:hover {
background-color: var(--fw-grey-300);
}
padding: 0.5rem;
&.is-active,
&:active {
background-color: var(--fw-grey-400);
}
}
border-radius: var(--fw-border-radius);
// Desctructive Buttons
&[disabled] {
font-weight: normal;
cursor: default;
}
.funkwhale.button.is-destructive {
border: 0;
background-color: var(--fw-destructive);
color: #fff;
&.is-focused,
&:focus {
box-shadow: none !important;
}
&[disabled] {
background-color: var(--fw-red-100);
color: var(--fw-blue-900);
}
&.is-outline {
background-color: transparent !important;
color: var(--fw-bg-color) !important;
}
&.is-hovered,
&:hover {
background-color: var(--fw-red-600);
}
&.is-loading {
@extend .is-active;
&.is-active,
&:active {
background-color: var(--fw-red-700);
> span {
opacity: 0;
}
}
}
}
export { default as FwButton } from './button/Button.vue'
export { default as FwLoader } from './loader/Loader.vue'
<script setup lang="ts">
interface Props {
container?: boolean
}
withDefaults(defineProps<Props>(), {
container: true
})
</script>
<template>
<div v-if="container" class="funkwhale loader-container">
<div class="funkwhale loader">
<div class="loader" />
</div>
</div>
<div v-else class="funkwhale loader">
<div class="loader" />
</div>
</template>
<style lang="scss">
@import './style.scss'
</style>
.funkwhale {
&.loader-container {
position: relative;
height: 100%;
}
&.loader {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2em;
// Modified version of https://github.com/lukehaas/css-loaders
> .loader,
> .loader:after {
border-radius: 50%;
width: 1em;
height: 1em;
}
> .loader:after {
content: '';
position: absolute;
display: block;
border: .11em solid var(--fw-text-color);
opacity: 0.2;
margin-left: -0.11em;
margin-top: -0.11em;
}
> .loader {
position: relative;
text-indent: -9999em;
font-size: 1em;
border-top: .11em solid transparent;
border-right: .11em solid transparent;
border-bottom: .11em solid transparent;
border-left: .11em solid var(--fw-text-color);
will-change: transform;
transform: rotate(0deg);
animation: rotate .5s infinite linear;
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
}
}
}
import { computed } from "vue"
export interface TypeProps {
primary?: boolean
secondary?: boolean
destructive?: boolean
}
export const useType = (props: TypeProps) => computed(() => props.primary
? 'is-primary'
: props.secondary
? 'is-secondary'
: props.destructive
? 'is-destructive'
: 'is-primary'
)
......@@ -29,11 +29,82 @@
--fw-beige-100: #fafaf6;
// Background
--fw-bg-color: #fff;
--fw-bg-color: var(--fw-grey-100);
--fw-text-color: var(--fw-blue-900);
// Override Bulma
--fw-primary: var(--fw-blue-500);
--fw-destructive: var(--fw-red-500);
}
html.dark {
--fw-bg-color: var(--fw-blue-900);
--fw-text-color: var(--fw-grey-100);
}
.funkwhale {
&.is-primary {
--fw-bg-color: var(--fw-blue-500);
--fw-text-color: var(--fw-grey-100);
&.input {
&[disabled] {
--fw-bg-color: var(--fw-blue-100) !important;
--fw-text-color: var(--fw-blue-900) !important;
}
&.is-hovered,
&:hover {
--fw-bg-color: var(--fw-blue-600);
}
&.is-active,
&:active {
--fw-bg-color: var(--fw-blue-700);
}
}
}
&.is-secondary {
--fw-bg-color: var(--fw-grey-200);
--fw-text-color: var(--fw-grey-800);
&.input {
&[disabled] {
--fw-bg-color: var(--fw-grey-100) !important;
}
&.is-hovered,
&:hover {
--fw-bg-color: var(--fw-grey-300);
}
&.is-active,
&:active {
--fw-bg-color: var(--fw-grey-400);
}
}
}
&.is-destructive {
--fw-bg-color: var(--fw-destructive);
--fw-text-color: var(--fw-grey-100);
&.input {
&[disabled] {
--fw-bg-color: var(--fw-red-100) !important;
--fw-text-color: var(--fw-blue-900) !important;
}
&.is-hovered,
&:hover {
--fw-bg-color: var(--fw-red-600);
}
&.is-active,
&:active {
--fw-bg-color: var(--fw-red-700);
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment