From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- shared/components/src/stores/media-query.ts | 63 ++++++++++++++++++++++ .../src/stores/navigation-folders-open.ts | 21 ++++++++ .../src/stores/prefers-reduced-motion.ts | 27 ++++++++++ shared/components/src/stores/sidebar-hidden.ts | 12 +++++ 4 files changed, 123 insertions(+) create mode 100644 shared/components/src/stores/media-query.ts create mode 100644 shared/components/src/stores/navigation-folders-open.ts create mode 100644 shared/components/src/stores/prefers-reduced-motion.ts create mode 100644 shared/components/src/stores/sidebar-hidden.ts (limited to 'shared/components/src/stores') diff --git a/shared/components/src/stores/media-query.ts b/shared/components/src/stores/media-query.ts new file mode 100644 index 0000000..83cc055 --- /dev/null +++ b/shared/components/src/stores/media-query.ts @@ -0,0 +1,63 @@ +// Based on https://github.com/cibernox/svelte-media +import { readable } from 'svelte/store'; +import { ArtworkConfig } from '@amp/web-app-components/config/components/artwork'; +import { getMediaConditions } from '@amp/web-app-components/src/utils/getMediaConditions'; + +const { BREAKPOINTS } = ArtworkConfig.get(); +const mqConditions = getMediaConditions(BREAKPOINTS); + +const DEFAULT_SETTING = 'medium'; + +/** + * Filters media query results and outputs the breakpoint name with a matching media query. + * + * @param {Object} mqls media query configurations (pulled from getMediaConditions()) + * @returns {String|undefined} breakpoint string that matches current media query + */ +function calculateMediaQuery(mqls: Record): string { + return Object.entries(mqls) + .filter(([_, query]) => query.matches) + .map(([name, _]) => name)[0]; +} + +/** + * This function allows to build a store that tracks which of the given media query conditions matches. + * @param initialValue The inital value for the store. It only bears importance in server side rendering + * as it will update immediately in the browser + * @param mediaQueryConditions The dictionary with the media query names and the MQ condition to match against. + * @returns Svelte.Store The name of the matching media query + */ +export function buildMediaQueryStore( + initialValue: string, + mediaQueryConditions: Record = mqConditions, +) { + return readable(initialValue, (set) => { + if ( + typeof window === 'undefined' || + typeof matchMedia === 'undefined' + ) { + set(initialValue); + return; + } + + let mqls = {}; + let updateMediaQuery = () => set(calculateMediaQuery(mqls)); + + for (const key in mediaQueryConditions) { + mqls[key] = window.matchMedia(mediaQueryConditions[key]); + // `addListener` is deprecated but should still be used for compatibility with more browsers. + mqls[key].addListener(updateMediaQuery); + } + + updateMediaQuery(); + + return function (): void { + for (let key in mqls) { + // `removeListener` is deprecated but should still be used for compatibility with more browsers. + mqls[key].removeListener(updateMediaQuery); + } + }; + }); +} + +export const mediaQueries = buildMediaQueryStore(DEFAULT_SETTING, mqConditions); diff --git a/shared/components/src/stores/navigation-folders-open.ts b/shared/components/src/stores/navigation-folders-open.ts new file mode 100644 index 0000000..b761371 --- /dev/null +++ b/shared/components/src/stores/navigation-folders-open.ts @@ -0,0 +1,21 @@ +import { type Writable, writable } from 'svelte/store'; + +type FolderState = Writable; +const folderStates = new Map(); + +export function subscribeFolderOpenState( + id: string, + defaultState?: boolean, +): FolderState { + let stateById = folderStates.get(id); + if (!stateById) { + folderStates.set(id, writable(defaultState ?? false)); + stateById = folderStates.get(id); + } + + return stateById; +} + +export function resetFoldersOpenState() { + folderStates.clear(); +} diff --git a/shared/components/src/stores/prefers-reduced-motion.ts b/shared/components/src/stores/prefers-reduced-motion.ts new file mode 100644 index 0000000..03d9393 --- /dev/null +++ b/shared/components/src/stores/prefers-reduced-motion.ts @@ -0,0 +1,27 @@ +import { readable } from 'svelte/store'; + +const DEFAULT_SETTING = false; + +export const prefersReducedMotion = readable(DEFAULT_SETTING, (set) => { + if (typeof window === 'undefined' || typeof matchMedia === 'undefined') { + set(DEFAULT_SETTING); + return; + } + + const motionQuery = matchMedia('(prefers-reduced-motion)'); + + /* istanbul ignore next */ + const motionQueryListener = (): void => { + set(motionQuery.matches); + }; + + // `addListener` is deprecated but should still be used for compatibility with more browsers. + motionQuery.addListener(motionQueryListener); + + set(motionQuery.matches); + + return function (): void { + // `removeListener` is deprecated but should still be used for compatibility with more browsers. + motionQuery.removeListener(motionQueryListener); + }; +}); diff --git a/shared/components/src/stores/sidebar-hidden.ts b/shared/components/src/stores/sidebar-hidden.ts new file mode 100644 index 0000000..2de14d1 --- /dev/null +++ b/shared/components/src/stores/sidebar-hidden.ts @@ -0,0 +1,12 @@ +import { derived } from 'svelte/store'; +import { buildMediaQueryStore } from '@amp/web-app-components/src/stores/media-query'; + +export const sidebarHiddenQuery = buildMediaQueryStore('visible', { + hidden: '(max-width: 483px)', + visible: '(min-width: 484px)', +}); + +export const sidebarIsHidden = derived( + sidebarHiddenQuery, + ($sidebarHiddenQuery) => $sidebarHiddenQuery === 'hidden', +); -- cgit v1.2.3