refactor: sidebar and navbar

pull/165/head
Kia King Ishii 4 years ago
parent 5bb4730f7f
commit 077784fa34

@ -4,7 +4,7 @@
<div class="flex-grow" />
<div class="nav">
<NavBarLinks />
<NavLinks />
</div>
<slot name="search" />
@ -12,7 +12,7 @@
<script setup lang="ts">
import NavBarTitle from './NavBarTitle.vue'
import NavBarLinks from './NavBarLinks.vue'
import NavLinks from './NavLinks.vue'
</script>
<style scoped>

@ -1,127 +0,0 @@
<template>
<div class="navbar-link">
<a
class="item"
:class="classes"
:href="href"
:target="target"
:rel="rel"
:aria-label="item.ariaLabel"
>
{{ item.text }} <OutboundLink v-if="isExternalLink" />
</a>
</div>
</template>
<script setup lang="ts">
import { computed, defineProps } from 'vue'
import { useRoute } from 'vitepress'
import { isExternal } from '../utils'
import type { DefaultTheme } from '../config'
import { useUrl } from '../composables/url'
import OutboundLink from './icons/OutboundLink.vue'
const { item } = defineProps<{
item: DefaultTheme.NavItemWithLink
}>()
const normalizePath = (path: string): string => {
path = path
.replace(/#.*$/, '')
.replace(/\?.*$/, '')
.replace(/\.html$/, '')
if (path.endsWith('/')) {
path += 'index'
}
return path
}
const { withBase } = useUrl()
const route = useRoute()
const classes = computed(() => ({
active: isActiveLink.value,
external: isExternalLink.value
}))
const isActiveLink = computed(() => {
return normalizePath(withBase(item.link)) === normalizePath(route.path)
})
const isExternalLink = computed(() => {
return isExternal(item.link)
})
const href = computed(() => {
return isExternalLink.value ? item.link : withBase(item.link)
})
const target = computed(() => {
if (item.target) {
return item.target
}
return isExternalLink.value ? '_blank' : ''
})
const rel = computed(() => {
if (item.rel) {
return item.rel
}
return isExternalLink.value ? 'noopener noreferrer' : ''
})
</script>
<style scoped>
.navbar-link {
position: relative;
padding: 0 1.5rem;
}
@media (min-width: 720px) {
.navbar-link {
padding: 0;
}
.navbar-link + .navbar-link,
.dropdown-wrapper + .navbar-link {
padding-left: 1.5rem;
}
}
.item {
display: block;
margin-bottom: -2px;
line-height: 40px;
font-size: 1rem;
font-weight: 600;
color: var(--c-text);
white-space: nowrap;
}
.item:hover,
.item.active {
text-decoration: none;
color: var(--c-brand);
}
@media (min-width: 720px) {
.item {
border-bottom: 2px solid transparent;
line-height: 1.5rem;
font-size: .9rem;
font-weight: 500;
}
.item:hover,
.item.active {
color: var(--c-text);
border-bottom-color: var(--c-brand);
}
.item.external:hover {
border-bottom-color: transparent;
}
}
</style>

@ -1,116 +0,0 @@
<template>
<nav v-if="navData || repoInfo" class="navbar-links">
<template v-if="navData">
<template v-for="item of navData">
<NavDropdownLink v-if="item.items" :item="item" />
<NavBarLink v-else :item="item" />
</template>
</template>
<NavDropdownLink v-if="localeCandidates" :item="localeCandidates" />
<NavBarLink v-if="repoInfo" :item="repoInfo" />
</nav>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useSiteData, useSiteDataByRoute, useRoute } from 'vitepress'
import { inBrowser } from '/@app/utils'
import NavBarLink from './NavBarLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'
import type { DefaultTheme } from '../config'
const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map(
(platform) => [platform, new RegExp(platform, 'i')] as const
)
const siteDataByRoute = useSiteDataByRoute()
const siteData = useSiteData()
const route = useRoute()
const repoInfo = computed(() => {
const theme = siteData.value.themeConfig as DefaultTheme.Config
const repo = theme.docsRepo || theme.repo
let text: string | undefined = theme.repoLabel
if (repo) {
const link = /^https?:/.test(repo) ? repo : `https://github.com/${repo}`
if (!text) {
// if no label is provided, deduce it from the repo url
const repoHosts = link.match(/^https?:\/\/[^/]+/)
if (repoHosts) {
const repoHost = repoHosts[0]
const foundPlatform = platforms.find(([_platform, re]) =>
re.test(repoHost)
)
text = foundPlatform && foundPlatform[0]
}
}
return { link, text: text || 'Source' }
}
return null
})
const localeCandidates = computed(() => {
const locales = siteData.value.themeConfig.locales
if (!locales) {
return null
}
const localeKeys = Object.keys(locales)
if (localeKeys.length <= 1) {
return null
}
// handle site base
const siteBase = inBrowser ? siteData.value.base : '/'
const siteBaseWithoutSuffix = siteBase.endsWith('/')
? siteBase.slice(0, -1)
: siteBase
// remove site base in browser env
const routerPath = route.path.slice(siteBaseWithoutSuffix.length)
const currentLangBase = localeKeys.find((v) => {
if (v === '/') {
return false
}
return routerPath.startsWith(v)
})
const currentContentPath = currentLangBase
? routerPath.substring(currentLangBase.length - 1)
: routerPath
const candidates = localeKeys.map((v) => {
const localePath = v.endsWith('/') ? v.slice(0, -1) : v
return {
text: locales[v].label || locales[v].lang,
link: `${localePath}${currentContentPath}`
}
})
const currentLangKey = currentLangBase ? currentLangBase : '/'
const selectText = locales[currentLangKey].selectText
? locales[currentLangKey].selectText
: 'Languages'
return {
text: selectText,
items: candidates
}
})
const navData = computed(() => {
return siteDataByRoute.value.themeConfig.nav
})
</script>
<style scoped>
.navbar-links {
padding: 1rem 0;
border-bottom: 1px solid var(--c-divider);
}
@media (min-width: 720px) {
.navbar-links {
display: flex;
align-items: center;
border-bottom: 0;
}
}
</style>

@ -1,249 +1,130 @@
<template>
<div class="dropdown-wrapper" :class="{ open }">
<button
class="dropdown-title"
type="button"
:aria-label="item.ariaLabel"
@click="setOpen(!open)"
>
<span>{{ item.text }}</span>
<span class="arrow" :class="open ? 'down' : 'right'" />
<div class="nav-dropdown-link" :class="{ open }">
<button class="button" :aria-label="item.ariaLabel" @click="toggle">
<span class="button-text">{{ item.text }}</span>
<span class="button-arrow" :class="open ? 'down' : 'right'" />
</button>
<ul class="nav-dropdown">
<li
v-for="(subItem, index) in item.items"
:key="subItem.link || index"
class="dropdown-item"
>
<h4 v-if="subItem.items">{{ subItem.text }}</h4>
<ul v-if="subItem.items" class="dropdown-subitem-wrapper">
<li
v-for="childSubItem in subItem.items"
:key="childSubItem.link"
class="dropdown-subitem"
>
<NavBarLink
:item="childSubItem"
@focusout="
isLastItemOfArray(childSubItem, subItem.items) &&
isLastItemOfArray(subItem, item.items) &&
setOpen(false)
"
/>
</li>
</ul>
<NavBarLink
v-else
:item="subItem"
@focusout="isLastItemOfArray(subItem, item.items) && setOpen(false)"
/>
<ul class="dialog">
<li v-for="item in item.items" :key="item.text" class="dialog-item">
<NavDropdownLinkItem :item="item" />
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import NavBarLink from './NavBarLink.vue'
import { ref, watch, defineProps } from 'vue'
import { watch, defineProps } from 'vue'
import { useRoute } from 'vitepress'
import type { DefaultTheme } from '../config'
import NavDropdownLinkItem from './NavDropdownLinkItem.vue'
defineProps<{
item: DefaultTheme.NavItemWithChildren
}>()
const open = ref(false)
const route = useRoute()
watch(
() => route.path,
() => {
open.value = false
}
)
ref: open = false
const setOpen = (value: boolean) => {
open.value = value
}
watch(() => route.path, () => { open = false })
const isLastItemOfArray = <T>(item: T, array: T[]) => {
return array.length && array.indexOf(item) === array.length - 1
function toggle() {
open = !open
}
</script>
<style>
.dropdown-wrapper {
<style scoped>
.nav-dropdown-link {
position: relative;
height: 36px;
overflow: hidden;
cursor: pointer;
display: block;
margin-left: 1.5rem;
}
.dropdown-wrapper .dropdown-title {
font: inherit;
color: var(--c-text);
font-size: 0.9rem;
font-weight: 500;
display: inline-block;
height: 1.75rem;
line-height: 1.75rem;
padding: inherit;
background: transparent;
border: none;
}
.dropdown-wrapper .dropdown-title:hover {
border-color: transparent;
}
.dropdown-wrapper .dropdown-title .arrow {
display: inline-block;
vertical-align: middle;
margin-top: -1px;
margin-left: 0.4rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item {
color: inherit;
line-height: 1.7rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item h4 {
margin: 0.45rem 0 0;
border-top: 1px solid #eee;
padding: 0.45rem 1.5rem 0 1.25rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .nav-item {
margin-left: 0.5rem;
}
@media (min-width: 720px) {
.nav-dropdown-link {
height: auto;
overflow: visible;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper {
padding: 0;
list-style: none;
.nav-dropdown-link:hover .dialog {
display: block;
}
}
.dropdown-wrapper
.nav-dropdown
.dropdown-item
.dropdown-subitem-wrapper
.dropdown-subitem {
font-size: 0.9em;
.nav-dropdown-link.open {
height: auto;
}
.dropdown-wrapper .nav-dropdown .dropdown-item a {
.button {
display: block;
line-height: 1.7rem;
position: relative;
border-bottom: none;
font-weight: 400;
margin-bottom: 0;
margin-left: 0;
padding: 0 1.5rem 0 1.25rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item a:hover {
color: var(--c-brand);
}
.dropdown-wrapper .nav-dropdown .dropdown-item a.active {
color: var(--c-brand);
}
.dropdown-wrapper .nav-dropdown .dropdown-item a.active::after {
content: '';
width: 0;
height: 0;
border-left: 5px solid var(--c-brand);
border-top: 3px solid transparent;
border-bottom: 3px solid transparent;
position: absolute;
top: calc(50% - 1.5px);
left: 8px;
}
.dropdown-wrapper .nav-dropdown .dropdown-item:first-child h4 {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dropdown-wrapper {
height: 1.8rem;
border: 0;
padding: 0 1.5rem;
width: 100%;
text-align: left;
line-height: 36px;
font-family: var(--font-family-base);
font-size: 1rem;
font-weight: 600;
color: var(--c-text);
white-space: nowrap;
background-color: transparent;
cursor: pointer;
}
.dropdown-wrapper:hover .nav-dropdown,
.dropdown-wrapper.open .nav-dropdown {
display: block;
.button:focus {
outline: 0;
}
.dropdown-wrapper.open:blur {
display: none;
@media (min-width: 720px) {
.button {
border-bottom: 2px solid transparent;
padding: 0;
line-height: 24px;
font-size: .9rem;
font-weight: 500;
}
}
.dropdown-wrapper .dropdown-title .arrow {
border-left: 4px solid transparent;
.button-arrow {
display: inline-block;
margin-top: -1px;
margin-left: 8px;
border-top: 6px solid #ccc;
border-right: 4px solid transparent;
border-top: 6px solid #aaa;
border-bottom: 0;
border-left: 4px solid transparent;
vertical-align: middle;
}
.dropdown-wrapper .nav-dropdown {
display: none;
height: auto !important;
box-sizing: border-box;
max-height: calc(100vh - 2.7rem);
overflow-y: auto;
position: absolute;
top: 100%;
right: 0;
background-color: #fff;
padding: 0.6rem 0;
border: 1px solid #ddd;
border-bottom-color: #ccc;
text-align: left;
border-radius: 0.25rem;
white-space: nowrap;
margin: 0;
.button-arrow.right {
transform: rotate(-90deg);
}
@media screen and (max-width: 719px) {
.dropdown-wrapper {
height: auto;
margin-left: 1.5rem;
}
.dropdown-wrapper .dropdown-title {
font-size: 1rem;
font-weight: 600;
@media (min-width: 720px) {
.button-arrow.right {
transform: rotate(0);
}
}
.dropdown-wrapper .nav-dropdown {
position: relative;
top: none;
right: none;
border: none;
padding: 4px 0;
background-color: transparent;
}
.dialog {
margin: 0;
padding: 0;
list-style: none;
}
.dropdown-wrapper:hover .nav-dropdown {
@media (min-width: 720px) {
.dialog {
display: none;
}
.dropdown-wrapper.open .nav-dropdown {
display: block;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .nav-item {
margin: 0;
padding: 0;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .nav-link {
font-size: 0.9rem;
position: absolute;
top: 26px;
right: -8px;
border-radius: 6px;
padding: 12px 0;
min-width: 128px;
background-color: var(--c-bg);
box-shadow: var(--shadow-3);
}
}
</style>

@ -0,0 +1,90 @@
<template>
<div class="nav-dropdown-link-item">
<a
class="item"
:class="classes"
:href="href"
:target="target"
:rel="rel"
:aria-label="ariaLabel"
>
<span class="arrow" />
<span class="text">{{ text }}</span>
<span class="icon"><OutboundLink v-if="isExternal" /></span>
</a>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
import type { DefaultTheme } from '../config'
import { useNavLink } from '../composables/navLink'
import OutboundLink from './icons/OutboundLink.vue'
const { item } = defineProps<{
item: DefaultTheme.NavItem
}>()
const {
classes,
isActive,
isExternal,
href,
target,
rel,
ariaLabel,
text
} = useNavLink(item)
</script>
<style scoped>
.item {
display: block;
padding: 0 1.5rem 0 2.5rem;
line-height: 32px;
font-size: .9rem;
font-weight: 500;
color: var(--c-text);
white-space: nowrap;
}
@media (min-width: 720px) {
.item {
padding: 0 24px 0 12px;
line-height: 32px;
font-size: .85rem;
font-weight: 500;
color: var(--c-text);
white-space: nowrap;
}
.item.active .arrow {
opacity: 1;
}
}
.item:hover,
.item.active {
text-decoration: none;
color: var(--c-brand);
}
.item.external:hover {
border-bottom-color: transparent;
color: var(--c-text);
}
@media (min-width: 720px) {
.arrow {
display: inline-block;
margin-right: 8px;
border-top: 6px solid #ccc;
border-right: 4px solid transparent;
border-bottom: 0;
border-left: 4px solid transparent;
vertical-align: middle;
opacity: 0;
transform: translateY(-2px) rotate(-90deg);
}
}
</style>

@ -0,0 +1,78 @@
<template>
<div class="nav-link">
<a
class="item"
:class="classes"
:href="href"
:target="target"
:rel="rel"
:aria-label="ariaLabel"
>
{{ text }} <OutboundLink v-if="isExternal" />
</a>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
import { useNavLink } from '../composables/navLink'
import OutboundLink from './icons/OutboundLink.vue'
const { item } = defineProps<{
item: DefaultTheme.NavItemWithLink
}>()
const {
classes,
isActive,
isExternal,
href,
target,
rel,
ariaLabel,
text
} = useNavLink(item)
</script>
<style scoped>
.nav-link {
}
.item {
display: block;
padding: 0 1.5rem;
line-height: 36px;
font-size: 1rem;
font-weight: 600;
color: var(--c-text);
white-space: nowrap;
}
.item:hover,
.item.active {
text-decoration: none;
color: var(--c-brand);
}
.item.external:hover {
border-bottom-color: transparent;
color: var(--c-text);
}
@media (min-width: 720px) {
.item {
border-bottom: 2px solid transparent;
padding: 0;
line-height: 24px;
font-size: .9rem;
font-weight: 500;
}
.item:hover,
.item.active {
border-bottom-color: var(--c-brand);
color: var(--c-text);
}
}
</style>

@ -0,0 +1,55 @@
<template>
<nav v-if="show" class="nav-links">
<template v-if="links">
<div v-for="item in links" :key="item.text" class="item">
<NavDropdownLink v-if="item.items" :item="item" />
<NavLink v-else :item="item" />
</div>
</template>
<div v-if="localeLinks" class="item">
<NavDropdownLink :item="localeLinks" />
</div>
<div v-if="repo" class="item">
<NavLink :item="repo" />
</div>
</nav>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useSiteDataByRoute } from 'vitepress'
import { useLocaleLinks } from '../composables/nav'
import { useRepo } from '../composables/repo'
import NavLink from './NavLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'
ref: siteByRoute = useSiteDataByRoute()
ref: localeLinks = useLocaleLinks()
ref: repo = useRepo()
ref: show = computed(() => links || repo)
ref: links = computed(() => siteByRoute.themeConfig.nav)
</script>
<style scoped>
.nav-links {
padding: .75rem 0;
border-bottom: 1px solid var(--c-divider);
}
@media (min-width: 720px) {
.nav-links {
display: flex;
padding: 6px 0 0;
align-items: center;
border-bottom: 0;
}
.item + .item {
padding-left: 24px;
}
}
</style>

@ -1,8 +1,6 @@
<template>
<aside class="sidebar" :class="{ open }">
<div class="nav">
<NavBarLinks class="show-mobile" />
</div>
<NavLinks class="nav" />
<slot name="sidebar-top" />
@ -14,7 +12,7 @@
<script setup lang="ts">
import { defineProps } from 'vue'
import NavBarLinks from './NavBarLinks.vue'
import NavLinks from './NavLinks.vue'
import SideBarLinks from './SideBarLinks.vue'
defineProps({

@ -0,0 +1,59 @@
import { computed } from 'vue'
import { useRoute, useSiteData } from 'vitepress'
import { inBrowser } from '/@app/utils'
import type { DefaultTheme } from '../config'
export function useLocaleLinks() {
const route = useRoute()
const site = useSiteData()
return computed(() => {
const theme = site.value.themeConfig as DefaultTheme.Config
const locales = theme.locales
if (!locales) {
return null
}
const localeKeys = Object.keys(locales)
if (localeKeys.length <= 1) {
return null
}
// handle site base
const siteBase = inBrowser ? site.value.base : '/'
const siteBaseWithoutSuffix = siteBase.endsWith('/')
? siteBase.slice(0, -1)
: siteBase
// remove site base in browser env
const routerPath = route.path.slice(siteBaseWithoutSuffix.length)
const currentLangBase = localeKeys.find((key) => {
return key === '/' ? false : routerPath.startsWith(key)
})
const currentContentPath = currentLangBase
? routerPath.substring(currentLangBase.length - 1)
: routerPath
const candidates = localeKeys.map((v) => {
const localePath = v.endsWith('/') ? v.slice(0, -1) : v
return {
text: locales[v].label,
link: `${localePath}${currentContentPath}`
}
})
const currentLangKey = currentLangBase ? currentLangBase : '/'
const selectText = locales[currentLangKey].selectText
? locales[currentLangKey].selectText
: 'Languages'
return { text: selectText, items: candidates }
})
}

@ -0,0 +1,71 @@
import { computed } from 'vue'
import { useRoute } from 'vitepress'
import type { DefaultTheme } from '../config'
import { isExternal as isExternalCheck } from '../utils'
import { useUrl } from '../composables/url'
export function useNavLink(item: DefaultTheme.NavItemWithLink) {
const route = useRoute()
const { withBase } = useUrl()
const classes = computed(() => ({
active: isActive.value,
external: isExternal.value
}))
const isActive = computed(() => {
return normalizePath(withBase(item.link)) === normalizePath(route.path)
})
const isExternal = computed(() => {
return isExternalCheck(item.link)
})
const href = computed(() => {
return isExternal.value ? item.link : withBase(item.link)
})
const target = computed(() => {
if (item.target) {
return item.target
}
return isExternal.value ? '_blank' : ''
})
const rel = computed(() => {
if (item.rel) {
return item.rel
}
return isExternal.value ? 'noopener noreferrer' : ''
})
const ariaLabel = computed(() => item.ariaLabel)
const text = computed(() => item.text)
return {
classes,
isActive,
isExternal,
href,
target,
rel,
ariaLabel,
text
}
}
function normalizePath(path: string): string {
path = path
.replace(/#.*$/, '')
.replace(/\?.*$/, '')
.replace(/\.html$/, '')
if (path.endsWith('/')) {
path += 'index'
}
return path
}

@ -0,0 +1,51 @@
import { computed } from 'vue'
import { useSiteDataByRoute } from 'vitepress'
import type { DefaultTheme } from '../config'
export const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map((platform) => {
return [platform, new RegExp(platform, 'i')] as const
})
export function useRepo() {
const site = useSiteDataByRoute()
return computed(() => {
const theme = site.value.themeConfig as DefaultTheme.Config
const name = theme.docsRepo || theme.repo
if (!name) {
return null
}
const link = getRepoUrl(name)
const text = getRepoText(link, theme.repoLabel)
return { text, link }
})
}
function getRepoUrl(repo: string): string {
// if the full url is not provided, default to GitHub repo
return /^https?:/.test(repo) ? repo : `https://github.com/${repo}`
}
function getRepoText(url: string, text?: string): string {
if (text) {
return text
}
// if no label is provided, deduce it from the repo url
const hosts = url.match(/^https?:\/\/[^/]+/)
if (!hosts) {
return 'Source'
}
const platform = platforms.find(([_p, re]) => re.test(hosts[0]))
if (platform && platform[0]) {
return platform[0]
}
return 'Source'
}

@ -60,12 +60,21 @@ export namespace DefaultTheme {
prevLink?: boolean
nextLink?: boolean
locales?: Record<string, LocaleConfig & Omit<Config, 'locales'>>
}
// navbar --------------------------------------------------------------------
export type NavItem = NavItemWithLink | NavItemWithChildren
export interface NavItemBase {
text: string
target?: string
rel?: string
ariaLabel?: string
}
export interface NavItemWithLink extends NavItemBase {
link: string
}
@ -74,13 +83,6 @@ export namespace DefaultTheme {
items: NavItem[]
}
export interface NavItemBase {
text: string
target?: string
rel?: string
ariaLabel?: string
}
// sidebar -------------------------------------------------------------------
export type SideBarConfig = SideBarItem[] | 'auto' | false
@ -126,4 +128,18 @@ export namespace DefaultTheme {
indexName: string
}
}
// locales --------------------------------------------------------------------
export interface LocaleConfig {
/**
* Text for the language dropdown.
*/
selectText?: string
/**
* Label for this locale in the language dropdown.
*/
label?: string
}
}

@ -21,11 +21,23 @@ a.sidebar-link-item.active {
}
.sidebar > .sidebar-links {
padding: 1.5rem 0;
padding: .75rem 0 5rem;
}
@media (min-width: 720px) {
.sidebar > .sidebar-links {
padding: 1.5rem 0;
}
}
.sidebar > .sidebar-links > .sidebar-link + .sidebar-link {
padding-top: 1rem;
padding-top: .5rem;
}
@media (min-width: 720px) {
.sidebar > .sidebar-links > .sidebar-link + .sidebar-link {
padding-top: 1.25rem;
}
}
.sidebar > .sidebar-links > .sidebar-link > .sidebar-link-item {

@ -33,6 +33,16 @@
--z-index-navbar: 10;
--z-index-sidebar: 6;
/**
* Shadows
* --------------------------------------------------------------------- */
--shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 2px rgba(0, 0, 0, .06);
--shadow-2: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);
--shadow-3: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);
--shadow-4: 0 14px 44px rgba(0, 0, 0, .12), 0 3px 9px rgba(0, 0, 0, .12);
--shadow-5: 0 18px 56px rgba(0, 0, 0, .16), 0 4px 12px rgba(0, 0, 0, .16);
/**
* Sizes
* --------------------------------------------------------------------- */

Loading…
Cancel
Save