diff --git a/components/Base/Button.vue b/components/Base/Button.vue index 1f8b52fc..a8b973fd 100644 --- a/components/Base/Button.vue +++ b/components/Base/Button.vue @@ -10,6 +10,7 @@ export interface BaseButtonProps { target?: '_blank' | '_self' | '_parent' | '_top'; outline?: boolean; block?: boolean; + disabled?: boolean; } const props = withDefaults(defineProps(), { @@ -60,6 +61,9 @@ const { theme } = useTheme(); `color-${color}`, `theme-${theme}`, { 'icon-only': isIconOnly, outline, 'size-block': block }, + { + disabled, + }, ]" v-bind="buttonProps" > @@ -242,10 +246,15 @@ const { theme } = useTheme(); } .size-x-large.icon-only { - padding: var(--space-2); + padding: var(--space-3); } .size-block { width: 100%; } + +.disabled { + pointer-events: none; + opacity: 0.5; +} diff --git a/components/Base/Icon.vue b/components/Base/Icon.vue index a3baebd9..50342fc6 100644 --- a/components/Base/Icon.vue +++ b/components/Base/Icon.vue @@ -65,6 +65,7 @@ const filledIcons = [ 'monitoring', 'online_prediction', 'password', + 'partner_exchange', 'post_add', 'public', 'published_with_changes', diff --git a/components/Block/Directory.vue b/components/Block/Directory.vue index b41dc49f..a49d2279 100644 --- a/components/Block/Directory.vue +++ b/components/Block/Directory.vue @@ -10,7 +10,7 @@ const props = defineProps(); const { data: block } = await useAsyncData(props.uuid, () => $directus.request( $readItem('block_directory', props.uuid, { - fields: ['style', 'grid', 'collection', 'filter', 'title_size'], + fields: ['style', 'grid', 'collection', 'filter', 'title_size', 'group_by'], }), ), ); @@ -73,6 +73,19 @@ const dirConfig = computed(() => { href: (item: Project) => `/built-with-directus/${item.slug}`, }, }; + } else if (context.collection === 'features') { + return { + searchFields: ['title', 'description'], + facetFields: ['module'], + fieldMapping: { + title: 'title', + image: 'thumbnail', + description: 'description', + href: (item: any) => `/features/${item.slug}`, + module: 'module', + }, + groupBy: 'module', + }; } }); @@ -82,6 +95,7 @@ const { searchQuery, selectedFacets, facets, filteredItems, isFilterActive, clea searchFields: unref(dirConfig)?.searchFields ?? [], facetFields: unref(dirConfig)?.facetFields ?? [], fieldMapping: unref(dirConfig)?.fieldMapping ?? undefined, + groupBy: unref(dirConfig)?.groupBy ?? undefined, }); // Mobile filter state @@ -130,23 +144,52 @@ const toggleFilter = () => {
- - - - -

No items were found. Try changing the search criteria.

+ + + +

+ No items were found. Try changing the search criteria. +

@@ -231,4 +274,15 @@ const toggleFilter = () => { justify-self: end; } } + +.group-container { + margin-bottom: var(--space-8); +} + +.group-title { + font-size: var(--text-lg); + font-weight: var(--weight-bold); + margin-bottom: var(--space-4); + border-bottom: 1px solid var(--gray-200); +} diff --git a/components/PageSection.vue b/components/PageSection.vue index 4dccfde8..2f54e323 100644 --- a/components/PageSection.vue +++ b/components/PageSection.vue @@ -209,6 +209,10 @@ const { height: headerHeight } = useHeaderHeight(); background-size: cover; } +.bg-colorful-muted { + background: linear-gradient(to bottom, #fe97dc15, #745eff15); +} + .bg-pristine-white + .bg-pristine-white-lines { border-block-start: 1px solid var(--gray-200); } diff --git a/composables/useDirectory.ts b/composables/useDirectory.ts index 6839b6ac..f883c844 100644 --- a/composables/useDirectory.ts +++ b/composables/useDirectory.ts @@ -19,12 +19,12 @@ interface UseDirectoryOptions { searchFields: string[]; facetFields: string[]; fieldMapping?: FieldMapping | undefined; + groupBy?: string; } -export function useDirectory({ items, searchFields, facetFields, fieldMapping = {} }: UseDirectoryOptions) { +export function useDirectory({ items, searchFields, facetFields, fieldMapping = {}, groupBy }: UseDirectoryOptions) { const route = useRoute(); const router = useRouter(); - const searchQuery = ref(''); const selectedFacets = ref>(Object.fromEntries(facetFields.map((field) => [field, []]))); @@ -52,7 +52,6 @@ export function useDirectory({ items, searchFields, facetFields, fieldMapping = const readFromURL = () => { const { q, ...facetParams } = route.query; - searchQuery.value = (q as string) || ''; Object.keys(selectedFacets.value).forEach((field) => { @@ -62,7 +61,6 @@ export function useDirectory({ items, searchFields, facetFields, fieldMapping = }; readFromURL(); - watch([searchQuery, selectedFacets], updateURL, { deep: true }); const facets = computed(() => { @@ -115,16 +113,34 @@ export function useDirectory({ items, searchFields, facetFields, fieldMapping = }, {} as DirectoryItem); }; + const groupItems = (items: DirectoryItem[]) => { + if (!groupBy) return items; + + return items.reduce( + (acc, item) => { + const groupValue = item[groupBy]; + + if (!acc[groupValue]) { + acc[groupValue] = []; + } + + acc[groupValue].push(item); + return acc; + }, + {} as Record, + ); + }; + const filteredItems = computed(() => { let result = items; - result = filterItemsByFacets(result); if (searchQuery.value) { result = fuse.search(searchQuery.value).map((res) => res.item); } - return result.map(applyFieldMapping); + const mappedResult = result.map(applyFieldMapping); + return groupBy ? groupItems(mappedResult) : mappedResult; }); const isFilterActive = computed(() => { diff --git a/modules/prerender.ts b/modules/prerender.ts index 30fc2e6f..d03f1087 100644 --- a/modules/prerender.ts +++ b/modules/prerender.ts @@ -57,6 +57,8 @@ export default defineNuxtModule({ const agencyPartners = await directus.request(readItems('agency_partners', { fields: ['slug'], limit: -1 })); + const features = await directus.request(readItems('features', { fields: ['slug'], limit: -1 })); + const shows = await directusTv.request(readItems('shows', { fields: ['slug'], limit: -1 })); const episodes = await directusTv.request( @@ -68,6 +70,7 @@ export default defineNuxtModule({ permalinks.push(...team.map((member) => `/team/${member.slug}`)); permalinks.push(...projects.map((project) => `/built-with-directus/${project.slug}`)); permalinks.push(...agencyPartners.map((partner) => `/agency-directory/${partner.slug}`)); + permalinks.push(...features.map((feature) => `/features/${feature.slug}`)); permalinks.push(...shows.map((show) => `/tv/${show.slug}`)); permalinks.push(...episodes.map((ep) => `/tv/${ep.season.show.slug}/${ep.slug}`)); diff --git a/pages/features/[slug].vue b/pages/features/[slug].vue new file mode 100644 index 00000000..c42ca3dd --- /dev/null +++ b/pages/features/[slug].vue @@ -0,0 +1,198 @@ + + + + + diff --git a/types/schema/blocks/block-directory.ts b/types/schema/blocks/block-directory.ts index 75243777..635ecc1a 100644 --- a/types/schema/blocks/block-directory.ts +++ b/types/schema/blocks/block-directory.ts @@ -1,6 +1,6 @@ export interface BlockDirectory { id: string; - collection: 'agency_partners'; + collection: 'agency_partners' | 'projects' | 'features'; filter: Record | null; style: | 'none' @@ -15,4 +15,5 @@ export interface BlockDirectory { | 'icon-above-title'; grid: '3' | '4' | '6'; title_size: 'small' | 'medium' | 'large'; + group_by?: string; } diff --git a/types/schema/content/feature.ts b/types/schema/content/feature.ts new file mode 100644 index 00000000..ec9b3847 --- /dev/null +++ b/types/schema/content/feature.ts @@ -0,0 +1,21 @@ +import type { Seo } from '../meta/index.js'; +import type { BlockMedia } from '../blocks/block-media.js'; +import type { User } from '../system/index.js'; + +export interface Feature { + id: string; + sort: number | null; + user_created: string | User | null; + date_created: string | null; + user_updated: string | User | null; + date_updated: string | null; + title: string | null; + slug: string | null; + description: string | null; + status: string | null; + content: string | null; + module?: string | null; + seo: Seo | null; + media: BlockMedia | string | null; + thumbnail: string | File | null; +} diff --git a/types/schema/content/index.ts b/types/schema/content/index.ts index 06ba66c8..00a2c11e 100644 --- a/types/schema/content/index.ts +++ b/types/schema/content/index.ts @@ -6,3 +6,4 @@ export type * from './video.js'; export type * from './event.js'; export type * from './developer-article.js'; export type * from './agency-partner.js'; +export type * from './feature.js'; diff --git a/types/schema/routes/page.ts b/types/schema/routes/page.ts index aa055493..876c6869 100644 --- a/types/schema/routes/page.ts +++ b/types/schema/routes/page.ts @@ -24,7 +24,7 @@ export interface PageBlock { item: Block; collection: BlockType; sort: number | null; - background: 'pristine-white' | 'pristine-white-lines' | 'simple-gray' | 'dark-night' | 'colorful'; + background: 'pristine-white' | 'pristine-white-lines' | 'simple-gray' | 'dark-night' | 'colorful' | 'colorful-muted'; spacing: 'none' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; negative_offset: boolean; width: 'full' | 'standard' | 'narrow'; diff --git a/types/schema/schema.ts b/types/schema/schema.ts index d2941158..7db85495 100644 --- a/types/schema/schema.ts +++ b/types/schema/schema.ts @@ -49,6 +49,7 @@ import type { SiteBanner, Team, Event, + Feature, DeveloperArticle, DeveloperArticleDocTag, DocTag, @@ -74,6 +75,7 @@ export interface Schema { site_banners: SiteBanner[]; team: Team[]; events: Event[]; + features: Feature[]; // Partner Program agency_partners: AgencyPartner[];