diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 91152ef6e675788b24dfd40a0e0ddc025759257a..46799e0597eead6f2eb398562fe17fa5ccc517c0 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -14,6 +14,8 @@ export default defineConfig({ text: "Components", items: [ { text: "Pill", link: "/components/pill/" }, + { text: "Alert", link: "/components/alert/" }, + { text: "Modal", link: "/components/modal/" }, { text: "Loader", link: "/components/loader/" }, { text: "Activity", link: "/components/activity/" }, { text: "Card", items: [ diff --git a/docs/components/alert/index.md b/docs/components/alert/index.md new file mode 100644 index 0000000000000000000000000000000000000000..b3b8de9ced9c8db70bebe57d1a97bd8b2f0263c4 --- /dev/null +++ b/docs/components/alert/index.md @@ -0,0 +1,102 @@ +# Alert + + +| Prop | Data type | Required? | Default | Description | +| ------------- | -------------------------------------------------- | --------- | ----------- | ----------------------- | +| `color` | `blue` \| `red` \| `purple` \| `green` \| `yellow` | No | `secondary` | Renders a colored alert | + + + +## Alert colors + +Funkwhale alert support a range of pastel colors to create visually appealing interfaces. + +::: details Colors + +- Red +- Blue +- Purple +- Green +- Yellow + +::: + +### Blue + +```vue-html +<fw-alert color="blue"> + Blue alert +</fw-alert> +``` + +<fw-alert color="blue"> + Blue alert +</fw-alert> + +### Red + +```vue-html +<fw-alert color="red"> + Red alert +</fw-alert> +``` + +<fw-alert color="red"> + Red alert +</fw-alert> + +### Purple + +```vue-html +<fw-alert color="purple"> + Purple alert +</fw-alert> +``` + +<fw-alert color="purple"> + Purple alert +</fw-alert> + +### Green + +```vue-html +<fw-alert color="green"> + Green alert +</fw-alert> +``` + +<fw-alert color="green"> + Green alert +</fw-alert> + +### Yellow + +```vue-html +<fw-alert color="yellow"> + Yellow alert +</fw-alert> +``` + +<fw-alert color="yellow"> + Yellow alert +</fw-alert> + +## Alert actions + +```vue-html{2-4} +<fw-alert> + Awesome artist + + <template #actions> + <fw-button>Got it</fw-button> + </template> +</fw-alert> +``` + +<fw-alert> + Awesome artist + <template #actions> + <fw-button>Got it</fw-button> + </template> +</fw-alert> + diff --git a/docs/components/modal/index.md b/docs/components/modal/index.md new file mode 100644 index 0000000000000000000000000000000000000000..0c6935bd632b1cfec97d08a74e953e8e4674a75d --- /dev/null +++ b/docs/components/modal/index.md @@ -0,0 +1,164 @@ +<script setup lang="ts"> +import { ref, watchEffect } from 'vue' + +const open = ref(false) +const open2 = ref(false) + +const open3 = ref(false) +const alertOpen = ref(true) +watchEffect(() => { + if (open3.value === false) { + alertOpen.value = true + } +}) + +const open4 = ref(false) +const open5 = ref(false) + +</script> + +# Modal + + +| Prop | Data type | Required? | Default | Description | +| ------------- | ----------------- | --------- | ------- | ------------------------ | +| `title` | `string` | Yes | | Modal title | +| `v-model` | `true` \| `false` | Yes | | Should the modal be open | + + + +## Modal open + + +```vue-html +<fw-modal v-model="open" title="My modal"> + Modal content +</fw-modal> + +<fw-button @click="open = true"> + Open modal +</fw-button> +``` + +<fw-modal v-model="open" title="My modal"> + Modal content +</fw-modal> +<fw-button @click="open = true"> + Open modal +</fw-button> + +## Modal actions + +```vue-html +<fw-modal v-model="open" title="My modal"> + Modal content + + <template #actions> + <fw-button @click="open = false" color="secondary"> + Cancel + </fw-button> + + <fw-button @click="open = false"> + Ok + </fw-button> + </template> +</fw-modal> + +<fw-button @click="open = true"> + Open modal +</fw-button> +``` + +<fw-modal v-model="open2" title="My modal"> + Modal content + <template #actions> + <fw-button @click="open2 = false" color="secondary"> + Cancel + </fw-button> + <fw-button @click="open2 = false"> + Ok + </fw-button> + </template> +</fw-modal> +<fw-button @click="open2 = true"> + Open modal +</fw-button> + +## Nested modals + +```vue-html +<fw-modal v-model="open" title="My modal"> + Modal content + + <template #actions> + <fw-button @click="open = false" color="secondary"> + Cancel + </fw-button> + + <fw-button @click="open = false"> + Ok + </fw-button> + </template> +</fw-modal> + +<fw-button @click="open = true"> + Open modal +</fw-button> +``` + +<fw-modal v-model="open4" title="My modal"> +<fw-modal v-model="open5" title="My modal"> + Nested modal content +</fw-modal> + <fw-button @click="open5 = true"> + Open nested modal + </fw-button> +</fw-modal> +<fw-button @click="open4 = true"> + Open modal +</fw-button> + +## Alert inside modal + +```vue-html +<fw-modal v-model="open" title="My modal"> + Modal content + + <template #alert v-if="alertOpen"> + <fw-alert> + Alert content + + <template #actions> + <fw-button @click="alertOpen = false">Close alert</fw-button> + </template> + </fw-alert> + </template> +</fw-modal> + +<fw-button @click="open = true"> + Open modal +</fw-button> +``` + +<fw-modal v-model="open3" title="My modal"> + Modal content + <template #alert v-if="alertOpen"> + <fw-alert> + Alert content + <template #actions> + <fw-button @click="alertOpen = false">Close alert</fw-button> + </template> + </fw-alert> + </template> + <template #actions> + <fw-button @click="open3 = false" color="secondary"> + Cancel + </fw-button> + <fw-button @click="open3 = false"> + Ok + </fw-button> + </template> +</fw-modal> +<fw-button @click="open3 = true"> + Open modal +</fw-button> diff --git a/src/components/alert/Alert.vue b/src/components/alert/Alert.vue new file mode 100644 index 0000000000000000000000000000000000000000..c4e13e4bc135dff895e7423e03921c9f854fbb7f --- /dev/null +++ b/src/components/alert/Alert.vue @@ -0,0 +1,23 @@ +<script setup lang="ts"> +import { useColorOrPastel } from '~/composables/colors' + +const props = defineProps<PastelProps>() +const color = useColorOrPastel(() => props.color, 'blue') +</script> + +<template> + <div + class="funkwhale is-colored alert" + :class="[color]" + > + <slot /> + + <div v-if="$slots.actions" class="actions"> + <slot name="actions" /> + </div> + </div> +</template> + +<style lang="scss"> +@import './style.scss' +</style> diff --git a/src/components/alert/style.scss b/src/components/alert/style.scss new file mode 100644 index 0000000000000000000000000000000000000000..7ad80ae919b4d4c7e030ebbed425c5c0c0a47cee --- /dev/null +++ b/src/components/alert/style.scss @@ -0,0 +1,45 @@ +.funkwhale.alert { + color: var(--fw-gray-900); + + @include light-theme { + background-color: var(--fw-pastel-1, var(--fw-bg-color)); + + > .actions .funkwhale.button { + --fw-bg-color: var(--fw-pastel-2); + + &:hover, &.is-hovered { + --fw-bg-color: var(--fw-pastel-3); + } + + &:active, &.is-active { + --fw-bg-color: var(--fw-pastel-4); + } + } + } + + @include dark-theme { + background-color: var(--fw-pastel-4, var(--fw-bg-color)); + + > .actions .funkwhale.button { + --fw-bg-color: var(--fw-pastel-3); + box-shadow: 0 2px 4px 2px rgba(#000, 0.1); + + &:hover, &.is-hovered { + --fw-bg-color: var(--fw-pastel-2); + } + + &:active, &.is-active { + --fw-bg-color: var(--fw-pastel-1); + } + } + } + + padding: 0.625rem 2rem; + line-height: 1.2; + display: flex; + align-items: center; + + > .actions { + margin-left: auto; + } +} diff --git a/src/components/index.ts b/src/components/index.ts index 095bfe399d44a8568d91b72a5a5311b0cd76d7f4..72fdb4fa7dbf64f3f9ef2732b67232e3e4add79f 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -43,3 +43,9 @@ 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' + +// Modal +export { default as FwModal } from './modal/Modal.vue' + +// Alert +export { default as FwAlert } from './alert/Alert.vue' diff --git a/src/components/modal/Modal.vue b/src/components/modal/Modal.vue new file mode 100644 index 0000000000000000000000000000000000000000..6501e1605bb175b7f1a82f5c960c03e702358d20 --- /dev/null +++ b/src/components/modal/Modal.vue @@ -0,0 +1,37 @@ +<script setup lang="ts"> +const title = defineProp<string>('title', { required: true }) +const open = defineModel<boolean>({ required: true }) +</script> + +<template> + <Teleport to="body"> + <Transition mode="out-in"> + <div v-if="open" @click.exact.stop="open = false" class="funkwhale overlay"> + <div @click.stop class="funkwhale modal"> + <h2>{{ title }}</h2> + + <Transition> + <div v-if="$slots.alert" class="alert-container"> + <div> + <slot name="alert" /> + </div> + </div> + </Transition> + <hr /> + + <div class="modal-content"> + <slot /> + </div> + <hr v-if="$slots.actions" /> + <div v-if="$slots.actions" class="modal-actions"> + <slot name="actions" /> + </div> + </div> + </div> + </Transition> + </Teleport> +</template> + +<style lang="scss"> +@import './style.scss' +</style> diff --git a/src/components/modal/style.scss b/src/components/modal/style.scss new file mode 100644 index 0000000000000000000000000000000000000000..6a0c898e6c9948b0d82fe2d1acde18ae2703bed4 --- /dev/null +++ b/src/components/modal/style.scss @@ -0,0 +1,85 @@ +.funkwhale.modal { + background: var(--fw-bg-color); + + box-shadow: 0 2px 4px 2px rgba(#000, 0.2); + border-radius: 1rem; + min-width: 32rem; + max-width: 90vw; + + > hr { + padding: 0; + margin: 0; + } + + > .alert-container { + display: grid; + grid-template-rows: 1fr; + margin-bottom: -1px; + + &.v-enter-active, + &.v-leave-active { + transition: grid-template-rows 0.2s ease; + } + + &.v-enter-from, + &.v-leave-to { + grid-template-rows: 0fr; + } + + > div { + overflow: hidden; + } + } + + > h2 { + font-size: 1.25em; + padding: 1.625rem 4.5rem 1rem; + line-height: 1.2; + text-align: center; + } + + .modal-content { + padding: 1rem 2rem 1rem; + } + + .modal-actions { + padding: 0.75rem 2rem 1rem; + } +} + +.funkwhale.overlay { + background: rgba(#000, .2); + + position: fixed; + inset: 0; + + z-index: 9001; + + display: flex; + align-items: center; + justify-content: center; + + &.v-enter-active, + &.v-leave-active { + transition: opacity 0.2s ease; + + .funkwhale.modal { + transition: transform 0.2s ease; + } + } + + &.v-enter-from, + &.v-leave-to { + opacity: 0; + + .funkwhale.modal { + transform: translateY(1rem); + } + } + + &.v-leave-to { + .funkwhale.modal { + transform: translateY(-1rem); + } + } +} diff --git a/src/styles/base/generic.scss b/src/styles/base/generic.scss index 5a346354a8856837a6d1fbd91a9a8384eba2208c..ba94089e0f7fd13a8c56223feaeedc1611f8bc23 100644 --- a/src/styles/base/generic.scss +++ b/src/styles/base/generic.scss @@ -50,6 +50,7 @@ // Horizontal rule %hr { + border: none; border-bottom: 1px solid var(--fw-border-color); @include light-theme { @@ -62,7 +63,6 @@ padding: 8px 0 0 0; margin: 0 0 8px 0; - border: none; } hr:not(.is-raw) { diff --git a/src/styles/funkwhale.scss b/src/styles/funkwhale.scss index 6cb4ca2ec4ae8d4ce46f03ad059edaf5c74cfded..b4260465b043f67c3b1003da75a5de1e732fc37c 100644 --- a/src/styles/funkwhale.scss +++ b/src/styles/funkwhale.scss @@ -3,9 +3,9 @@ @import "font"; @import "colors"; -body { +html, body { font-family: $font-main; - font-size: 16px; + font-size: 16px !important; // background-color: var(--fw-bg-color); }