Files
speckle-server/packages/ui-components/src/components/form/Button.vue
T
andrewwallacespeckle fcb924d3a5 DO NOT MERGE - refactor: new design system implementation (#2537)
* refactor WIP

* Button design changes

* FE2 FormButton Updates

* ts composition api

* CommonTextLink Changes

* CommonTextLink prop updates

* Add disabled styles

* WIP

* Design system updates

* Colour Updates

* New Text Styles. Initial FE2 changes

* More fe2 styling classes

* Minor update

* Minor update

* Fix build

* More updates for discussion

* More styling updates

* Minor updates to inputs

* Revert change to size options

* More text updates

* More font class swapping

* Revert dui3 changes

* Confirmed Lineheights

* Add story files for new text styles

* Minor copy changes

* Minor typo

* Revert variant>color

* New Colours WIP

* andrew/web-1371-misalignment-in-account-dropdown

* andrew/web-1374-settings-text-styles-are-not-right

* andrew/web-1375-nav-texts-should-be-14px

* andrew/web-1376-decrease-size-of-versions-header

* andrew/web-1377-version-card-title

* Updates

* semibold>medium

* Colour updates

* Sizing updates

* Colour updates

* Colour updates

* Measure mode

* Updates

* Fix build

* Fix build

* WIP Updates

* Changes from PR

* Updated login, registration and reset password styling

* Make share dropdown bg white

* Updated viewer titles

* Fix: Resize panel highlight color in the viewer should be blue

* Fix: Blue + Add link in Models. And other blue links in Viewer

* Add labelPosition Prop. Fix Button stories

* Updated CommonLink to remove default underline

* Add Highlight Color

* Card updates from Michal

* Updated discussion icon on version card

* Small tweaks to version card

* Small tweaks to version card

* Fix: Ghost button doesn't have padding

* Fix: Write Delete...

* Fix: Version hover border color

* Updates to Project Card. Updates to PageTabs

* Fix: Adjust title in announcement modal

* Updates from Comments

* Select Background Colour

* Fix: Select dropdown color

* Improve list view. Improve discussions

* Fix: Minor tweaks to onboarding checklist

* Fix: Clean up nav

* Hide third item when not >md

* Change project heading size

* Add border to version card

* Adjust spacing in dropdowns

* Slight change

* Update button style in Version card

* Tweaked nav menu

* Tweaked nav menu

* Various styling tweaks

* Fix settings modal subheader

* Various styling tweaks and fixes

* Tweak settings dialog styling

* Tweak simple scrollbar

* Minor tweaks to model page

* Minor tweaks to model page

* Minor tweak to login

* Tweak discussion card

* Tweak settings page

* Tweak vertical tabs

* Tweak Dialog alignment

* Fix some paddings

* Change IconVersions to ClockIcon

* Tweak spacing between icons

* Updates to Card Icons

* Bold "connectors" in empty project message

* Remove padding in Profile field

* Update inline model create

* Remove icons from share menu

* Updated Delete dialog

* Wrong text positioning in alert

* Updated copy in dropdown

* Change bg to bg-foundation in select dropdown component

* Fix merge conflicy

* Selection Info title colour

* Wrong text class

* Update card colours based on call

* Update card colours

* Update empty state

* Input label font weight

* Updates to Embed

* Various styling fixes

* Fix; Viewer panel header styling

* Fix; Adjust BG in dev mode list items

* Fix; Fix button placement in video modal

* Fix: Share menu is not using LayoutMenu

* Fix: Buttons clash under filters

* Fix: Adjust spacing in selection info

* Fix: Adjust gray BG behind model preview images

* Fix: No hover cursor on model card

* Fix: Align text styling in dev mode and selection info panel

* Fix for menu width

* Fix mobile problems

* Fix Add spacing on new login screens

* Revert prose change. Add prose-sm

* Text - Use contain for bg image

* Fix onboarding screens

* Responsive fixes

* Fix hydration errors

* Added padding to Add Model Dialog

* Fix versions buttons

* Fix build problem

* Changes PRE PR

* Final Pre PR Changes

* Remove DUI3 change

* Fix small issue with dialog after merge conflict

* Remove label classes from Visibility Select

* Revert changes made in Controls.vue

* Remove old-webhooks

* Add highlight colours to Storybook

* Add v-keyboard-clickable

---------

Co-authored-by: Mike Tasset <mike.tasset@gmail.com>
2024-07-30 15:34:41 +01:00

300 lines
7.5 KiB
Vue

<template>
<Component
:is="to ? linkComponent : 'button'"
:href="to"
:to="to"
:type="buttonType"
:external="external"
:class="buttonClasses"
:disabled="isDisabled"
role="button"
:style="
color !== 'subtle' && !text
? `box-shadow: -1px 1px 4px 0px #0000000a inset; box-shadow: 0px 2px 2px 0px #0000000d;`
: ''
"
@click="onClick"
>
<Component :is="finalLeftIcon" v-if="finalLeftIcon" :class="iconClasses" />
<slot v-if="!hideText">Button</slot>
<Component :is="iconRight" v-if="iconRight || !loading" :class="iconClasses" />
</Component>
</template>
<script setup lang="ts">
import { isObjectLike } from 'lodash'
import type { PropAnyComponent } from '~~/src/helpers/common/components'
import { computed, resolveDynamicComponent } from 'vue'
import type { Nullable } from '@speckle/shared'
import { ArrowPathIcon } from '@heroicons/vue/24/solid'
import type { FormButtonStyle, FormButtonSize } from '~~/src/helpers/form/button'
const emit = defineEmits<{
/**
* Emit MouseEvent on click
*/
(e: 'click', val: MouseEvent): void
}>()
const props = defineProps<{
/**
* URL to which to navigate - can be a relative (app) path or an absolute link for an external URL
*/
to?: string
/**
* Choose from one of 3 button sizes
*/
size?: FormButtonSize
/**
* If set, will make the button take up all available space horizontally
*/
fullWidth?: boolean
/**
* Similar to "link", but without an underline and possibly in different colors
*/
text?: boolean
/**
* Will remove paddings and background. Use for links.
*/
link?: boolean
/**
* color:
* primary: the default primary blue.
* outline: foundation background and outline
* subtle: no styling
*/
color?: FormButtonStyle
/**
* Should rounded-full be added?:
*/
rounded?: boolean
/**
* Whether the target location should be forcefully treated as an external URL
* (for relative paths this will likely cause a redirect)
*/
external?: boolean
/**
* Whether to disable the button so that it can't be pressed
*/
disabled?: boolean
/**
* If set, will have type set to "submit" to enable it to submit any parent forms
*/
submit?: boolean
/**
* Add icon to the left from the text
*/
iconLeft?: Nullable<PropAnyComponent>
/**
* Add icon to the right from the text
*/
iconRight?: Nullable<PropAnyComponent>
/**
* Hide default slot (when you want to show icons only)
*/
hideText?: boolean
/**
* Customize component to be used when rendering links.
*
* The component will try to dynamically resolve NuxtLink and RouterLink and use those, if this is set to null.
*/
linkComponent?: Nullable<PropAnyComponent>
/**
* Disables the button and shows a spinning loader
*/
loading?: boolean
}>()
const NuxtLink = resolveDynamicComponent('NuxtLink')
const RouterLink = resolveDynamicComponent('RouterLink')
const linkComponent = computed(() => {
if (props.linkComponent) return props.linkComponent
if (props.external) return 'a'
if (isObjectLike(NuxtLink)) return NuxtLink
if (isObjectLike(RouterLink)) return RouterLink
return 'a'
})
const buttonType = computed(() => {
if (props.to) return undefined
if (props.submit) return 'submit'
return 'button'
})
const isDisabled = computed(() => props.disabled || props.loading)
const finalLeftIcon = computed(() => (props.loading ? ArrowPathIcon : props.iconLeft))
const bgAndBorderClasses = computed(() => {
const classParts: string[] = []
const colorsBgBorder = {
subtle: [
'bg-transparent border-transparent text-foreground font-medium',
'hover:bg-primary-muted disabled:hover:bg-transparent focus-visible:border-foundation'
],
outline: [
'bg-foundation border-outline-2 text-foreground font-medium',
'hover:bg-primary-muted disabled:hover:bg-foundation focus-visible:border-foundation'
],
danger: [
'bg-danger border-danger-darker text-foundation font-medium',
'hover:bg-danger-darker disabled:hover:bg-danger focus-visible:border-foundation'
],
primary: [
'bg-primary border-outline-1 text-foreground-on-primary font-semibold',
'hover:bg-primary-focus disabled:hover:bg-primary focus-visible:border-foundation'
]
}
if (props.rounded) {
classParts.push('!rounded-full')
}
if (props.text || props.link) {
switch (props.color) {
case 'subtle':
classParts.push('text-foreground')
break
case 'outline':
classParts.push('text-foreground')
break
case 'danger':
classParts.push('text-danger')
break
case 'primary':
default:
classParts.push('text-primary')
break
}
} else {
switch (props.color) {
case 'subtle':
classParts.push(...colorsBgBorder.subtle)
break
case 'outline':
classParts.push(...colorsBgBorder.outline)
break
case 'danger':
classParts.push(...colorsBgBorder.danger)
break
case 'primary':
default:
classParts.push(...colorsBgBorder.primary)
break
}
}
return classParts.join(' ')
})
const sizeClasses = computed(() => {
switch (props.size) {
case 'sm':
return 'h-6 text-body-2xs'
case 'lg':
return 'h-10 text-body-sm'
default:
case 'base':
return 'h-8 text-body-xs'
}
})
const paddingClasses = computed(() => {
if (props.text || props.link) {
return 'p-0'
}
const hasIconLeft = !!props.iconLeft
const hasIconRight = !!props.iconRight
const hideText = props.hideText
switch (props.size) {
case 'sm':
if (hideText) return 'w-6'
if (hasIconLeft) return 'py-1 pr-2 pl-1'
if (hasIconRight) return 'py-1 pl-2 pr-1'
return 'px-2 py-1'
case 'lg':
if (hideText) return 'w-10'
if (hasIconLeft) return 'py-2 pr-6 pl-4'
if (hasIconRight) return 'py-2 pl-6 pr-4'
return 'px-6 py-2'
case 'base':
default:
if (hideText) return 'w-8'
if (hasIconLeft) return 'py-1 pr-4 pl-2'
if (hasIconRight) return 'py-1 pl-4 pr-2'
return 'px-4 py-1'
}
})
const generalClasses = computed(() => {
const baseClasses = [
'inline-flex justify-center items-center',
'text-center select-none whitespace-nowrap',
'outline outline-2 outline-transparent',
'transition duration-200 ease-in-out focus-visible:outline-outline-4'
]
const additionalClasses = []
if (!props.text && !props.link) {
additionalClasses.push('rounded-md border')
}
if (props.fullWidth) {
additionalClasses.push('w-full')
} else if (!props.hideText) {
additionalClasses.push('max-w-max')
}
if (isDisabled.value) {
additionalClasses.push('cursor-not-allowed opacity-60')
}
return [...baseClasses, ...additionalClasses].join(' ')
})
const buttonClasses = computed(() => {
return [
generalClasses.value,
sizeClasses.value,
bgAndBorderClasses.value,
paddingClasses.value
].join(' ')
})
const iconClasses = computed(() => {
const classParts: string[] = ['shrink-0']
if (props.loading) {
classParts.push('animate-spin')
}
switch (props.size) {
case 'sm':
classParts.push('h-5 w-5 p-0.5')
break
case 'lg':
classParts.push('h-6 w-6 p-1')
break
case 'base':
default:
classParts.push('h-6 w-6 p-1')
break
}
return classParts.join(' ')
})
const onClick = (e: MouseEvent) => {
if (isDisabled.value) {
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
return
}
emit('click', e)
}
</script>