Code Monkey home page Code Monkey logo

svelte-themer's Introduction

svelte-themer

A theming engine for your Svelte apps using CSS Variables, persisted.

<script>
  import { ThemeWrapper, ThemeToggle } from 'svelte-themer'
</script>

<ThemeWrapper>
  <main>
    <h1>svelte themer</h1>
    <ThemeToggle />
  </main>
</ThemeWrapper>

<style>
  :global(html) {
    background-color: var(--theme-colors-background, initial);
    color: var(--theme-colors-text, initial);
  }
</style>

CSS Variables

CSS variables are created for app-wide consumption using the nomenclature --[prefix]-[property!]

For example:

  • --theme-text by default where property = 'text'
  • --base-text where prefix = 'base' and property = 'text'
  • --text where prefix = null || undefined and property = 'text'

Now supports adding all theme colors as theme-specific CSS variables:

const lightTheme = {
  light: {
    colors: {
      text: '#282230',
      background: {
        _: '#f1f1f1',
        contrast: '#b1b1b1',
      },
      primary: '#01796f',
      primary_dark: '#016159',
      secondary: '#562931',
    },
  },
}

Turns into

:root {
  --theme-light-colors-text: #282230;
  --theme-light-colors-background: #f1f1f1;
  --theme-light-colors-background-contrast: #b1b1b1;
  --theme-light-colors-primary: #01796f;
  --theme-light-colors-primary_dark: #016159;
  --theme-light-colors-secondary: #562931;
}

[theme='light'],
.theme--light {
  --theme-colors-text: var(--theme-light-colors-text);
  --theme-colors-background: var(--theme-light-colors-background);
  --theme-colors-background-contrast: --var(
    theme-light-colors-background-contrast
  );
  --theme-colors-primary: var(--theme-light-colors-primary);
  --theme-colors-primary_dark: var(--theme-light-colors-primary_dark);
  --theme-colors-secondary: var(--theme-light-colors-secondary);
}

Getting Started

Use the preset themes supplied by svelte-themer or create your own! Theme names are specified by the key, and all properties are transformed into CSS Variables.

NOTE: svelte-themer is preset with 3 themes to showcase the flexible functionality of toggle()

// src/themes.js
export const themes = {
  light: {
    colors: {
      text: '#282230',
      background: {
        _: '#f1f1f1',
        contrast: '#b1b1b1',
      },
      primary: '#01796f',
      primary_dark: '#016159',
      secondary: '#562931',
    },
  },
  dark: {
    colors: {
      text: '#f1f1f1',
      background: {
        _: '#27323a',
        contrast: '#0d1215',
      },
      primary: '#01978b',
      primary_dark: '#00887c',
      secondary: '#fe8690',
    },
  },
}

Components

With svelte-themer there are two components: a wrapper component, and a button for toggling themes. The provided button is more for convenience as the function used to toggle themes is exposed to the theme context.

ThemeWrapper

<!-- src/App.svelte -->
<script>
  import { ThemeWrapper } from 'svelte-themer'
  import themes from './themes.js'
</script>

<ThemeWrapper themes="{themes}">
  <main>
    <h1>My Svelte App</h1>
  </main>
</ThemeWrapper>

This allows any components nested to access the theme Context which wraps a writeable theme store

Theme Persistence

By default svelte-themer persists the chosen theme with localStorage, and can be modified via the key prop. To disabled persistence, provide key={null}.

<ThemeWrapper key="my-svelte-app__theme">
  <!--  -->
</ThemeWrapper>

Theme Loading Order

ThemeWrapper will load a theme on first visit based on the following order:

  1. User-provided - The value specified in the theme prop.
  2. Saved - User's stored choice (from localStorage)
  3. Prefers - User's Operating System settings (via prefers-color-scheme)
  4. Fallback - First theme in themes specified (from presets, light)

By default, the "prefers" step will choose a theme based on OS settings, however this can be modified to directly choose "light" or "dark" by leveraging the mode prop:

<ThemeWrapper mode="auto|light|dark">
  <!--  -->
</ThemeWrapper>

Accessing Theme Context

Described below is the pattern used for accessing theme context to create your own toggle button.

<!-- src/MyToggleButton.svelte -->
<script>
  import { getContext } from 'svelte'
  let { toggle, current, theme } = getContext('theme')
</script>

<button on:click="{toggle}">
  <slot>{$current}</slot>
</button>

Provided Theme Toggle

<!-- src/App.svelte -->
<script>
  import { ThemeWrapper, ThemeToggle } from 'svelte-themer'
  import themes from './themes.js'
