mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 09:34:50 +00:00
* refactor: managed content card * fix: update filter pill styling to be chips-like * fix: modpack exporting * fixed: vanilla server projs + header * refactor: filters * refactor: detect external file icons + other metadata inside * refactor: support json5 metadata * fix: qa * fix: qa * fix: fmt + prepr * fix: fmt * fix: qa * fix: headers * feat: locking * fix: clippy * fix: qa * fix: frozen loc * fix: shared instances diff calc break * fix: env filter + remove categories
166 lines
5.5 KiB
Vue
166 lines
5.5 KiB
Vue
<script setup lang="ts">
|
|
import type { Component, CSSProperties } from 'vue'
|
|
import { computed, ref } from 'vue'
|
|
|
|
import type {
|
|
ButtonColor,
|
|
ButtonInteraction,
|
|
ButtonNativeType,
|
|
ButtonSize,
|
|
ButtonType,
|
|
} from './types'
|
|
|
|
const baseClasses = [
|
|
// Base
|
|
'relative inline-flex min-w-0 shrink-0 items-center justify-center',
|
|
'whitespace-nowrap border-0 no-underline',
|
|
// Interactions
|
|
'touch-manipulation cursor-pointer select-none transition-[background-color,color,box-shadow,filter,opacity,transform] duration-150 ease-out',
|
|
'enabled:active:scale-[0.97]',
|
|
// Hovering
|
|
'[&:not(:disabled):not([aria-disabled=true]):hover]:brightness-[--hover-brightness]',
|
|
// Accessibility
|
|
'[&:not(:disabled):not([aria-disabled=true]):focus-visible]:brightness-[--hover-brightness] focus-visible:outline-none [&:not(:disabled):not([aria-disabled=true]):focus-visible]:ring-4 [&:not(:disabled):not([aria-disabled=true]):focus-visible]:ring-brand-shadow',
|
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
'[&[aria-disabled=true]]:cursor-not-allowed [&[aria-disabled=true]]:opacity-50',
|
|
].join(' ')
|
|
|
|
const sizeClasses: Record<ButtonSize, string> = {
|
|
xs: 'h-7 gap-1 rounded-lg px-1.5 text-sm font-semibold leading-5 [&>svg]:size-4 [&>svg]:min-h-4 [&>svg]:min-w-4 [&>svg]:shrink-0',
|
|
sm: 'h-8 gap-1 rounded-[10px] px-1.5 text-sm font-semibold leading-5 [&>svg]:size-4 [&>svg]:min-h-4 [&>svg]:min-w-4 [&>svg]:shrink-0',
|
|
md: 'h-9 gap-1.5 rounded-xl px-2.5 text-base font-semibold leading-5 [&>svg]:size-5 [&>svg]:min-h-5 [&>svg]:min-w-5 [&>svg]:shrink-0',
|
|
lg: 'h-10 gap-2 rounded-[14px] px-4 text-base font-semibold leading-5 [&>svg]:size-5 [&>svg]:min-h-5 [&>svg]:min-w-5 [&>svg]:shrink-0',
|
|
xl: 'h-12 gap-2 rounded-2xl px-3.5 text-base font-extrabold leading-5 [&>svg]:size-6 [&>svg]:min-h-6 [&>svg]:min-w-6 [&>svg]:shrink-0',
|
|
}
|
|
|
|
const iconOnlySizeClasses: Record<ButtonSize, string> = {
|
|
xs: 'w-7 !px-0',
|
|
sm: 'w-8 !px-0',
|
|
md: 'w-9 !px-0',
|
|
lg: 'w-10 !px-0',
|
|
xl: 'w-12 !px-0',
|
|
}
|
|
|
|
const typeClasses: Record<ButtonType, string> = {
|
|
base: 'button-frame--base bg-surface-4 text-contrast [&>svg]:text-primary',
|
|
colored:
|
|
'button-frame--colored bg-[--button-color] text-[var(--color-accent-contrast)] [&>svg]:text-inherit',
|
|
outlined:
|
|
'button-frame--outlined bg-transparent text-[var(--button-color,var(--color-contrast))] [&>svg]:text-[var(--button-color,var(--color-base))]',
|
|
quiet: 'button-frame--quiet bg-transparent [&>svg]:text-inherit',
|
|
}
|
|
|
|
const interactionClasses: Record<ButtonInteraction, string> = {
|
|
surface:
|
|
'[&:not(:disabled):not([aria-disabled=true]):hover]:bg-surface-4 [&:not(:disabled):not([aria-disabled=true]):focus-visible]:bg-surface-4',
|
|
filled:
|
|
'[&:not(:disabled):not([aria-disabled=true]):hover]:!bg-[--button-color] [&:not(:disabled):not([aria-disabled=true]):focus-visible]:!bg-[--button-color] [&:not(:disabled):not([aria-disabled=true]):hover]:!text-[var(--color-accent-contrast)] [&:not(:disabled):not([aria-disabled=true]):focus-visible]:!text-[var(--color-accent-contrast)]',
|
|
none: '[&:not(:disabled):not([aria-disabled=true]):hover]:!brightness-100 [&:not(:disabled):not([aria-disabled=true]):focus-visible]:!brightness-100',
|
|
}
|
|
|
|
const colorVariables: Record<ButtonColor, string> = {
|
|
brand: 'var(--color-brand)',
|
|
red: 'var(--color-red)',
|
|
orange: 'var(--color-orange)',
|
|
green: 'var(--color-green)',
|
|
blue: 'var(--color-blue)',
|
|
purple: 'var(--color-purple)',
|
|
medal_promotion: 'var(--medal-promotion-text-orange, var(--color-orange))',
|
|
}
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
as: string | Component
|
|
type?: ButtonType
|
|
color?: ButtonColor
|
|
size?: ButtonSize
|
|
interaction?: ButtonInteraction
|
|
iconOnly?: boolean
|
|
circular?: boolean
|
|
nativeType?: ButtonNativeType
|
|
}>(),
|
|
{
|
|
type: 'base',
|
|
size: 'md',
|
|
interaction: 'surface',
|
|
iconOnly: false,
|
|
circular: false,
|
|
nativeType: undefined,
|
|
},
|
|
)
|
|
|
|
const element = ref<HTMLElement | null>(null)
|
|
const classes = computed(() => [
|
|
baseClasses,
|
|
typeClasses[props.type],
|
|
props.type === 'quiet' ? interactionClasses[props.interaction] : '',
|
|
sizeClasses[props.size],
|
|
props.iconOnly ? iconOnlySizeClasses[props.size] : '',
|
|
props.circular ? '!rounded-full' : '',
|
|
])
|
|
const style = computed((): CSSProperties | undefined => {
|
|
if ((props.type === 'outlined' || props.type === 'quiet') && !props.color) return undefined
|
|
if (props.type !== 'colored' && props.type !== 'outlined' && props.type !== 'quiet')
|
|
return undefined
|
|
|
|
return {
|
|
'--button-color': colorVariables[props.color ?? 'brand'],
|
|
} as CSSProperties
|
|
})
|
|
|
|
defineExpose({ element })
|
|
</script>
|
|
|
|
<template>
|
|
<component
|
|
:is="as"
|
|
ref="element"
|
|
data-button
|
|
:type="props.nativeType"
|
|
:class="classes"
|
|
:style="style"
|
|
>
|
|
<slot />
|
|
</component>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.button-frame--base {
|
|
box-shadow:
|
|
inset 0 0 0 1px var(--surface-5),
|
|
0 1px 1px rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
.button-frame--colored {
|
|
box-shadow:
|
|
0 0 0 1px color-mix(in srgb, var(--button-color) 30%, transparent),
|
|
0 2px 4px rgba(0, 0, 0, 0.04),
|
|
0 5px 8px rgba(0, 0, 0, 0.04),
|
|
0 10px 18px rgba(0, 0, 0, 0.03),
|
|
0 24px 48px rgba(0, 0, 0, 0.03);
|
|
}
|
|
|
|
.button-frame--colored::before {
|
|
position: absolute;
|
|
inset: 0;
|
|
padding: 1px;
|
|
pointer-events: none;
|
|
content: '';
|
|
border-radius: inherit;
|
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0));
|
|
-webkit-mask:
|
|
linear-gradient(#000 0 0) content-box,
|
|
linear-gradient(#000 0 0);
|
|
-webkit-mask-composite: xor;
|
|
mask-composite: exclude;
|
|
}
|
|
|
|
.button-frame--outlined {
|
|
box-shadow: inset 0 0 0 1px var(--button-color, var(--surface-5));
|
|
}
|
|
|
|
.button-frame--quiet {
|
|
color: var(--button-color, var(--color-base));
|
|
}
|
|
</style>
|