diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index b2955ae134f045362d32d105a7c99f7b525e9e13..91152ef6e675788b24dfd40a0e0ddc025759257a 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -13,12 +13,9 @@ export default defineConfig({ { text: "Components", items: [ - { text: "Button", items: [ - { text: "Basic Button", link: "/components/button/basic" }, - { text: "Play Button", link: "/components/button/play" }, - ] }, { text: "Pill", link: "/components/pill/" }, { text: "Loader", link: "/components/loader/" }, + { text: "Activity", link: "/components/activity/" }, { text: "Card", items: [ { text: "Basic Card", link: "/components/card/basic" }, { text: "Radio Card", link: "/components/card/radio" }, @@ -27,14 +24,21 @@ export default defineConfig({ { text: "Playlist Card", link: "/components/card/playlist" }, { text: "Podcast Card", link: "/components/card/podcast" }, ] }, - { text: "Activity", link: "/components/activity/" }, - { text: "Popover", link: "/components/popover/" }, { text: "Form", items: [ { text: "Input", link: "/components/input/" }, - { text: "Pagination", link: "/components/pagination/" }, { text: "Textarea", link: "/components/textarea/" }, + { text: "Toggle", link: "/components/toggle/" }, + { text: "Popover", link: "/components/popover/" }, + { text: "Button", items: [ + { text: "Basic Button", link: "/components/button/basic" }, + { text: "Play Button", link: "/components/button/play" }, + ] }, + ] }, + { text: "Content Navigation", items: [ + { text: "Table of Contents", link: "/components/toc/" }, + { text: "Pagination", link: "/components/pagination/" }, + { text: "Tabs", link: "/components/tabs/" }, ] }, - { text: "Table of Contents", link: "/components/toc/" }, ] } ] diff --git a/docs/components/tabs/index.md b/docs/components/tabs/index.md new file mode 100644 index 0000000000000000000000000000000000000000..5f73ab9cf5b1a819c868b745eb04d85a0c9dd9cc --- /dev/null +++ b/docs/components/tabs/index.md @@ -0,0 +1,49 @@ +# Tabs +::: warning +The `<fw-tab>` components won't render outside of the `<fw-tabs>` component. +::: + +```html +<fw-tabs> + <fw-tab title="Overview">Overview content</fw-tab> + <fw-tab title="Activity">Activity content</fw-tab> +</fw-tabs> +``` +<fw-tabs> + <fw-tab title="Overview">Overview content</fw-tab> + <fw-tab title="Activity">Activity content</fw-tab> +</fw-tabs> + +## Tabs-right slot +```html +<fw-tabs> + <fw-tab title="Overview">Overview content</fw-tab> + <fw-tab title="Activity">Activity content</fw-tab> + + <template #tabs-right> + <fw-input icon="bi-search" placeholder="Search" /> + </template> +</fw-tabs> +``` +<fw-tabs> + <fw-tab title="Overview">Overview content</fw-tab> + <fw-tab title="Activity">Activity content</fw-tab> + + <template #tabs-right> + <fw-input icon="bi-search" placeholder="Search" /> + </template> +</fw-tabs> + +## Duplicated tabs render as a single tab +```html +<fw-tabs> + <fw-tab title="Overview">Overview content</fw-tab> + <fw-tab title="Activity">Activity content</fw-tab> + <fw-tab title="Overview">More overview content</fw-tab> +</fw-tabs> +``` +<fw-tabs> + <fw-tab title="Overview">Overview content</fw-tab> + <fw-tab title="Activity">Activity content</fw-tab> + <fw-tab title="Overview">More overview content</fw-tab> +</fw-tabs> diff --git a/docs/components/toggle/index.md b/docs/components/toggle/index.md new file mode 100644 index 0000000000000000000000000000000000000000..a10b78996abd3fb3a1a150c7242d550b554a7641 --- /dev/null +++ b/docs/components/toggle/index.md @@ -0,0 +1,18 @@ +<script setup lang="ts"> +import { ref } from 'vue' + +const toggle = ref(false) +</script> + +# Toggle +```html +<fw-toggle v-model="toggle" /> +``` +<fw-toggle v-model="toggle" /> + +## Big toggle +```html +<fw-toggle big v-model="toggle" /> +``` +<fw-toggle big v-model="toggle" /> + diff --git a/src/components/activity/style.scss b/src/components/activity/style.scss index 5809013d7b78397c020cd69dd51537f7fd3f052d..4ed1686f5496c7c1beb5f1d9feabbb81f4d63213 100644 --- a/src/components/activity/style.scss +++ b/src/components/activity/style.scss @@ -10,7 +10,7 @@ gap: 12px; + .funkwhale.activity { - border-top: 1px solid var(--fw-grey-300); + border-top: 1px solid var(--fw-gray-300); } > .activity-image { @@ -52,27 +52,27 @@ align-items: flex-start; > .track-title { - color: var(--fw-grey-900); + color: var(--fw-gray-900); font-weight: 700; line-height: 1.5em; html.dark & { - color: var(--fw-grey-300); + color: var(--fw-gray-300); } } > .artist { - --fw-link-color: var(--fw-grey-900); + --fw-link-color: var(--fw-gray-900); line-height: 1.5em; font-size: 0.857rem; html.dark & { - --fw-link-color: var(--fw-grey-300); + --fw-link-color: var(--fw-gray-300); } } > .user { - --fw-link-color: var(--fw-grey-500); + --fw-link-color: var(--fw-gray-500); line-height: 1.5em; font-size: 0.8125rem; } diff --git a/src/components/button/play/style.scss b/src/components/button/play/style.scss index 91acfef3fd4d895f08a9a9a079f690967feda692..5c48813c08fc0462837425add28d79ce533437d7 100644 --- a/src/components/button/play/style.scss +++ b/src/components/button/play/style.scss @@ -1,7 +1,7 @@ .funkwhale { &.play-button { --fw-bg-color: #fff !important; - --fw-text-color: var(--fw-grey-600) !important; + --fw-text-color: var(--fw-gray-600) !important; will-change: transform; font-size: 0.6rem !important; diff --git a/src/components/button/style.scss b/src/components/button/style.scss index bdef74a2c8abb51fb400fa580bed6e1b60eddb1a..08094d5d92a7e31506bd836b1e599d75b6f7109f 100644 --- a/src/components/button/style.scss +++ b/src/components/button/style.scss @@ -39,24 +39,24 @@ } &.is-secondary.is-outline { - --fw-bg-color: var(--fw-grey-600); + --fw-bg-color: var(--fw-gray-600); &.is-hovered, &:hover { - --fw-bg-color: var(--fw-grey-700); + --fw-bg-color: var(--fw-gray-700); } &.is-active, &:active { - --fw-text-color: var(--fw-grey-300); + --fw-text-color: var(--fw-gray-300); } &[disabled] { - --fw-bg-color: var(--fw-grey-400) !important; + --fw-bg-color: var(--fw-gray-400) !important; background-color: transparent !important; html.dark & { - --fw-bg-color: var(--fw-grey-800) !important; + --fw-bg-color: var(--fw-gray-800) !important; } } } diff --git a/src/components/card/radio/style.scss b/src/components/card/radio/style.scss index 9d31a7866dd6654fc8b7fed3017735fde06301d2..db3c96bea92755ad5b15bdc7f453aed502bbdb75 100644 --- a/src/components/card/radio/style.scss +++ b/src/components/card/radio/style.scss @@ -5,7 +5,7 @@ --fw-card-image-width: var(--fw-card-width); --fw-card-padding: 0; --fw-bg-color: var(--fw-pastel-3); - --fw-text-color: var(--fw-grey-100); + --fw-text-color: var(--fw-gray-100); text-align: left !important; background: var(--fw-bg-color); @@ -37,7 +37,7 @@ .radio-description { --fw-bg-color: var(--fw-pastel-1); - --fw-text-color: var(--fw-grey-900); + --fw-text-color: var(--fw-gray-900); text-align: center; font-size: 0.875rem; diff --git a/src/components/card/style.scss b/src/components/card/style.scss index 23448ed0cd490aba85b90d8bc2d37919669e316d..acd00fecbd8ff9c6339b46aa14629553293fbf1e 100644 --- a/src/components/card/style.scss +++ b/src/components/card/style.scss @@ -98,9 +98,9 @@ font-size: 0.8125rem; padding-top: 6px; - color: var(--fw-grey-700); + color: var(--fw-gray-700); html.dark & { - color: var(--fw-grey-500); + color: var(--fw-gray-500); } > .options-button { diff --git a/src/components/index.ts b/src/components/index.ts index 9e9df95137658b66de6cfca66c964f2cfc92de3a..d2be292d82915b1d2fe5f309a63625390713b920 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -23,6 +23,7 @@ export { default as FwLoader } from './loader/Loader.vue' // Form export { default as FwPagination } from './pagination/Pagination.vue' export { default as FwTextarea } from './textarea/Textarea.vue' +export { default as FwToggle } from './toggle/Toggle.vue' export { default as FwInput } from './input/Input.vue' // Pills @@ -31,6 +32,10 @@ export { default as FwPill } from './pill/Pill.vue' // Toc export { default as FwToc } from './toc/Toc.vue' +// Tabs +export { default as FwTabs } from './tabs/Tabs.vue' +export { default as FwTab } from './tabs/Tab.vue' + // Utils export { default as FwSanitizedHtml } from './utils/SanitizedHtml.vue' export { default as FwMarkdown } from './utils/Markdown.vue' diff --git a/src/components/input/style.scss b/src/components/input/style.scss index 96d5f8ff8cc7ba44245c1713ff29cadbefbf28a6..0635a52792577ee48f45909b89309cd3a650d8ca 100644 --- a/src/components/input/style.scss +++ b/src/components/input/style.scss @@ -1,10 +1,10 @@ .funkwhale { &.input { - --fw-bg-color: var(--fw-grey-100); + --fw-bg-color: var(--fw-gray-100); --fw-border-color: var(--fw-bg-color); html.dark & { - --fw-bg-color: var(--fw-grey-800); + --fw-bg-color: var(--fw-gray-800); } position: relative; @@ -36,11 +36,11 @@ background-color: transparent; &::placeholder { - color: var(--fw-grey-600); + color: var(--fw-gray-600); } &:hover { - --fw-border-color: var(--fw-grey-300); + --fw-border-color: var(--fw-gray-300); } } @@ -48,7 +48,7 @@ .suffix { display: flex; align-items: center; - color: var(--fw-grey-600); + color: var(--fw-gray-600); font-size: 14px; pointer-events: none; } diff --git a/src/components/pagination/style.scss b/src/components/pagination/style.scss index d65eae511a30f66a36e8f7aa133289502737636f..97fd748f6decdafc1e2362898b060f70a358642c 100644 --- a/src/components/pagination/style.scss +++ b/src/components/pagination/style.scss @@ -47,7 +47,7 @@ } > .goto { - border-left: 1px solid var(--fw-grey-200); + border-left: 1px solid var(--fw-gray-200); margin-left: 16px; padding-left: 16px; display: flex; diff --git a/src/components/popover/style.scss b/src/components/popover/style.scss index 17d87ea9b6ac39b10a8b7a48f36a5b1d53699c69..eace4eeb5298c72ed2c2db27ef1bee8569ddeab7 100644 --- a/src/components/popover/style.scss +++ b/src/components/popover/style.scss @@ -1,9 +1,9 @@ .funkwhale { &.popover { - --fw-border-color: var(--fw-grey-500); + --fw-border-color: var(--fw-gray-500); html.dark & { - --fw-border-color: var(--fw-grey-800); + --fw-border-color: var(--fw-gray-800); } position: absolute; @@ -40,10 +40,10 @@ white-space: pre; &:hover { - background-color: var(--fw-grey-100); + background-color: var(--fw-gray-100); html.dark & { - background-color: var(--fw-grey-800); + background-color: var(--fw-gray-800); } } @@ -73,7 +73,7 @@ .funkwhale.pill { font-size: 13px; - + > .pill-content > .bi { display: inline-block; transition: transform .2s ease; diff --git a/src/components/tabs/Tab.vue b/src/components/tabs/Tab.vue new file mode 100644 index 0000000000000000000000000000000000000000..9c15f384eb8675a7cce415a803e52d1a8102ab39 --- /dev/null +++ b/src/components/tabs/Tab.vue @@ -0,0 +1,26 @@ +<script setup lang="ts"> +import { TABS_INJECTION_KEY } from '~/injection-keys' +import { whenever } from '@vueuse/core' +import { inject, ref } from 'vue' + +interface Props { + title: string +} + +const props = defineProps<Props>() + +const { currentTab, tabs } = inject(TABS_INJECTION_KEY, { + currentTab: ref(props.title), + tabs: [] +}) + +whenever(() => !tabs.includes(props.title), () => { + tabs.push(props.title) +}, { immediate: true }) +</script> + +<template> + <div v-if="currentTab === title" class="tab-content"> + <slot /> + </div> +</template> diff --git a/src/components/tabs/Tabs.vue b/src/components/tabs/Tabs.vue new file mode 100644 index 0000000000000000000000000000000000000000..8b11f549a847271bb946f5f7ca4b888bf7b84349 --- /dev/null +++ b/src/components/tabs/Tabs.vue @@ -0,0 +1,46 @@ +<script setup lang="ts"> +import { TABS_INJECTION_KEY } from '~/injection-keys' +import { provide, reactive, ref, watch } from 'vue' + +const tabs = reactive([] as string[]) +const currentTab = ref() + +provide(TABS_INJECTION_KEY, { + tabs, + currentTab +}) + +watch(() => tabs.length, (to, from) => { + if (from === 0) { + currentTab.value = tabs[0] + } +}) +</script> + +<template> + <div class="funkwhale tabs"> + <div class="tabs-header"> + <div + v-for="tab in tabs" + :key="tab" + :class="{ 'is-active': currentTab === tab }" + @click="currentTab = tab" + class="tabs-item" + > + {{ tab }} + </div> + + <div class="tabs-right"> + <slot name="tabs-right" /> + </div> + </div> + + <hr /> + + <slot /> + </div> +</template> + +<style lang="scss"> +@import './style.scss' +</style> diff --git a/src/components/tabs/style.scss b/src/components/tabs/style.scss new file mode 100644 index 0000000000000000000000000000000000000000..8ad244b8469cf0b5a604ab92fca14c4cb946812f --- /dev/null +++ b/src/components/tabs/style.scss @@ -0,0 +1,24 @@ +.funkwhale { + &.tabs { + > .tabs-header { + display: flex; + align-items: center; + + > .tabs-item { + font-size: 14px; + padding: 8px; + min-width: 96px; + text-align: center; + cursor: pointer; + + &.is-active { + font-weight: 900; + } + } + + > .tabs-right { + margin-left: auto; + } + } + } +} diff --git a/src/components/textarea/style.scss b/src/components/textarea/style.scss index 4ca3c5df8f506023150500d5538523b67ee02ea9..417e7f3b768710e2adfe594c708b5e87191842a8 100644 --- a/src/components/textarea/style.scss +++ b/src/components/textarea/style.scss @@ -1,25 +1,25 @@ .funkwhale { &.textarea { --fw-border-color: var(--fw-bg-color); - --fw-buttons-border-color: var(--fw-grey-400); + --fw-buttons-border-color: var(--fw-gray-400); position: relative; padding: 8px; - background-color: var(--fw-grey-100); + background-color: var(--fw-gray-100); border-radius: var(--fw-border-radius); box-shadow: inset 0 0 0 4px var(--fw-border-color); html.dark & { - background-color: var(--fw-grey-900); - --fw-border-color: var(--fw-grey-900); - --fw-buttons-border-color: var(--fw-grey-800); + background-color: var(--fw-gray-900); + --fw-border-color: var(--fw-gray-900); + --fw-buttons-border-color: var(--fw-gray-800); } &:hover { - --fw-border-color: var(--fw-grey-300); + --fw-border-color: var(--fw-gray-300); html.dark & { - --fw-border-color: var(--fw-grey-700); + --fw-border-color: var(--fw-gray-700); } } diff --git a/src/components/toc/style.scss b/src/components/toc/style.scss index b4a0e819aae5c60002123ad087abaeb17db0fc00..fd0d258e02e6df3273987c57a7e6bd20ba9c0289 100644 --- a/src/components/toc/style.scss +++ b/src/components/toc/style.scss @@ -23,7 +23,7 @@ padding: 4px 8px; display: block; - box-shadow: inset 1px 0 0 var(--fw-grey-200); + box-shadow: inset 1px 0 0 var(--fw-gray-200); &.is-active { --fw-link-color: var(--fw-secondary); diff --git a/src/components/toggle/Toggle.vue b/src/components/toggle/Toggle.vue new file mode 100644 index 0000000000000000000000000000000000000000..307f9b35e51e17a7cc1515d146cf8bb62b87ca98 --- /dev/null +++ b/src/components/toggle/Toggle.vue @@ -0,0 +1,30 @@ +<script setup lang="ts"> +import { useVModel } from '@vueuse/core'; + + +interface Events { + (e: 'update:modelValue', enabled: boolean): void +} +interface Props { + modelValue: boolean + big?: boolean +} + +const emit = defineEmits<Events>() +const props = defineProps<Props>() + +const enabled = useVModel(props, 'modelValue', emit) +</script> + +<template> + <div + class="funkwhale toggle" + :class="{ 'is-active': enabled, 'is-big': big }" + > + <input type="checkbox" v-model="enabled" /> + </div> +</template> + +<style lang="scss"> +@import './style.scss' +</style> diff --git a/src/components/toggle/style.scss b/src/components/toggle/style.scss new file mode 100644 index 0000000000000000000000000000000000000000..e7ced584014823eb0fdc4377c34b754c78776d2b --- /dev/null +++ b/src/components/toggle/style.scss @@ -0,0 +1,60 @@ +.funkwhale { + &.toggle { + --fw-bg-color: var(--fw-gray-500); + + position: relative; + border-radius: 100vw; + background-color: var(--fw-bg-color); + overflow: hidden; + height: 20px; + aspect-ratio: 2; + + > input { + opacity: 0; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + cursor: pointer; + } + + &::before { + display: block; + content: ''; + position: absolute; + top: 2px; + left: 2px; + height: calc(100% - 4px); + aspect-ratio: 1; + border-radius: 50%; + background: var(--fw-blue-010); + transition: transform .2s ease; + } + + &:hover { + --fw-bg-color: var(--fw-gray-300); + } + + &.is-big { + height: 28px; + } + + &.is-active { + --fw-bg-color: var(--fw-primary); + + &::before { + transform: translateX(calc(100% + 4px)); + } + + &:hover { + --fw-bg-color: var(--fw-blue-100); + } + } + + &[disabled] { + opacity: 0; + pointer-events: none; + } + } +} diff --git a/src/injection-keys.ts b/src/injection-keys.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6d16d95e1089fc4de2522ad2c2bc82eca32500b --- /dev/null +++ b/src/injection-keys.ts @@ -0,0 +1,6 @@ +import type { InjectionKey, Ref } from "vue" + +export const TABS_INJECTION_KEY = Symbol('tabs') as InjectionKey<{ + tabs: string[] + currentTab: Ref<string> +}> diff --git a/src/styles/base/generic.scss b/src/styles/base/generic.scss index bc67443e51fe6219ba40aeb0c567daab974efcd8..f0f051c3a65a49a13e74cc8e683b54561a10788b 100644 --- a/src/styles/base/generic.scss +++ b/src/styles/base/generic.scss @@ -18,3 +18,11 @@ a { } } } + +.vp-doc hr, +hr { + padding: 8px 0 0 0; + margin: 0 0 8px 0; + border: none; + border-bottom: 2px solid var(--fw-gray-200); +} diff --git a/src/styles/colors.scss b/src/styles/colors.scss index e2b5b0bc3a83face468ffa30dc189b04a58a786e..1ff721ef95d8b86fa2548cc8cd9bd0b4e2ef7863 100644 --- a/src/styles/colors.scss +++ b/src/styles/colors.scss @@ -15,16 +15,16 @@ --fw-red-700: #9c1a0e; --fw-red-900: #520600; - // Grey - --fw-grey-100: #f2efef; - --fw-grey-200: #eee9e9; - --fw-grey-300: #ddd9d9; - --fw-grey-400: #cfcaca; - --fw-grey-500: #b3afaf; - --fw-grey-600: #908888; - --fw-grey-700: #605656; - --fw-grey-800: #5c4b4b; - --fw-grey-900: #292525; + // gray + --fw-gray-100: #f2efef; + --fw-gray-200: #eee9e9; + --fw-gray-300: #ddd9d9; + --fw-gray-400: #cfcaca; + --fw-gray-500: #b3afaf; + --fw-gray-600: #908888; + --fw-gray-700: #605656; + --fw-gray-800: #5c4b4b; + --fw-gray-900: #292525; // Beige --fw-beige-100: #fafaf6; @@ -67,13 +67,13 @@ html.dark { --fw-bg-color: var(--fw-blue-900); - --fw-text-color: var(--fw-grey-100); + --fw-text-color: var(--fw-gray-100); } .funkwhale { &.is-primary { --fw-bg-color: var(--fw-blue-500); - --fw-text-color: var(--fw-grey-100); + --fw-text-color: var(--fw-gray-100); &.is-colored { &[disabled] { @@ -94,36 +94,36 @@ html.dark { } &.is-secondary { - --fw-bg-color: var(--fw-grey-200); - --fw-text-color: var(--fw-grey-800); + --fw-bg-color: var(--fw-gray-200); + --fw-text-color: var(--fw-gray-800); html.dark & { - --fw-text-color: var(--fw-grey-300); + --fw-text-color: var(--fw-gray-300); } &.is-colored { &[disabled] { - --fw-bg-color: var(--fw-grey-100) !important; + --fw-bg-color: var(--fw-gray-100) !important; } &.is-hovered, &:hover { - --fw-bg-color: var(--fw-grey-300); + --fw-bg-color: var(--fw-gray-300); html.dark & { - --fw-text-color: var(--fw-grey-800); + --fw-text-color: var(--fw-gray-800); } } &.is-active, &:active { - --fw-bg-color: var(--fw-grey-400); + --fw-bg-color: var(--fw-gray-400); } } } &.is-destructive { --fw-bg-color: var(--fw-destructive); - --fw-text-color: var(--fw-grey-100); + --fw-text-color: var(--fw-gray-100); &.is-colored { &[disabled] { diff --git a/src/styles/funkwhale.scss b/src/styles/funkwhale.scss index 32a3daffca8304c924fb0bc562bbe5ca99e74c10..4cff967eaa7185ff2fd3214c5cd57e0ab50d5c4c 100644 --- a/src/styles/funkwhale.scss +++ b/src/styles/funkwhale.scss @@ -8,7 +8,7 @@ body { } .has-background-light { - background-color: var(--fw-grey-100); + background-color: var(--fw-gray-100); } * {