Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: PCascadePanels #1175

Merged
merged 36 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4908916
Scaffold cascade menu
znicholasbrown Mar 27, 2024
60a8615
Panels are working
znicholasbrown Mar 27, 2024
dcba9bf
Fix v-model
znicholasbrown Mar 27, 2024
b0c36cd
Allow selecting multiple at any level
znicholasbrown Mar 27, 2024
cd85eb2
Remove errant console statement
znicholasbrown Mar 27, 2024
9288757
Throw out old version, create new
znicholasbrown Mar 28, 2024
239abc2
Fix chevron margin
znicholasbrown Mar 28, 2024
b08be68
Add new compositions
znicholasbrown Mar 28, 2024
a169a60
Back to basics
znicholasbrown Mar 28, 2024
2a5416a
Rework complete?
znicholasbrown Mar 29, 2024
f562505
Nice, it works
znicholasbrown Mar 29, 2024
ba3083d
Remove values
znicholasbrown Mar 29, 2024
356b2f3
Remove more values references
znicholasbrown Mar 29, 2024
0431066
Clean up unused components
znicholasbrown Mar 29, 2024
729af7f
Add some demos
znicholasbrown Mar 29, 2024
d2eb181
Fix some loading
znicholasbrown Mar 29, 2024
575e1d7
Remove errant console statement
znicholasbrown Mar 29, 2024
d57accb
Update description
znicholasbrown Mar 29, 2024
05be599
Small tweaks to pointer events
znicholasbrown Mar 29, 2024
d3bda54
Fix filtering in demo
znicholasbrown Mar 29, 2024
394ae49
Update CSP
znicholasbrown Mar 29, 2024
ed40048
Too specific
znicholasbrown Mar 29, 2024
3621689
Add some transition classes
znicholasbrown Mar 29, 2024
8a4c703
Remove unused transition classes
znicholasbrown Mar 29, 2024
3b3657b
Remove errant console statement
znicholasbrown Apr 1, 2024
bf5eed6
Fix useCascadePanel reactivity
znicholasbrown Apr 1, 2024
bddf856
Fix injection issue, allow passing reactive variables but unwrap them
znicholasbrown Apr 1, 2024
4c349f5
Don't double provide panel
znicholasbrown Apr 1, 2024
eb9d750
Don't initialize state based on panels
znicholasbrown Apr 1, 2024
f828af4
Add some code wrappers to demo message
znicholasbrown Apr 1, 2024
aaa45d3
Don't absolutely position panels when leaving
znicholasbrown Apr 1, 2024
ab95357
Don't unmount panels
znicholasbrown Apr 2, 2024
b55a011
Rename getInjectedCascadePanels => useInjectedCascadePanels
znicholasbrown Apr 2, 2024
309e427
Dupe logic, add error classes
znicholasbrown Apr 2, 2024
5dd7524
Remove computed panel is open method - replace with bespoke method
znicholasbrown Apr 2, 2024
c58706c
Expose panels - only use injected panels if they match
znicholasbrown Apr 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions demo/components/cascade/AdvancedDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<template>
<div class="advanced-demo">
<div ref="target">
<p-label label="Region">
<p-button class="advanced-demo__target" small icon-append="ChevronDownIcon" @click.stop="toggle">
<span v-if="emptyContinent" class="advanced-demo__placeholder">Select a continent to begin</span>
<span v-else>{{ continent }}</span>

<template v-if="!emptyContinent">
<span class="advanced-demo__separator"> / </span>
<span v-if="emptyCountry" class="advanced-demo__placeholder">Select a country</span>
<span v-else-if="continent">{{ country }}</span>
</template>

<template v-if="!emptyCountry">
<span class="advanced-demo__separator"> / </span>
<span v-if="emptyBorders" class="advanced-demo__placeholder">Select borders</span>
<span v-else>{{ borders.join(', ') }}</span>
</template>
</p-button>
</p-label>

<teleport to="body">
<div ref="content" :style="styles">
<PCascadePanels :panels="panels" class="advanced-demo__panels">
<template #continents>
<Continents v-model:value="continent" class="advanced-demo__panel" />
</template>

<template #countries>
<Countries v-model:value="country" v-model:continent="continent" class="advanced-demo__panel" />
</template>

<template #borders>
<keep-alive>
<Borders v-model:value="borders" v-model:country="country" class="advanced-demo__panel" />
</keep-alive>
</template>
</PCascadePanels>
</div>
</teleport>
</div>
</div>
</template>

<script lang="ts" setup>
import { PCascadePanels } from '@/components'
import { CascadePanel, useCascadePanels, useMostVisiblePositionStyles } from '@/compositions'
import { keys } from '@/types'
import { positions } from '@/utilities'
import { useKeyDown } from '@prefecthq/vue-compositions'
import { computed, ref, watch } from 'vue'
import Borders from '@/demo/components/cascade/Borders.vue'
import Continents from '@/demo/components/cascade/Continents.vue'
import Countries from '@/demo/components/cascade/Countries.vue'

const continent = ref<string>()
const country = ref<string>()
const borders = ref<string[]>([])

const panels: CascadePanel[] = [
{
id: 'continents',
level: 0,
},
{
id: 'countries',
level: 1,
},
{
id: 'borders',
level: 2,
},
]

