Arno Kaimbacher
87e9314b00
Some checks failed
CI Pipeline / japa-tests (push) Failing after 51s
- added lime color inside tailwind.config.js - added some utilities scripts needed for components - npm updates - changed postcss.config.js for nesting css styles - added about function to NavBar.vue
545 lines
15 KiB
Vue
545 lines
15 KiB
Vue
<template>
|
|
<component :is="is" :class="componentClass" :target="target" href="href" aria-label="ariaLabel" aria-pressed="pressed"
|
|
:disabled="disabled" :type="type" v-on:click="click">
|
|
|
|
<span class="button-vue__wrapper">
|
|
<!-- <span v-if="hasIcon" class="button-vue__icon" aria-hidden="true">
|
|
{{ this.$slots.icon }}-->
|
|
<BaseIcon v-if="icon" :path="icon" class="button-vue__icon" aria-hidden="true" :size="size" />
|
|
|
|
<!-- <span v-if="hasText" class="button-vue__text">{{ this.$slots.default }}</span> -->
|
|
<span v-if="label" class="font-bold whitespace-nowrap text-ellipsis overflow-hidden" :class="labelClass">{{
|
|
label }}</span>
|
|
</span>
|
|
</component>
|
|
</template>
|
|
<script lang="ts">
|
|
import BaseIcon from '@/Components/BaseIcon.vue';
|
|
// import { h } from 'vue';
|
|
export default {
|
|
name: 'NcButton',
|
|
|
|
components: {
|
|
BaseIcon
|
|
},
|
|
|
|
props: {
|
|
size: {
|
|
type: [String, Number],
|
|
default: 16,
|
|
},
|
|
small: Boolean,
|
|
roundedFull: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
label: {
|
|
type: [String, Number],
|
|
default: null,
|
|
},
|
|
icon: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
/**
|
|
* Set the text and icon alignment
|
|
*
|
|
* @default 'center'
|
|
*/
|
|
alignment: {
|
|
type: String,
|
|
default: 'center',
|
|
validator: (alignment: string) => ['start', 'start-reverse', 'center', 'center-reverse', 'end', 'end-reverse'].includes(alignment),
|
|
},
|
|
|
|
/**
|
|
* Toggles the disabled state of the button on and off.
|
|
*/
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
|
|
/**
|
|
* Specifies the button type
|
|
* Accepted values: primary, secondary, tertiary, tertiary-no-background, tertiary-on-primary, error, warning, success. If left empty,
|
|
* the default button style will be applied.
|
|
*/
|
|
type: {
|
|
type: String,
|
|
validator(value: string) {
|
|
return (
|
|
[
|
|
'primary',
|
|
'secondary',
|
|
'tertiary',
|
|
'tertiary-no-background',
|
|
'tertiary-on-primary',
|
|
'error',
|
|
'warning',
|
|
'success',
|
|
].indexOf(value) !== -1
|
|
);
|
|
},
|
|
default: 'secondary',
|
|
},
|
|
|
|
/**
|
|
* Specifies the button native type
|
|
* Accepted values: submit, reset, button. If left empty,
|
|
* the default "button" type will be used.
|
|
*/
|
|
nativeType: {
|
|
type: String,
|
|
validator(value: string) {
|
|
return ['submit', 'reset', 'button'].indexOf(value) !== -1;
|
|
},
|
|
default: 'button',
|
|
},
|
|
|
|
/**
|
|
* Specifies whether the button should span all the available width.
|
|
* By default, buttons span the whole width of the container.
|
|
*/
|
|
wide: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
|
|
/**
|
|
* Always try to provide an aria-label to your button. Make it more
|
|
* specific than the button's name by provide some more context. E.g. if
|
|
* the name of the button is "send" in the Mail app, the aria label could
|
|
* be "Send email".
|
|
*/
|
|
ariaLabel: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
|
|
/**
|
|
* Providing the href attribute turns the button component into an `a`
|
|
* element.
|
|
*/
|
|
href: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
|
|
/**
|
|
* Providing the download attribute with href downloads file when clicking.
|
|
*/
|
|
download: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
|
|
/**
|
|
* Providing the to attribute turns the button component into a `router-link`
|
|
* element. Takes precedence over the href attribute.
|
|
*/
|
|
to: {
|
|
type: [String, Object],
|
|
default: null,
|
|
},
|
|
|
|
/**
|
|
* Pass in `true` if you want the matching behaviour of `router-link` to
|
|
* be non-inclusive: https://router.vuejs.org/api/#exact
|
|
*/
|
|
exact: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
/**
|
|
* @deprecated To be removed in @nextcloud/vue 9. Migration guide: remove ariaHidden prop from NcAction* components.
|
|
* @todo Add a check in @nextcloud/vue 9 that this prop is not provided,
|
|
* otherwise root element will inherit incorrect aria-hidden.
|
|
*/
|
|
ariaHidden: {
|
|
type: Boolean,
|
|
default: null,
|
|
},
|
|
|
|
/**
|
|
* The pressed state of the button if it has a checked state
|
|
* This will add the `aria-pressed` attribute and for the button to have the primary style in checked state.
|
|
*/
|
|
pressed: {
|
|
type: Boolean,
|
|
default: null,
|
|
},
|
|
},
|
|
|
|
emits: ['update:pressed', 'click'],
|
|
|
|
computed: {
|
|
labelClass() {
|
|
return (this.small && this.icon) ? 'px-1' : 'px-2';
|
|
},
|
|
is() {
|
|
if (this.href) {
|
|
return 'a';
|
|
}
|
|
return 'button';
|
|
},
|
|
hasText() {
|
|
// return !!this.$slots.default;
|
|
return this.label != undefined;
|
|
},
|
|
hasIcon() {
|
|
// return this.$slots?.icon;
|
|
return this.icon != undefined;
|
|
},
|
|
// type() {
|
|
// return this.href ? null : this.nativeType;
|
|
// },
|
|
|
|
componentClass() {
|
|
const base = [
|
|
'button-vue',
|
|
this.roundedFull ? 'rounded-full' : 'rounded',
|
|
'inline-flex',
|
|
'cursor-pointer',
|
|
'justify-center',
|
|
'items-center',
|
|
'whitespace-nowrap',
|
|
'focus:outline-none',
|
|
'transition-colors',
|
|
// 'focus:ring-2',
|
|
{
|
|
'button-vue--icon-only': this.hasIcon && !this.hasText,
|
|
'button-vue--text-only': this.hasText && !this.hasIcon,
|
|
'button-vue--icon-and-text': this.hasIcon && this.hasText,
|
|
[`button-vue--vue-${this.realType}`]: this.realType,
|
|
'button-vue--wide': this.wide,
|
|
[`button-vue--${this.flexAlignment}`]: this.flexAlignment !== 'center',
|
|
'button-vue--reverse': this.isReverseAligned,
|
|
// Add other classes based on conditions as needed
|
|
|
|
|
|
},
|
|
];
|
|
if (this.small) {
|
|
base.push('text-sm', this.roundedFull ? 'px-3 py-1' : 'p-1');
|
|
} else {
|
|
base.push('py-2', this.roundedFull ? 'px-6' : 'px-3');
|
|
}
|
|
return base;
|
|
},
|
|
|
|
target() {
|
|
return (!this.to && this.href) ? '_self' : null;
|
|
},
|
|
|
|
/**
|
|
* The real type to be used for the button, enforces `primary` for pressed state and, if stateful button, any other type for not pressed state
|
|
* Otherwise the type property is used.
|
|
*/
|
|
realType() {
|
|
// Force *primary* when pressed
|
|
if (this.pressed) {
|
|
return 'primary';
|
|
}
|
|
// If not pressed but button is configured as stateful button then the type must not be primary
|
|
if (this.pressed === false && this.type === 'primary') {
|
|
return 'secondary';
|
|
}
|
|
return this.type;
|
|
},
|
|
|
|
/**
|
|
* The flexbox alignment of the button content
|
|
*/
|
|
flexAlignment() {
|
|
return this.alignment.split('-')[0];
|
|
},
|
|
|
|
/**
|
|
* If the button content should be reversed (icon on the end)
|
|
*/
|
|
isReverseAligned() {
|
|
return this.alignment.includes('-');
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
click($event) {
|
|
// Update pressed prop on click if it is set
|
|
if (typeof this.pressed === 'boolean') {
|
|
/**
|
|
* Update the current pressed state of the button (if the `pressed` property was configured)
|
|
*
|
|
* @property {boolean} newValue The new `pressed`-state
|
|
*/
|
|
this.$emit('update:pressed', !this.pressed)
|
|
}
|
|
// We have to both navigate and emit the click event
|
|
this.$emit('click', $event)
|
|
// navigate?.($event)
|
|
},
|
|
},
|
|
|
|
|
|
|
|
|
|
};
|
|
</script>
|
|
|
|
<style lang="css" scoped>
|
|
.button-vue {
|
|
position: relative;
|
|
|
|
/* width: fit-content;
|
|
overflow: hidden;
|
|
border: 0; */
|
|
padding: 0;
|
|
/* font-size: var(--default-font-size);
|
|
font-weight: bold; */
|
|
min-height: 44px;
|
|
min-width: 44px;
|
|
display: flex;
|
|
align-items: center;
|
|
/* justify-content: center; */
|
|
|
|
/* Cursor pointer on element and all children */
|
|
cursor: pointer;
|
|
|
|
& *,
|
|
span {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* border-radius: math.div($clickable-area, 2); */
|
|
/* transition-property: color, */
|
|
/* border-color,
|
|
background-color;
|
|
transition-duration: 0.1s;
|
|
transition-timing-function: linear; */
|
|
|
|
/* border-radius: math.div($clickable-area, 2); */
|
|
/* border-radius: calc(50% - var(--clickable-area) / 2);
|
|
transition-property: color, border-color, background-color;
|
|
transition-duration: 0.1s;
|
|
transition-timing-function: linear; */
|
|
|
|
/* No outline feedback for focus. Handled with a toggled class in js (see data) */
|
|
&:focus {
|
|
outline: none;
|
|
}
|
|
|
|
&:disabled {
|
|
cursor: default;
|
|
|
|
& * {
|
|
cursor: default;
|
|
}
|
|
|
|
opacity: 0.5;
|
|
/* // Gives a wash out effect */
|
|
filter: saturate(0.7l);
|
|
}
|
|
|
|
/* // Default button type */
|
|
color: var(--color-primary-element-light-text);
|
|
background-color: var(--color-primary-element-light);
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: var(--color-primary-element-light-hover);
|
|
}
|
|
|
|
|
|
/* Back to the default color for this button when active */
|
|
/* TODO: add ripple effect */
|
|
&:active {
|
|
background-color: var(--color-primary-element-light);
|
|
}
|
|
|
|
&__wrapper {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
}
|
|
|
|
&--end &__wrapper {
|
|
justify-content: end;
|
|
}
|
|
|
|
&--start &__wrapper {
|
|
justify-content: start;
|
|
}
|
|
|
|
&--reverse &__wrapper {
|
|
flex-direction: row-reverse;
|
|
}
|
|
|
|
&--reverse &--icon-and-text {
|
|
padding-inline: calc(var(--default-grid-baseline) * 4) var(--default-grid-baseline);
|
|
}
|
|
|
|
&__icon {
|
|
height: 44px;
|
|
width: 44px;
|
|
min-height: 44px;
|
|
min-width: 44px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
/* &__text {
|
|
font-weight: bold;
|
|
margin-bottom: 1px;
|
|
padding: 2px 0;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
} */
|
|
|
|
/* Icon-only button */
|
|
&--icon-only {
|
|
width: 44px !important;
|
|
}
|
|
|
|
/* Text-only button */
|
|
&--text-only {
|
|
padding: 0 12px;
|
|
|
|
& .button-vue__text {
|
|
margin-left: 4px;
|
|
margin-right: 4px;
|
|
}
|
|
}
|
|
|
|
/* Icon and text button */
|
|
&--icon-and-text {
|
|
padding-block: 0;
|
|
padding-inline: var(--default-grid-baseline) calc(var(--default-grid-baseline) * 4);
|
|
}
|
|
|
|
/* Wide button spans the whole width of the container */
|
|
&--wide {
|
|
width: 100%;
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid var(--color-main-text) !important;
|
|
box-shadow: 0 0 0 4px var(--color-main-background) !important;
|
|
|
|
&.button-vue--vue-tertiary-on-primary {
|
|
outline: 2px solid var(--color-primary-element-text);
|
|
border-radius: var(--border-radius);
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
|
|
/* Button types */
|
|
|
|
/* // Primary */
|
|
&--vue-primary {
|
|
background-color: var(--color-primary-element);
|
|
color: var(--color-primary-element-text);
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: var(--color-primary-element-hover);
|
|
}
|
|
|
|
/* Back to the default color for this button when active
|
|
TODO: add ripple effect */
|
|
&:active {
|
|
background-color: var(--color-primary-element);
|
|
}
|
|
}
|
|
|
|
/* Secondary */
|
|
&--vue-secondary {
|
|
color: var(--color-primary-element-light-text);
|
|
background-color: var(--color-primary-element-light);
|
|
|
|
&:hover:not(:disabled) {
|
|
color: var(--color-primary-element-light-text);
|
|
background-color: var(--color-primary-element-light-hover);
|
|
}
|
|
}
|
|
|
|
/* Tertiary */
|
|
&--vue-tertiary {
|
|
color: var(--color-main-text);
|
|
background-color: transparent;
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: var(--color-background-hover);
|
|
}
|
|
}
|
|
|
|
/* Tertiary, no background */
|
|
&--vue-tertiary-no-background {
|
|
color: var(--color-main-text);
|
|
background-color: transparent;
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
|
|
/* Tertiary on primary color (like the header) */
|
|
&--vue-tertiary-on-primary {
|
|
color: var(--color-primary-element-text);
|
|
background-color: transparent;
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
|
|
/* Success */
|
|
&--vue-success {
|
|
background-color: var(--color-success);
|
|
color: white;
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: var(--color-success-hover);
|
|
}
|
|
|
|
/* Back to the default color for this button when active
|
|
: add ripple effect */
|
|
&:active {
|
|
background-color: var(--color-success);
|
|
}
|
|
}
|
|
|
|
/* Warning */
|
|
&--vue-warning {
|
|
background-color: var(--color-warning);
|
|
color: white;
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: var(--color-warning-hover);
|
|
}
|
|
|
|
/* Back to the default color for this button when active
|
|
TODO: add ripple effect */
|
|
&:active {
|
|
background-color: var(--color-warning);
|
|
}
|
|
}
|
|
|
|
/* Error */
|
|
&--vue-error {
|
|
background-color: var(--color-error);
|
|
color: white;
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: var(--color-error-hover);
|
|
}
|
|
|
|
/* Back to the default color for this button when active
|
|
TODO: add ripple effect */
|
|
&:active {
|
|
background-color: var(--color-error);
|
|
}
|
|
}
|
|
}
|
|
</style>
|