</script>

<ThemeWrapper themes="{themes}">
  <main>
    <h1>My Svelte App</h1>
    <ThemeToggle />
  </main>
</ThemeWrapper>

Actions

use:theme

<script>
  import { theme } from 'svelte-themer/use'
  export let myTheme = {
    text: 'red',
  }
</script>

<div use:theme="{myTheme}">
  <p>Hello, World!</p>
</div>

<style>
  p {
    color: var(--text);
  }
</style>

use:stylesheet

<script>
  import { stylesheet } from 'svelte-themer/use'
  export let myTheme = {
    text: 'red',
  }
</script>

<div use:stylesheet="{myTheme}">
  <p>Hello, World!</p>
</div>

<style>
  p {
    color: var(--text);
  }
</style>

Contributing

Refer to the contributing guidelines.

License

MIT

svelte-themer's People

Contributors

dysfunc avatar josefaidt avatar rarenatoe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

svelte-themer's Issues

improve release notes and release automation

Description

Provide readable release notes with thanks to contributors.

  • fix github actions (currently the script to release is commented out, ensure versions are updated)
  • add "thank you" section for contributors

Dark mode on per theme basis

First off, nice job on the theme provider 👏 I like the linear approach to themes, but I was curious about your thoughts on having dark mode styling on a per theme basis v. independent themes (i.e., "light" and "dark")?

Instead of doing this:

[{
  name: 'light',
  colors: {
    text: '#282230',
    background: '#f1f1f1',
    primary: '#01796f',
    primary_dark: '#016159',
    secondary: '#562931',
  }
}]

You could do something like this:

[{
  name: 'forest',
  defaults: {
    colors: {
      text: '#282230',
      background: '#f1f1f1',
      primary: '#01796f',
      primary_dark: '#016159',
      secondary: '#562931',
    }
  },
  dark: {
    colors: {
      background: '#000'
    }
  }
}]

The defaults would contain all of theme variables, and dark would contain only the values you would want to override/modify.

You could expose a new mode toggle and allow each theme to control it's own dark mode presentation.

I'll create a PR with these changes.

can't reproduce it :/

using svelte basic template...

Folder structure: ⤵️

image

package.json ⤵️

image

main.js ⤵️

image

themes.js ⤵️

image

App.svelte ⤵️

image

Results:

Light ⤵️

image

Dark ⤵️

image

Is there something I'm missing?

Uncaught TypeError: ThemeWrapper is not a constructor

Hi.

Using sveltejs/template with nothing modified except svelte-themer ^0.4.8 installed as a dev dependency.

// App.svelte
<script>
	import { ThemeWrapper, ThemeToggle } from 'svelte-themer'
</script>
  
<ThemeWrapper>
	<main>
		<h1>svelte themer</h1>
		<ThemeToggle />
	</main>
</ThemeWrapper>

<style>
	:global(html) {
		background-color: var(--theme-colors-background, initial);
		color: var(--theme-colors-text, initial);
	}
</style>

npm run dev

In the browser console all I get is

Uncaught TypeError: ThemeWrapper is not a constructor
    create_fragment bundle.js:2449
    init index.mjs:1500
    App bundle.js:2518
    app main.js:3
    <anonymous> bundle.js:2536

I'm a Svelte noob, am I missing something?

Thank you for your consideration.

per container theme?

This is super useful!

I have a very special case where I might want to theme within containers. Do you know if it's possible to add a container flag such as

.container > :global(svg)

I am not sure how one would inject the .container > :global part...

The exampIe got this from is here:

https://svelte.dev/repl/4e04a629ab934863b09c4ee3f69d99a1?version=3.8.1

I'll try to check out your code to see if I can solve it but if you have a quick idea how to do it I'd appreciate it!!

Cheers, /Jon

Uncaught TypeError: getContext(...) is undefined

Hello,

svelte-themer 0.4.9 as a dev dependency, sveltejs/template.

Getting an error because getContext('theme') is undefined when trying to implement my own theme toggle button. Note that using the provided ThemeToggle everything worked.

// App.svelte
<script>
	import { ThemeWrapper } from 'svelte-themer'
	import ThemeButton from './components/ThemeButton.svelte'
	import themes from './themes.js'
</script>
  
<ThemeWrapper themes="{themes}">
	<main>
		<p>Hello</p>
		<ThemeButton />
	</main>
</ThemeWrapper>

<style>
	:global(html) {
		background-color: var(--theme-colors-background, initial);
		color: var(--theme-colors-text, initial);
	}
