UNPKG

unified-query

Version:

Composable search input with autocompletion and a rich query-language parser for the Unified Data System

139 lines (138 loc) 5.22 kB
/* -------------------------------------------------------------------------- * Unified‑Search ‑ theme API * -------------------------------------------------------------------------- * Exports a tiny `createSearchTheme` factory that returns a CodeMirror * extension. It supports: * • Basic palette, font & sizing options * • Autocomplete tooltip styling (inc. per‑option icons) * • Decoration classes `.cm-entity-name` and `.cm-qs-keyword` * * Compared with unified‑text this is intentionally minimal – we only expose * the levers that matter for the search box. * -------------------------------------------------------------------------- */ import { EditorView } from '@codemirror/view'; /* -------------------------------------------------------------------------- */ /* Default values */ /* -------------------------------------------------------------------------- */ const defaults = { dark: false, fontFamily: 'Inter, sans-serif', fontSize: '15px', background: '#ffffff', foreground: '#1f1f1f', selection: '#cce0ff', caret: '#000000', autocomplete: { background: '#ffffff', border: '#d4d4d8', selectionBackground: '#e4e4e7', foreground: '#1f1f1f' }, icons: {}, entityName: { color: '#0d9488', fontWeight: '500' }, keyword: { color: '#7c3aed', fontWeight: '500' } }; /* -------------------------------------------------------------------------- */ /* Factory */ /* -------------------------------------------------------------------------- */ export function createTheme(opts = {}) { const o = { ...defaults, ...opts, autocomplete: { ...defaults.autocomplete, ...(opts.autocomplete ?? {}) } }; /* Build dynamic icon selectors */ const iconStyles = []; if (o.icons) { for (const [iconType, base64String] of Object.entries(o.icons)) { iconStyles.push([ `.cm-completionIcon-${iconType}`, { "&:after": { content: `url('data:image/svg+xml;base64,${base64String}')`, verticalAlign: "middle" } } ]); } } const addStyles = Object.fromEntries(iconStyles); const css = { // Root panel styled like an input '&': { backgroundColor: o.background, border: '1px solid #ccc', borderRadius: '4px', padding: '2px 6px', whiteSpace: 'nowrap', overflow: 'hidden', color: o.foreground, fontFamily: o.fontFamily, fontSize: o.fontSize }, '&.cm-focused': { outline: 'none' }, '.cm-content': { padding: '4px 0', fontFamily: opts.fontFamily, }, '.cm-activeLine': { backgroundColor: 'transparent' }, '.cm-gutters': { display: 'none' }, // Decorations '.cm-entity-name': o.entityName, '.cm-qs-keyword': o.keyword, // Matched selection '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { backgroundColor: 'transparent' // Override highlighting for matching bracket }, '.cm-selectionMatch': { backgroundColor: 'transparent' // Override highlighting text that matches selection }, /* ----------------------- Autocomplete tooltip -------------------- */ '.cm-tooltip': { border: `1px solid ${o.autocomplete.border}`, backgroundColor: o.autocomplete.background, boxShadow: '0 4px 8px rgba(0,0,0,0.15)', borderRadius: '8px', padding: '6px' }, ...addStyles, ".cm-completionIcon": { fontSize: "90%", width: "1em", display: "inline-block", textAlign: "center", paddingRight: ".6em", opacity: "1.0", boxSizing: "content-box", transform: "scale(0.8)" }, '.cm-tooltip-autocomplete': { '& > ul > li > .cm-completionLabel': { fontFamily: o.fontFamily, color: o.autocomplete.foreground, fontSize: '14px' }, '& > ul > li > .cm-completionDetail': { fontStyle: 'normal', fontFamily: o.fontFamily, color: o.autocomplete.foreground, opacity: 0.5, fontSize: '12px', fontWeight: 'light' }, '& > ul > li > .cm-completionLabel > .cm-completionMatchedText': { fontWeight: 'bold', textDecoration: 'none' }, '& > ul > li[aria-selected]': { backgroundColor: o.autocomplete?.selectionBackground, color: o.autocomplete.foreground, borderRadius: '4px' } }, }; return EditorView.theme(css, { dark: o.dark }); } export default createTheme;