Skip to content
Snippets Groups Projects
Commit 0f267b6e authored by Kasper Seweryn's avatar Kasper Seweryn :pancakes:
Browse files

Add select fields

parent e79555fd
Branches
Tags
1 merge request!1Implement all components
Pipeline #23644 passed
......@@ -31,6 +31,57 @@ This is the default pill color
Destructive pill
</fw-pill>
### Blue
```html
<fw-pill blue>
Blue pill
</fw-pill>
```
<fw-pill blue>
Blue pill
</fw-pill>
### Red
```html
<fw-pill red>
Red pill
</fw-pill>
```
<fw-pill red>
Red pill
</fw-pill>
### Purple
```html
<fw-pill purple>
Purple pill
</fw-pill>
```
<fw-pill purple>
Purple pill
</fw-pill>
### Green
```html
<fw-pill green>
Green pill
</fw-pill>
```
<fw-pill green>
Green pill
</fw-pill>
### Yellow
```html
<fw-pill yellow>
Yellow pill
</fw-pill>
```
<fw-pill yellow>
Yellow pill
</fw-pill>
## Images
```html
<fw-pill>
......
<script setup lang="ts">
import { ref } from 'vue'
const bandcampPrivacy = ref('pod')
const ccPrivacy = ref('public')
const bandcamp = ref(false)
const cc = ref(false)
const share = ref(false)
const items = [
{
type: 'button',
......@@ -49,16 +56,26 @@ const items = [
click: () => {},
items: [
{
type: 'button',
type: 'select',
text: 'Bandcamp',
icon: '',
click: () => {}
model: bandcamp,
extraItems: [
{
type: 'library-privacy-level',
model: bandcampPrivacy
}
]
},
{
type: 'button',
type: 'select',
text: 'Creative Commons',
icon: '',
click: () => {}
model: cc,
extraItems: [
{
type: 'library-privacy-level',
model: ccPrivacy
}
]
},
{ type: 'separator', },
{
......@@ -69,13 +86,24 @@ const items = [
},
{ type: 'separator', },
{
type: 'button',
type: 'select',
text: 'Share by link',
icon: '',
model: share,
extraItems: [
{
type: 'circle-button',
icon: 'bi-link',
click: () => {}
},
{
type: 'circle-button',
icon: 'bi-code',
click: () => {}
},
]
},
]
},
{
type: 'button',
text: 'Download',
......
<script setup lang="ts">
import { type ColorProps, useColor } from '~/composables/useColor'
import { type PastelProps, usePastel } from '~/composables/usePastels'
interface Props extends ColorProps {
interface Props extends ColorProps, PastelProps {
// TODO (wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
primary?: boolean
secondary?: boolean
destructive?: boolean
red?: boolean
blue?: boolean
purple?: boolean
green?: boolean
yellow?: boolean
}
const props = defineProps<Props>()
const type = useColor(props, 'is-secondary')
const color = usePastel(props, '')
</script>
<template>
<button
class="funkwhale input pill"
:class="[type]"
:class="[type, color]"
>
<div v-if="!!$slots.image" class="pill-image">
<slot name="image" />
......
......@@ -3,7 +3,7 @@
position: relative;
display: inline-flex;
background-color: var(--fw-bg-color);
background-color: var(--fw-pastel-2, var(--fw-bg-color));
color: var(--fw-text-color);
font-family: $font-main;
......
<script setup lang="ts">
import { useVModel, onClickOutside } from '@vueuse/core'
import { computed, ref, reactive, watch } from 'vue'
import { FwPopover } from '~/components'
import { useVModel, onClickOutside, whenever } from '@vueuse/core'
import { computed, ref, reactive, watch, type Ref } from 'vue'
import { FwPopover, FwButton, FwPill } from '~/components'
import { useI18n } from 'vue-i18n'
interface Button {
const { t } = useI18n()
interface Clickable {
click: (event: Event) => Promise<void> | void
}
interface HasExtras {
extraItems?: ExtraItem[]
}
interface Button extends Clickable, HasExtras {
type: 'button'
text: string
icon: string
click: () => Promise<void> | void
items?: Item[]
}
interface Select extends HasExtras {
type: 'select'
text: string
model: Ref<boolean>
}
interface Separator {
type: 'separator'
}
type Item = Button | Separator
type Item = Button | Separator | Select
type EnumeratedItem = Item & { _id: number }
interface ExtraCircleButton extends Clickable {
type: 'circle-button'
icon: string
}
type PrivacyLevel = 'pod' | 'public' | 'private'
interface ExtraLibraryPrivacyLevel {
type: 'library-privacy-level'
model: Ref<PrivacyLevel>
}
type ExtraItem = (ExtraCircleButton | ExtraLibraryPrivacyLevel) & { _id?: number }
interface Events {
(e: 'update:open', open: boolean): void
}
......@@ -34,8 +63,13 @@ const props = withDefaults(defineProps<Props>(), {
clickOutside: true
})
const items = computed<EnumeratedItem[]>(() => props.items.map(item => ({ ...item, _id: Math.random() })))
const items = computed<EnumeratedItem[]>(() => props.items.map(item => ({
...item,
extraItems: ('extraItems' in item ? item.extraItems : undefined)?.map(extra => ({ ...extra, _id: Math.random() })),
_id: Math.random()
})))
// Children
const hovered = ref<EnumeratedItem>()
const childOpen = reactive<Record<string, boolean>>({})
watch(hovered, (to, from) => {
......@@ -62,6 +96,104 @@ watch(open, (to) => {
hovered.value = undefined
}
})
// Clickable
const click = (item: Item | ExtraItem, event: Event) => {
if ('click' in item && item.click) {
return item.click(event)
}
if (item.type === 'select') {
item.model.value = !item.model.value
return
}
}
// Extras
const extraOpen = reactive<Record<string, boolean>>({})
// Privacy level
const privacyLevel = ref<ExtraLibraryPrivacyLevel & { _id?: number }>()
watch(privacyLevel, (to, from) => {
// @ts-expect-error Vue has already handled this...
const previousLevel: PrivacyLevel | undefined = from?.model
// @ts-expect-error Vue has already handled this...
const level: PrivacyLevel | undefined = to?.model
if (previousLevel) {
privacyLevelToggle[previousLevel].value = false
}
if (level) {
privacyLevelToggle[level].value = true
}
if (from?._id) {
extraOpen[from._id] = false
}
if (to?._id) {
extraOpen[to._id] = true
}
}, { deep: false })
const privacyLevelToggle: Record<PrivacyLevel, Ref<boolean>> = {
private: ref(false),
public: ref(false),
pod: ref(false)
}
whenever(privacyLevelToggle.private, () => {
// @ts-expect-error Vue has already handled this...
privacyLevel.value.model = 'private'
privacyLevelToggle.private.value = true
privacyLevelToggle.public.value = false
privacyLevelToggle.pod.value = false
})
whenever(privacyLevelToggle.public, () => {
// @ts-expect-error Vue has already handled this...
privacyLevel.value.model = 'public'
privacyLevelToggle.private.value = false
privacyLevelToggle.public.value = true
privacyLevelToggle.pod.value = false
})
whenever(privacyLevelToggle.pod, () => {
// @ts-expect-error Vue has already handled this...
privacyLevel.value.model = 'pod'
privacyLevelToggle.private.value = false
privacyLevelToggle.public.value = false
privacyLevelToggle.pod.value = true
})
const privacyLevelItems = computed(() => [
{
type: 'select',
text: privacyLevelLabels.value.private,
model: privacyLevelToggle.private
},
{
type: 'select',
text: privacyLevelLabels.value.public,
model: privacyLevelToggle.public
},
{
type: 'select',
text: privacyLevelLabels.value.pod,
model: privacyLevelToggle.pod
},
] as Select[])
const privacyLevelLabels = computed<Record<PrivacyLevel, string>>(() => ({
private: t('vui.privacy-level.private'),
public: t('vui.privacy-level.public'),
pod: t('vui.privacy-level.pod')
}))
</script>
<template>
......@@ -74,6 +206,7 @@ watch(open, (to) => {
v-for="item in items"
:key="item._id"
@mouseover="hovered = item"
@click="click(item, $event)"
:class="['item', item.type]"
>
<template v-if="item.type === 'button'">
......@@ -91,6 +224,48 @@ watch(open, (to) => {
v-model:open="childOpen[item._id]"
/>
</template>
<template v-else-if="item.type === 'select'">
<i :class="['bi', item.model.value ? 'bi-check-square' : 'bi-square']" />
{{ item.text }}
</template>
<div
v-if="'extraItems' in item && item.extraItems?.length"
class="extra-items"
>
<div
v-for="extra of item.extraItems"
:key="extra._id"
>
<fw-button
v-if="extra.type === 'circle-button'"
@click.stop="click(extra, $event)"
:icon="extra.icon"
secondary
round
/>
<fw-pill
v-if="extra.type === 'library-privacy-level'"
@click.stop="privacyLevel = extra"
@mouseleave="privacyLevel = undefined"
:class="{ 'is-open': privacyLevel?._id === extra._id }"
:secondary="extra.model.value === 'private'"
:red="extra.model.value === 'public'"
:blue="extra.model.value === 'pod'"
>
{{ privacyLevelLabels[extra.model.value] }}
<i class="bi bi-chevron-down" />
<fw-popover
:items="privacyLevelItems"
:click-outside="false"
v-model:open="extraOpen[extra._id ?? '']"
/>
</fw-pill>
</div>
</div>
</div>
</div>
</template>
......
......@@ -29,6 +29,7 @@
}
> .item {
&.select,
&.button {
cursor: pointer;
padding: 0 8px;
......@@ -36,6 +37,7 @@
display: flex;
align-items: center;
border-radius: var(--fw-border-radius);
white-space: pre;
&:hover {
background-color: var(--fw-grey-100);
......@@ -57,6 +59,32 @@
left: 90%;
align-self: flex-start;
}
> .extra-items {
margin-left: auto;
display: flex;
gap: 4px;
margin-right: -12px;
> :first-child {
margin-left: 2rem;
}
.funkwhale.pill {
> .pill-content > .bi {
display: inline-block;
transition: transform .2s ease;
will-change: transform;
}
&.is-open {
> .pill-content > .bi {
transform: rotate(-180deg);
}
}
}
}
}
&.separator {
......
......@@ -9,7 +9,7 @@ export interface PastelProps {
}
type Color = 'is-red' | 'is-blue' | 'is-purple' | 'is-green' | 'is-yellow'
export const usePastel = (props: PastelProps, defaultColor: Color = 'is-blue') => computed<Color>(() => props.blue
export const usePastel = (props: PastelProps, defaultColor: Color | '' = 'is-blue') => computed<Color | ''>(() => props.blue
? 'is-blue'
: props.red
? 'is-red'
......
......@@ -8,3 +8,7 @@ vui:
pagination:
previous: Previous
next: Next
privacy-level:
private: private
public: public
pod: pod
......@@ -56,7 +56,7 @@
--fw-pastel-yellow-4: #efa300;
// Background
--fw-bg-color: #fff;
--fw-bg-color: var(--fw-blue-010);
--fw-text-color: var(--fw-blue-900);
// Override Bulma
......@@ -145,6 +145,9 @@ html.dark {
}
.funkwhale {
--fw-bg-color: var(--fw-blue-010);
--fw-text-color: var(--fw-blue-900);
@each $pastel in ("blue", "red", "green", "purple", "yellow") {
&.is-#{$pastel} {
--fw-pastel-1: var(--fw-pastel-#{$pastel}-1);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment