diff --git a/apps/storybook/.storybook/preview.ts b/apps/storybook/.storybook/preview.ts index 686a965d..60e9b396 100644 --- a/apps/storybook/.storybook/preview.ts +++ b/apps/storybook/.storybook/preview.ts @@ -1,12 +1,10 @@ import { defineCustomElements } from 'ui-stencil/loader' import 'ui-stencil/dist/orama-ui/orama-ui.css' import { html } from 'lit-html' +import { DARK_THEME_BG, LIGTH_THEME_BG } from '../constants' defineCustomElements() -const LIGTH_THEME_BG = '#fbfbfb' -const DARK_THEME_BG = '#050505' - const preview = { decorators: [ (story, context) => { diff --git a/apps/storybook/constants.ts b/apps/storybook/constants.ts new file mode 100644 index 00000000..88f50c85 --- /dev/null +++ b/apps/storybook/constants.ts @@ -0,0 +1,2 @@ +export const LIGTH_THEME_BG = '#fbfbfb' +export const DARK_THEME_BG = '#050505' diff --git a/apps/storybook/stories/search-box.stories.tsx b/apps/storybook/stories/search-box.stories.tsx index 28a23277..fa18f0f2 100644 --- a/apps/storybook/stories/search-box.stories.tsx +++ b/apps/storybook/stories/search-box.stories.tsx @@ -1,19 +1,51 @@ +import { spread } from '@open-wc/lit-helpers' import type { StoryObj, Meta } from '@storybook/web-components' import type { Components } from 'ui-stencil' +import { html } from 'lit-html' +import { DARK_THEME_BG, LIGTH_THEME_BG } from '../constants' type Story = StoryObj const meta: Meta = { title: 'Public/SearchBox', component: 'search-box', + argTypes: { + colorScheme: { + options: ['light', 'dark', 'system'], + defaultValue: 'light', + control: { type: 'radio' }, + }, + }, } export default meta +// const Template = ({ colorScheme, ...args }, context) => { +// // todo: I'd like to programatically update parameters background value of colorScheme changes and vice versa +// console.log('colorScheme', colorScheme) +// console.log('args', args.themeConfig) + +// return html` +// +// ` +// } + export const SearchBox: Story = { + // render: Template, args: { open: true, facetProperty: 'category', resultMap: { description: 'title', }, + colorScheme: 'light', + themeConfig: { + colors: { + light: { + '--text-color-primary': 'purple', + }, + dark: { + '--text-color-primary': 'pink', + }, + }, + }, }, } diff --git a/packages/ui-stencil-vue/lib/components.ts b/packages/ui-stencil-vue/lib/components.ts index a5ebc042..5efcfe49 100644 --- a/packages/ui-stencil-vue/lib/components.ts +++ b/packages/ui-stencil-vue/lib/components.ts @@ -34,8 +34,8 @@ export const OramaChatUserMessage = /*@__PURE__*/ defineContainer('orama-facets', undefined, [ 'facets', - 'onFacetClick', - 'selectedFacet' + 'selectedFacet', + 'onFacetClick' ]); @@ -88,7 +88,7 @@ export const OramaToggler = /*@__PURE__*/ defineContainer('ora export const SearchBox = /*@__PURE__*/ defineContainer('search-box', undefined, [ 'themeConfig', - 'color', + 'colorScheme', 'facetProperty', 'open', 'resultMap' diff --git a/packages/ui-stencil/src/components.d.ts b/packages/ui-stencil/src/components.d.ts index 171d8ca2..6f9c9788 100644 --- a/packages/ui-stencil/src/components.d.ts +++ b/packages/ui-stencil/src/components.d.ts @@ -12,6 +12,7 @@ import { InputProps } from "./components/internal/orama-input/orama-input"; import { ResultMap, SearchResult, SearchResultBySection } from "./types/index"; import { SearchResultsProps } from "./components/internal/orama-search-results/orama-search-results"; import { TextProps } from "./components/internal/orama-text/orama-text"; +import { TThemeOverrides } from "./config/theme"; export { ButtonProps } from "./components/internal/orama-button/orama-button"; export { TChatMessage } from "./context/chatContext"; export { Facet } from "./components/internal/orama-facets/orama-facets"; @@ -19,6 +20,7 @@ export { InputProps } from "./components/internal/orama-input/orama-input"; export { ResultMap, SearchResult, SearchResultBySection } from "./types/index"; export { SearchResultsProps } from "./components/internal/orama-search-results/orama-search-results"; export { TextProps } from "./components/internal/orama-text/orama-text"; +export { TThemeOverrides } from "./config/theme"; export namespace Components { interface OramaButton { "as"?: ButtonProps['as']; @@ -87,11 +89,11 @@ export namespace Components { "performInitialAnimation": boolean; } interface SearchBox { - "color": 'dark' | 'light' | 'system'; + "colorScheme": 'dark' | 'light' | 'system'; "facetProperty"?: string; "open"?: boolean; "resultMap"?: Partial; - "themeConfig": { colors: { light: { primaryColor: string }; dark: { primaryColor: string } } }; + "themeConfig": Partial; } interface SearchBoxToggler { } @@ -305,11 +307,11 @@ declare namespace LocalJSX { "performInitialAnimation"?: boolean; } interface SearchBox { - "color"?: 'dark' | 'light' | 'system'; + "colorScheme"?: 'dark' | 'light' | 'system'; "facetProperty"?: string; "open"?: boolean; "resultMap"?: Partial; - "themeConfig"?: { colors: { light: { primaryColor: string }; dark: { primaryColor: string } } }; + "themeConfig"?: Partial; } interface SearchBoxToggler { } diff --git a/packages/ui-stencil/src/components/internal/orama-facets/orama-facets.tsx b/packages/ui-stencil/src/components/internal/orama-facets/orama-facets.tsx index 0395d8af..2f74b92f 100644 --- a/packages/ui-stencil/src/components/internal/orama-facets/orama-facets.tsx +++ b/packages/ui-stencil/src/components/internal/orama-facets/orama-facets.tsx @@ -13,8 +13,8 @@ export type Facet = { name: string; count: number } */ export class OramaFacets { @Prop() facets: Facet[] - @Prop() onFacetClick: (facetName: string) => void @Prop() selectedFacet: string + @Prop() onFacetClick: (facetName: string) => void handleClick(facet: Facet) { this.onFacetClick(facet.name) diff --git a/packages/ui-stencil/src/components/search-box/readme.md b/packages/ui-stencil/src/components/search-box/readme.md index 30038c3d..b2bce159 100644 --- a/packages/ui-stencil/src/components/search-box/readme.md +++ b/packages/ui-stencil/src/components/search-box/readme.md @@ -5,13 +5,13 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| --------------- | ---------------- | ----------- | ------------------------------------------------------------------------------------- | ----------- | -| `color` | `color` | | `"dark" \| "light" \| "system"` | `undefined` | -| `facetProperty` | `facet-property` | | `string` | `undefined` | -| `open` | `open` | | `boolean` | `false` | -| `resultMap` | -- | | `{ title?: string; description?: string; path?: string; section?: string; }` | `{}` | -| `themeConfig` | -- | | `{ colors: { light: { primaryColor: string; }; dark: { primaryColor: string; }; }; }` | `undefined` | +| Property | Attribute | Description | Type | Default | +| --------------- | ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `colorScheme` | `color-scheme` | | `"dark" \| "light" \| "system"` | `'light'` | +| `facetProperty` | `facet-property` | | `string` | `undefined` | +| `open` | `open` | | `boolean` | `false` | +| `resultMap` | -- | | `{ title?: string; description?: string; path?: string; section?: string; }` | `{}` | +| `themeConfig` | -- | | `{ typography?: DeepPartial<{ '--font-primary': string; }>; colors?: DeepPartial<{ light: { '--text-color-primary': string; '--text-color-secondary': string; '--text-color-teriary': string; '--text-color-inactive': string; '--background-color-primary': string; '--background-color-secondary': string; '--background-color-tertiary': string; '--background-color-fourth': string; '--border-color-primary': string; '--border-color-secondary': string; '--border-color-inactive': string; '--icon-color-primary': string; '--icon-color-secondary': string; '--icon-color-tertiary': string; '--icon-color-inactive': string; '--icon-color-accent': string; }; dark: { '--text-color-primary': string; '--text-color-secondary': string; '--text-color-teriary': string; '--text-color-inactive': string; '--background-color-primary': string; '--background-color-secondary': string; '--background-color-tertiary': string; '--background-color-fourth': string; '--border-color-primary': string; '--border-color-secondary': string; '--border-color-inactive': string; '--icon-color-primary': string; '--icon-color-secondary': string; '--icon-color-tertiary': string; '--icon-color-inactive': string; '--icon-color-accent': string; }; system: {}; }>; }` | `undefined` | ## Dependencies diff --git a/packages/ui-stencil/src/components/search-box/search-box.tsx b/packages/ui-stencil/src/components/search-box/search-box.tsx index dd941300..f6a57333 100644 --- a/packages/ui-stencil/src/components/search-box/search-box.tsx +++ b/packages/ui-stencil/src/components/search-box/search-box.tsx @@ -1,10 +1,11 @@ -import { Component, Host, Prop, Watch, h, Listen } from '@stencil/core' +import { Component, Host, Prop, Watch, h, Listen, Element, State } from '@stencil/core' import { OramaClient } from '@oramacloud/client' import { searchState } from '@/context/searchContext' import { chatContext } from '@/context/chatContext' import { globalContext } from '@/context/GlobalContext' import { ChatService } from '@/services/ChatService' import { SearchService } from '@/services/SearchService' +import type { TThemeOverrides } from '@/config/theme' import type { ResultMap } from '@/types' @Component({ @@ -13,12 +14,22 @@ import type { ResultMap } from '@/types' shadow: true, }) export class SearchBox { - @Prop() themeConfig: { colors: { light: { primaryColor: string }; dark: { primaryColor: string } } } - @Prop() color: 'dark' | 'light' | 'system' + @Element() el: HTMLElement + + @Prop() themeConfig: Partial + @Prop() colorScheme: 'dark' | 'light' | 'system' = 'light' @Prop() facetProperty?: string @Prop() open? = false @Prop() resultMap?: Partial = {} + @State() systemScheme: 'light' | 'dark' = 'light' + + @Watch('themeConfig') + @Watch('colorScheme') + watchHandler() { + this.updateTheme() + } + @Watch('open') handleOpenChange(newValue: boolean) { globalContext.open = newValue @@ -35,6 +46,49 @@ export class SearchBox { console.log('Item clicked', event.detail) } + updateTheme() { + const scheme = this.colorScheme === 'system' ? this.systemScheme : this.colorScheme + const uiElement = document.querySelector('#orama-ui') + + if (uiElement && scheme) { + uiElement.classList.remove('theme-light', 'theme-dark') + uiElement.classList.add(`theme-${scheme}`) + } + + this.updateCssVariables(scheme) + } + + updateCssVariables(scheme: 'light' | 'dark') { + const config = this.themeConfig + const root = document.querySelector('#orama-ui') as HTMLElement + + if (root && config && scheme) { + if (config.colors?.[scheme]) { + for (const key of Object.keys(config.colors[scheme])) { + root.style.setProperty(`${key}`, config.colors[scheme][key]) + } + } + if (config.typography) { + for (const key of Object.keys(config.typography)) { + root.style.setProperty(`${key}`, config.typography[key]) + } + } + } + } + + detectSystemColorScheme() { + const darkSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)') + + this.systemScheme = darkSchemeQuery.matches ? 'dark' : 'light' + + darkSchemeQuery.addEventListener('change', (event) => { + this.systemScheme = event.matches ? 'dark' : 'light' + if (this.colorScheme === 'system') { + this.updateTheme() + } + }) + } + componentWillLoad() { globalContext.open = this.open @@ -52,6 +106,9 @@ export class SearchBox { searchState.searchService = new SearchService(oramaClient) chatContext.chatService = new ChatService(oramaClient) + + this.updateTheme() + this.detectSystemColorScheme() } render() { diff --git a/packages/ui-stencil/src/config/colors.ts b/packages/ui-stencil/src/config/colors.ts index f4571da3..bd56021d 100644 --- a/packages/ui-stencil/src/config/colors.ts +++ b/packages/ui-stencil/src/config/colors.ts @@ -14,12 +14,12 @@ export const primitive = { gray900: '#151515', gray950: '#050505', purple500: '#8152EE', - purple700: '#6A4BB2' + purple700: '#6A4BB2', } export const sassVariables = {} -const semantic = { +const semanticColors = { light: { // TODO: placheholder primitives to be replaced '--text-color-primary': primitive.gray50, @@ -40,7 +40,7 @@ const semantic = { '--icon-color-secondary': primitive.gray200, '--icon-color-tertiary': primitive.gray600, '--icon-color-inactive': primitive.gray500, - '--icon-color-accent': primitive.purple500 + '--icon-color-accent': primitive.purple500, }, dark: { '--text-color-primary': primitive.gray50, @@ -61,10 +61,9 @@ const semantic = { '--icon-color-secondary': primitive.gray200, '--icon-color-tertiary': primitive.gray600, '--icon-color-inactive': primitive.gray500, - // TODO: check the primitive to use for accent - '--icon-color-accent': primitive.purple500 + '--icon-color-accent': primitive.purple500, }, - system: {} + system: {}, } -export default semantic +export default semanticColors diff --git a/packages/ui-stencil/src/config/theme.ts b/packages/ui-stencil/src/config/theme.ts index 9bb4ce70..32de9a42 100644 --- a/packages/ui-stencil/src/config/theme.ts +++ b/packages/ui-stencil/src/config/theme.ts @@ -3,7 +3,15 @@ import typography from './typography' const theme = { typography, - colors + colors, } +type TTheme = typeof theme + +type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P] +} + +export type TThemeOverrides = DeepPartial + export default theme