const { openPanelById, closePanelById, toggle, close } = useCascadePanels(panels)

const placement = ref([positions.bottomLeft])
const { styles, target, container, content } = useMostVisiblePositionStyles(placement)
container.value = document.body

const emptyContinent = computed(() => !continent.value)
const emptyCountry = computed(() => !country.value)
const emptyBorders = computed(() => !borders.value.length)

watch(() => continent.value, (value) => {
if (value) {
openPanelById('countries')
} else {
country.value = undefined
closePanelById('countries')
}
})

watch(() => country.value, (value) => {
if (value) {
openPanelById('borders')
} else {
borders.value = []
closePanelById('borders')
}
})

useKeyDown(keys.escape, close)
</script>

<style>
.advanced-demo__target { @apply
min-w-48
}

.advanced-demo__target .p-button__content {@apply
justify-between
px-1
}

.p-overflow-menu { @apply
bg-transparent
shadow-none
}

.advanced-demo__panels { @apply
flex
gap-1
}

.advanced-demo__placeholder,
.advanced-demo__separator { @apply
text-subdued
}
</style>
47 changes: 47 additions & 0 deletions demo/components/cascade/BasicDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<p-button small @click="toggle">
Toggle Cascade Panels
</p-button>

<PCascadePanels :panels="panels">
<template #level-0>
<Level0 />
</template>

<template #level-1>
<Level1 />
</template>

<template #level-2="{ close }">
No component for level-2 but we can still access the panel state

<p-button small @click="close">
Close
</p-button>
</template>
</PCascadePanels>
</template>

<script lang="ts" setup>
import { PCascadePanels } from '@/components'
import { CascadePanel, useCascadePanels } from '@/compositions'
import Level0 from '@/demo/components/cascade/Level0.vue'
import Level1 from '@/demo/components/cascade/Level1.vue'

const panels: CascadePanel[] = [
{
id: 'level-0',
level: 0,
},
{
id: 'level-1',
level: 1,
},
{
id: 'level-2',
level: 2,
},
]

const { toggle } = useCascadePanels(panels)
</script>
148 changes: 148 additions & 0 deletions demo/components/cascade/Borders.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<template>
<p-card class="borders">
<p-text-input v-model="search" class="borders__search" placeholder="Search borders" />

<transition name="fade" mode="out-in">
<p-loading-icon v-if="loading" class="borders__loading-icon" />

<div v-else-if="empty" class="borders__empty">
No borders found
</div>

<div v-else class="borders__content">
<template v-for="border in filteredborders" :key="border">
<p-overflow-menu-item class="borders__item" :class="classes.border(border)" @click="toggle(border)">
{{ border }}
<p-icon v-if="value.includes(border)" size="small" icon="CheckIcon" class="ml-auto" />
</p-overflow-menu-item>
</template>
</div>
</transition>
</p-card>
</template>

<script setup lang="ts">
import { useSubscriptionWithDependencies } from '@prefecthq/vue-compositions'
import { computed, ref } from 'vue'

const search = ref()
const loading = ref(false)
const country = defineModel<string | undefined>('country', { required: true })
const value = defineModel<string[]>('value', { default: [] })

type CountryResponse = {
borders: string[],
}


const fetchBorders = async (country: string): Promise<string[]> => {
if (!country) {
return []
}

loading.value = true

try {
const response = await fetch(`https://restcountries.com/v3.1/name/${country.toLowerCase()}?fullText=true`)
const data = await response.json()
return data.map((country: CountryResponse) => country.borders).flat().filter((border: string) => border)
} catch {
return []
} finally {
setTimeout(() => loading.value = false, 1000)
}
}

const subscriptionArgs = computed <[string] | null>(() => {
if (!country.value) {
return null
}

return [country.value]
})

const subscription = useSubscriptionWithDependencies(fetchBorders, subscriptionArgs)
const borders = computed<string[]>(() => subscription.response ?? [])
const empty = computed(() => filteredborders.value.length === 0 && !loading.value)

const filteredborders = computed(() => {
if (!search.value) {
return borders.value
}

return borders.value.filter(border => border.toLowerCase().includes(search.value.toLowerCase()))
})

const classes = computed(() => ({
border: (border: string) => ({
'p-overflow-menu-item--active': value.value.includes(border),
}),
}))

function toggle(country: string): void {
if (value.value.includes(country)) {
value.value = value.value.filter(border => border !== country)
} else {
value.value = [...value.value, country]
}
}
</script>


<style>
.borders { @apply
px-0
flex
flex-col
min-w-64
}

.borders__item { @apply
text-xs
rounded
}

.borders__search { @apply
mx-4
shrink
w-auto
mb-4
}

.borders__search .p-text-input__control { @apply
text-xs
}

.borders__content { @apply
overflow-y-auto
max-h-64
px-4
grow
relative
}

.borders__close { @apply
ml-auto
text-subdued
}

.borders__loading-icon { @apply
mx-auto
}

.borders__empty { @apply
flex
justify-center
text-xs
items-center
text-subdued
}

.fade-enter-active, .fade-leave-active { @apply
transition-opacity
}

.fade-enter, .fade-leave-to { @apply
opacity-0
}
</style>
Loading
Loading