</style>
// components/ThemeButton.svelte
<script>
    import { getContext } from 'svelte'
    let { toggle, current, theme } = getContext('theme')
</script>

<button on:click="{toggle}">
    <slot>{$current}</slot>
</button>

Am I doing anything wrong?

Thanks

add preprocessor

Description

Add a preprocessor (ref #32) to generate CSS file for external consumption or preprocess into the ThemeWrapper style block (with global styles), negating the need for the current JS-based solution to load on mount.

Thoughts

  • how do we determine whether preprocessor is in use (e.g. when do we not execute the onMount behavior)?
  • do we add styles to ThemeWrapper style block with :global(html) or :root? Does this have unintended behaviors with initial load?
  • add an option to "eject" the created stylesheet for external consumption

Dependence on window

Hello Jose,

Great writeup on svelte theming! I came here after I failed to persist the themes myself, but it looks like I'll face the same issue here. If I'm using Sapper, or any other kind of SSR, then I won't have access to the window variable in my code (or localStorage or sessionStorage ...). Is there any way to remove that dependency? I see you use it to check the preferred mode of the browser. I noticed your localStorage is properly set inside onMount however!

clean up logic, remove JS consumption logic

  • move to reactive and afterUpdate for theme setter
  • switch to use theme.css file (must be linked to index.html and available)
    • would users rather feed themes.js OR feed theme.css

Themer breaks page.js routing

So my problem is that when I try to use this package it somehow breaks my page.js routing but it works if I refresh the page after clicking the link for the page in the navbar. Tried it on npm run dev and npm run build

Code:

// App.svelte
<script>
  import router from "page";
  import { ThemeWrapper, ThemeToggle } from "svelte-themer";

  import Home from "./pages/Home.svelte";
  import Files from "./pages/Files.svelte";
  import ThemesPage from "./pages/Themes.svelte";
  import Account from "./pages/Account.svelte";

  import Nav from "./components/Nav.svelte";
  import Footer from "./components/Footer.svelte";

  let page;

  router("/", () => (page = Home));
  router("/files", () => (page = Files));
  router("/themes", () => (page = ThemesPage));
  router("/account", () => (page = Account));

  router.start();
</script>

<ThemeWrapper>
  <Nav />
  <svelte:component this={page} />
  <ThemeToggle />
  <Footer />
</ThemeWrapper>
// Nav.svelte
<nav class="pa3 pa4-ns">
  <div class="tc pb3">
    <a
      class="fas fa-folder-open link dim f6 f5-ns dib mr3 pointer"
      href="/">home</a>
    <a
      class="fas fa-folder-open link dim f6 f5-ns dib mr3 pointer"
      href="/files">files</a>
    <a
      class="fas fa-folder-open link dim f6 f5-ns dib mr3 pointer"
      href="/themes">themes</a>
    <a
      class="fas fa-folder-open link dim f6 f5-ns dib mr3 pointer"
      href="/account">account</a>
  </div>
</nav>

Also I get this error in the console after importing this package and I don't really know what may be causing it

Uncaught Error: Function called outside component initialization
    get_current_component index.js:119
    setContext index.js:129
    instance index.js:514
    flush index.mjs:723
    init index.mjs:1477
    App main.js:5818
    app main.js:3
    <anonymous> main.js:5835
index.js:119:18

feat: support `prefers-color-scheme`

Support prefers-color-scheme with window.matchMedia() to determine user theme on first visit.

Current Behavior:

on visit, active theme choice is decided by

  1. local storage (if exists)
  2. fallback to first theme in themes array

Proposed Behavior:

on visit, active theme choice is decided by

  1. local storage (if exists)
  2. prefers-color-scheme choice (will be light or dark and themes should have a palette with one of these names)
  3. fallback to first theme in themes array

Changes

  • add new prop to disable proposed behavior, something like disablePrefersColorScheme? Forcing the new behavior may cause unwanted UX adjustments
  • add test to prove new behavior and prop are working as intended

Is global the right way to go?

This could be done with a wrapping element that does not affect the dom's structure beyond css. A mix of setProperty() and removeProperty() could be used to handle the theme transitions.

I do like your object storage for variable settings. A lot cleaner than what I was working on.

Possible benefits:
Global default variables could be defined in script to handle non-js systems. (rare to be sure)
-- global defaults also allow for partial rewrites of the variables. (i.e. no need to rewrite media breakpoints unless desired.)
Applying to a wrapping element allows for the nesting of variable settings. Perhaps modals look different.
Named wrappers could still use localStorage for defaults.
This allows for easier real-time changes with user-defined themes.
Property setters make the code cleaner since you don't have to build complex strings beyond your tiered variable names.

Listed below is a simple example. Everything in the slot would see the variable --txtColor as red.
display: contents; removes the wrapper from creating display issues (grids, etc).

<script>
	import { onMount } from 'svelte';
	let el;	
	onMount(() => {
		el.style.setProperty('--txtColor','red')
	});
	
</script>

<div bind:this={el} style="display: contents; ">
	<slot ></slot>
</div>

feat: support custom prefix for CSS variables

Support using a custom prefix for CSS variables through a prefix prop on ThemeWrapper

  • add PREFIX constant with value of theme in module scope of ThemeWrapper
  • add prefix prop with default PREFIX
  • add tests

Behavior:

  • if prefix={"vendor"}, CSS variables will look like --vendor-...
  • if prefix={null}, CSS variables will look like --...
  • if prefix={""}, ThemeWrapper should throw an error "Invalid prefix string supplied"
  • if prefix={" "}, ThemeWrapper should throw an error "Invalid prefix string supplied" (be sure to .trim() prefix string)

feat(next): themes object instead of array

// themes.js
export default {
  themer: {
    light: {},
    dark: {},
    colors: {},
  },
  forest: {
    light: {},
    dark: {},
    colors: {},
  },
  solarized: {
    light: {},
    dark: {},
    colors: {},
  },
}

The following packages did not export their

Setup info

I have installed through yarn add -D svelte-themer, however I have also tried adding as a normal dependency and nothing changed.

Errors

At build time I get the following

[rollup-plugin-svelte] The following packages did not export their `package.json` file so we could not check the "svelte" field. If you had difficulties importing svelte components from a package, then please contact the author and ask them to export the package.json file.

- svelte-themer

When I try to access the website I get the following:

Error: <ThemeWrapper> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules
    at validate_component (/.../project/__sapper__/dev/server/server.js:95:15)
    at /.../project/__sapper__/dev/server/server.js:15381:3
    at Object.$$render (/.../project/__sapper__/dev/server/server.js:113:22)
    at /.../project/__sapper__/dev/server/server.js:15438:40
    at $$render (/.../project/__sapper__/dev/server/server.js:113:22)
    at Object.render (/.../project/__sapper__/dev/server/server.js:121:26)
    at /.../project/__sapper__/dev/server/server.js:20314:49
    at Generator.next (<anonymous>)
    at fulfilled (/.../project/__sapper__/dev/server/server.js:15757:58)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
Error: <ThemeWrapper> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules
    at validate_component (/.../project/__sapper__/dev/server/server.js:95:15)
    at /.../project/__sapper__/dev/server/server.js:15381:3
    at Object.$$render (/.../project/__sapper__/dev/server/server.js:113:22)
    at /.../project/__sapper__/dev/server/server.js:15438:40
    at $$render (/.../project/__sapper__/dev/server/server.js:113:22)
    at Object.render (/.../project/__sapper__/dev/server/server.js:121:26)
    at /.../project/__sapper__/dev/server/server.js:20314:49
    at Generator.next (<anonymous>)
    at fulfilled (/.../project/__sapper__/dev/server/server.js:15757:58)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
Error: <ThemeWrapper> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules
    at validate_component (/.../project/__sapper__/dev/server/server.js:95:15)
    at /.../project/__sapper__/dev/server/server.js:15381:3
    at Object.$$render (/.../project/__sapper__/dev/server/server.js:113:22)
    at /.../project/__sapper__/dev/server/server.js:15438:40
    at $$render (/.../project/__sapper__/dev/server/server.js:113:22)
    at Object.render (/.../project/__sapper__/dev/server/server.js:121:26)
    at /.../project/__sapper__/dev/server/server.js:20314:49
    at Generator.next (<anonymous>)
    at fulfilled (/.../project/__sapper__/dev/server/server.js:15757:58)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

feat: support nested theme keys

Support nested theme keys for supplied theme.

When supplied:

const themes = [{
  name: 'light',
  colors: {
    grey: {
      _: '#8b868c',
      dark: '#5a535b',
    },
  },
}]

CSS Variables should generate as

--theme-light-grey: #d46a6a;
--theme-light-grey-dark: #5a535b; 

This should not change the functionality of supplying underscores in keys (i.e. primary_dark) as this is more for added flexibility and plans of adding base theme props.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.