diff --git a/src/components/SongMessageContainer.vue b/src/components/SongMessageContainer.vue new file mode 100644 index 0000000..ff3d7ab --- /dev/null +++ b/src/components/SongMessageContainer.vue @@ -0,0 +1,84 @@ + + + + + + + + ✓ + ✕ + ! + 印 + + {{ t.text }} + + + + + + \ No newline at end of file diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..8d59b1f --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,55 @@ +import SongButton from './SongButton.vue' +import SongCard from './SongCard.vue' +import SongTag from './SongTag.vue' +import SongInput from './SongInput.vue' +import SongNav from './SongNav.vue' +import SongTable from './SongTable.vue' +import SongBadge from './SongBadge.vue' +import SongProgress from './SongProgress.vue' +import SongModal from './SongModal.vue' +import SongScrim from './SongScrim.vue' +import SongLogo from './SongLogo.vue' +import SongDisclaimer from './SongDisclaimer.vue' +import SongLoading from './SongLoading.vue' +import SongSeal from './SongSeal.vue' + +export { + SongButton, + SongCard, + SongTag, + SongInput, + SongNav, + SongTable, + SongBadge, + SongProgress, + SongModal, + SongScrim, + SongLogo, + SongDisclaimer, + SongLoading, + SongSeal +} + +export const SongComponents = { + install(app) { + const map = { + SongButton, + SongCard, + SongTag, + SongInput, + SongNav, + SongTable, + SongBadge, + SongProgress, + SongModal, + SongScrim, + SongLogo, + SongDisclaimer, + SongLoading, + SongSeal + } + for (const [name, c] of Object.entries(map)) { + app.component(name, c) + } + } +} \ No newline at end of file diff --git a/src/composables/useMessage.js b/src/composables/useMessage.js new file mode 100644 index 0000000..2ecc3d9 --- /dev/null +++ b/src/composables/useMessage.js @@ -0,0 +1,38 @@ +import { reactive, createVNode, render } from 'vue' +import SongMessageContainer from '../components/SongMessageContainer.vue' + +const list = reactive([]) +let seed = 0 +let hostEl = null + +function push(type, text, duration = 3400) { + const id = ++seed + list.push({ id, type, text }) + setTimeout(() => remove(id), duration) +} + +function remove(id) { + const i = list.findIndex(t => t.id === id) + if (i > -1) list.splice(i, 1) +} + +function ensureHost() { + if (!hostEl) { + hostEl = document.createElement('div') + hostEl.className = 'song-message-host' + document.body.appendChild(hostEl) + const vnode = createVNode(SongMessageContainer, { list }) + render(vnode, hostEl) + } +} + +export function useMessage() { + ensureHost() + return { + list, + success: text => push('success', text), + danger: text => push('danger', text), + warning: text => push('warning', text), + info: text => push('info', text) + } +} \ No newline at end of file diff --git a/src/composables/useSongTheme.js b/src/composables/useSongTheme.js new file mode 100644 index 0000000..216ae9f --- /dev/null +++ b/src/composables/useSongTheme.js @@ -0,0 +1,34 @@ +import { ref, reactive } from 'vue' + +const STORAGE_KEY = 'song-theme' +const isDark = ref(false) +let initialized = false + +function apply(v) { + isDark.value = v + document.documentElement.setAttribute('data-theme', v ? 'dark' : 'light') + try { + localStorage.setItem(STORAGE_KEY, v ? 'dark' : 'light') + } catch { /* ignore */ } +} + +const theme = reactive({ + isDark, + toggle() { + apply(!isDark.value) + } +}) + +export function useSongTheme() { + if (!initialized) { + initialized = true + let stored = null + try { stored = localStorage.getItem(STORAGE_KEY) } catch { /* ignore */ } + const prefersDark = typeof window !== 'undefined' && + window.matchMedia('(prefers-color-scheme: dark)').matches + apply(stored ? stored === 'dark' : prefersDark) + } + return theme +} + +export const initSongTheme = useSongTheme \ No newline at end of file