Skip to content

Commit

Permalink
Improvements for theme color CSS variables (#229)
Browse files Browse the repository at this point in the history
* Set light theme CSS variables as default

* Scope theme color CSS variables

* Doc

* 0.9.1

* Update assets dependencies
  • Loading branch information
ArthurClemens authored Dec 30, 2024
1 parent 2a4f8dd commit 61f8f50
Show file tree
Hide file tree
Showing 20 changed files with 1,256 additions and 474 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ node_modules

/test/assertion_failures/*
!/test/assertion_failures/README.md
assets/css/gen-variables.scss
assets/css/gen-*
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.9.1

Improvements for theme color CSS variables:

- The light theme variables are now set as default, so it is no longer needed to wrap components inside a theme wrapper.
- When using the `@scope` rule, color CSS variables now work inside the scope.

## 0.9.0

### Changes
Expand Down
5 changes: 2 additions & 3 deletions assets/css/primercss.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
@import "@primer/css/color-modes/index.scss";
@import "@primer/css/core/index.scss";
@import "@primer/css/product/index.scss";
@import "@primer/css/marketing/index.scss";
Expand All @@ -12,8 +11,8 @@
@import '@primer/primitives/dist/css/functional/size/viewport.css';
@import '@primer/primitives/dist/css/functional/typography/typography.css';

/* color */
@import '@primer/primitives/dist/css/functional/themes/light.css';
/* color modes */
@import 'gen-themes-light.css';
@import '@primer/primitives/dist/css/functional/themes/light-tritanopia.css';
@import '@primer/primitives/dist/css/functional/themes/light-high-contrast.css';
@import '@primer/primitives/dist/css/functional/themes/light-colorblind.css';
Expand Down
1,255 changes: 964 additions & 291 deletions assets/package-lock.json

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions assets/package.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
{
"name": "primer-live",
"version": "0.9.0",
"version": "0.9.1",
"description": "JavaScript and CSS for PrimerLive",
"license": "MIT",
"repository": {},
"scripts": {
"build": "tsx scripts/build.ts",
"post-process-scoped-css": "tsx scripts/post-process-scoped-css.ts",
"pre-process-light-theme-css": "tsx scripts/pre-process-light-theme-css.ts",
"build:types": "./node_modules/typescript/bin/tsc --p tsconfig.gen.json"
},
"dependencies": {
"@primer/css": "21.5.0",
"@primer/view-components": "^0.34.0",
"@types/phoenix_live_view": "^0.18.5",
"fs": "^0.0.1-security",
"prettier": "^3.3.3",
"prettier": "^3.4.2",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/node": "^22.4.1",
"@types/node": "^22.10.2",
"@types/yargs": "^17.0.33",
"esbuild": "^0.23.1",
"esbuild": "^0.24.2",
"esbuild-sass-plugin": "^3.3.1",
"tsx": "^4.17.0",
"typescript": "^5.5.4"
"tsx": "^4.19.2",
"typescript": "^5.7.2"
},
"author": "Arthur Clemens <[email protected]> (http://arthurclemens.com)"
}
}
23 changes: 5 additions & 18 deletions assets/scripts/post-process-scoped-css.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
import { join } from "path";
import * as fs from "fs";
import * as prettier from "prettier";
import yargs from "yargs/yargs";
import { hideBin } from "yargs/helpers";
import { getCssText, storeCssText } from "./shared"

declare var process: {
argv: string[];
};

function getCssText(path: string) {
return fs
.readFileSync(path, {
encoding: "utf8",
flag: "r",
})
.toString();
}

function replaceTexts(text: string) {
return text
.replace(/:root/g, ":scope")
.replace(/.__AMP__/g, "&");
.replace(/:root/g, ":scope")
.replace(/.__AMP__/g, "&")
.replace(/(\[data-color-mode=[a-z-_]+\]\[data-\w+-theme\*?=[a-z-_]+\])/g, "$1 :scope")
;
}

function wrapInScope(text: string) {
Expand All @@ -29,12 +22,6 @@ function wrapInScope(text: string) {
}`;
}

function storeCssText(path: string, data: string) {
fs.writeFileSync(path, data, {
flag: "w",
});
}

type ProcessFileOpts = {
prettify: boolean;
};
Expand Down
24 changes: 24 additions & 0 deletions assets/scripts/pre-process-light-theme-css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

import { join } from "path";
import { getCssText, storeCssText } from "./shared"

const ORIGIN_PATH = "assets/node_modules/@primer/primitives/dist/css/functional/themes/light.css"
const DESTINATION_PATH = "assets/css/gen-themes-light.css"

const relativeOriginPath = join("../", ORIGIN_PATH);
const relativeDestinationPath = join("../", DESTINATION_PATH);

async function processFile(
originPath: string,
destinationPath: string
) {

let text = getCssText(originPath);
text = `
:root,
${text}`;

storeCssText(destinationPath, text);
}

processFile(relativeOriginPath, relativeDestinationPath);
16 changes: 16 additions & 0 deletions assets/scripts/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as fs from "fs";

export function getCssText(path: string) {
return fs
.readFileSync(path, {
encoding: "utf8",
flag: "r",
})
.toString();
}

export function storeCssText(path: string, data: string) {
fs.writeFileSync(path, data, {
flag: "w",
});
}
15 changes: 11 additions & 4 deletions doc-extra/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,25 @@ Otherwise:

The relatively new `@scope` rule enables localized CSS. In practical terms, it allows multiple CSS libraries to coexist within a single codebase without causing style conflicts. For example, use Tailwind components in one section of the page, and use PrimerLive components in another section.

Browser support for the @scope rule is still limited. For more details, see [Can I use: @scope rule](https://caniuse.com/css-cascade-scope). If you're using PrimerLive in a controlled environment where users have access to the latest browsers, you may consider experimenting with this feature.
Browser support for the `@scope` rule is still limited. For more details, see [Can I use: @scope rule](https://caniuse.com/css-cascade-scope). If you're using PrimerLive in a controlled environment where users have access to the latest browsers, you may consider experimenting with this feature.

To use PrimerLive CSS with CSS scope:

- Change the CSS import to `primer-live-scoped.min.css` (or `primer-live-scoped.css`).
- Create a container with class `primer-live` (not the `html` element) where you will be using PrimerLive components.
- Create a container with class `primer-live` where you will be using PrimerLive components. This can be any element, except the `html` element.
- To make theming work properly, the container must be a child of the `theme` component (or equivalent element with attributes created with `PrimerLive.Theme.html_attributes/2`).

Example:

```
<html>
<body>
<section>Some content</section>
<section class="primer-live">PrimerLive components</section>
<section>
Some content
</section>
<section class="primer-live">
PrimerLive components
</section>
</body>
</html>
```
Expand Down
5 changes: 5 additions & 0 deletions doc-extra/styling.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Styling

- [Primer Design System](#primer-design-system)
- [Light and dark theme](#light-and-dark-theme)
- [Customizing components](#customizing-components)
- [Style attributes](#style-attributes)
- [Adding classes](#adding-classes)
Expand All @@ -21,6 +22,10 @@ PrimerLive components are based on the styling described in the [Primer Design S

The CSS that comes with PrimerLive includes component styles and utility classes.

## Light and dark theme

PrimerLive contains styles for light and dark color modes and themes, with support for color blindness. See `PrimerLive.Theme` for usage and approaches to persist theme settings.

## Customizing components

### Style attributes
Expand Down
27 changes: 14 additions & 13 deletions lib/component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1278,7 +1278,7 @@ defmodule PrimerLive.Component do
phx-target={@myself}
>
<%= label %>
</:item>
</:item>
</.tabnav>
...
Expand Down Expand Up @@ -1608,7 +1608,7 @@ defmodule PrimerLive.Component do
phx-target={@myself}
>
<%= label %>
</:item>
</:item>
</.underline_nav>
...
Expand Down Expand Up @@ -2546,7 +2546,7 @@ defmodule PrimerLive.Component do
slot :item,
required: true,
doc: """
Subnav buttons item.
Subnav buttons item.
- To create a link element, pass attribute `href`, `navigate` or `patch`.
- To pass event data, use `phx-click` and `phx-value-item`.
Expand Down Expand Up @@ -6976,7 +6976,7 @@ defmodule PrimerLive.Component do
slot :item,
required: true,
doc: """
Menu item content.
Menu item content.
- To create a link element, pass attribute `href`, `navigate` or `patch`.
- To pass event data, use `phx-click` and `phx-value-item`.
Expand Down Expand Up @@ -13088,9 +13088,9 @@ defmodule PrimerLive.Component do
@doc section: :theme

@doc ~S"""
Creates a wrapper that sets the light/dark color mode and theme, with support for color blindness.
Creates a wrapper that sets the light and dark color mode and theme, with support for color blindness.
See also `PrimerLive.Theme`.
See also `PrimerLive.Theme` for approaches to persist theme settings.
## Alternative method
Expand Down Expand Up @@ -13133,15 +13133,16 @@ defmodule PrimerLive.Component do
</.theme>
```
Use a `theme_state` struct for easier passing around state:
Use a `theme_state` struct for passing around state:
```
assigns = assign
|> assign(:theme_state, %{
color_mode: "light",
light_theme: "light_high_contrast",
dark_theme: "dark_high_contrast"
})
theme_state = %{
color_mode: "light",
light_theme: "light_high_contrast",
dark_theme: "dark_high_contrast"
}
assigns = assigns |> assign(:theme_state, theme_state)
<.theme theme_state={@theme_state}>
Content
Expand Down
34 changes: 25 additions & 9 deletions lib/theme/theme.ex
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
defmodule PrimerLive.Theme do
@moduledoc """
Primer CSS contains styles for light/dark color modes and themes, with support for color blindness.
PrimerLive contains styles for light and dark color modes and themes, with support for color blindness.
PrimerLive provides components and functions to work with themes:
PrimerLive provides these theme components and functions:
- [`theme/1`](`PrimerLive.Component.theme/1`) - wrapper to set the theme on child elements
- [`theme_menu_options/1`](`PrimerLive.Component.theme_menu_options/1`) - contents for a theme menu
- `html_attributes/2` - HTML attributes to set a theme on a component or element directly
## Persistency
## Theme component
The `theme/1` component sets the required HTML attributes.
There is no easy way to save persistent session data in LiveView because LiveView's state is stored in a process that ends when the page is left.
Default settings (light theme):
### Session for theme state has been removed (as of 0.4.0)
```
<.theme>
Content
</.theme>
```
Using the session to store the theme state (for example [using a AJAX call roundtrip](https://thepugautomatic.com/2020/05/persistent-session-data-in-phoenix-liveview/)) does not work as expected: when navigating to another LiveView page, the updated session data is not refetched and only becomes available after a page refresh. This means that the previous offered solution with `PrimerLive.ThemeSessionController` is no longer recommended and in fact removed.
To hardcode the theme to dark mode:
```
<.theme color_mode="dark" dark_theme="dark_dimmed">
Content
</.theme>
```
### Alternatives: database or cache
Usually, the theme is set to user preferences. A menu with theme options can be created with `theme_menu_options/1` - see "Handling user selection" below.
## Persistency
If you already have a database set up for storing data by session ID, it's a small step to integrate the theme state with it.
Expand All @@ -26,6 +40,10 @@ defmodule PrimerLive.Theme do
- [Cachex with Phoenix (2020)](https://www.alenm.com/code/phoenix-cachex)
- [Use caching to speed up data loading in Phoenix LiveView](https://fullstackphoenix.com/quick_tips/liveview-caching)
### Not recommended: Session for theme state
Using the session to store the theme state (for example [using a AJAX call roundtrip](https://thepugautomatic.com/2020/05/persistent-session-data-in-phoenix-liveview/)) does not work as expected: when navigating to another LiveView page, the updated session data is not refetched and only becomes available after a page refresh.
## Handling user selection
Assuming you are providing a [theme menu](`PrimerLive.Component.theme_menu_options/1`) on the website, the option the user selects must be stored in persistent storage.
Expand Down Expand Up @@ -61,8 +79,6 @@ defmodule PrimerLive.Theme do
{:noreply, socket}
end
```
"""

use Phoenix.Component
Expand Down
10 changes: 7 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule PrimerLive.MixProject do
def project do
[
app: :primer_live,
version: "0.9.0",
version: "0.9.1",
elixir: "~> 1.17",
homepage_url: "https://github.com/ArthurClemens/primer_live",
elixirc_paths: elixirc_paths(Mix.env()),
Expand Down Expand Up @@ -144,15 +144,19 @@ defmodule PrimerLive.MixProject do
"cmd mix docs"
],
"assets.build": [
# Pre-process CSS
"cmd npm --prefix assets run pre-process-light-theme-css",
# Cleanup built files
"cmd rm -rf priv/static/*",
# Build JS and CSS
"cmd npm --prefix assets run build:types",
"cmd npm --prefix assets run build -- --bundle --format=esm --sourcemap --outfile=../priv/static/primer-live.esm.js",
"cmd npm --prefix assets run build -- --format=iife --target=es2016 --outfile=../priv/static/primer-live.js",
"cmd npm --prefix assets run build -- --format=cjs --sourcemap --outfile=../priv/static/primer-live.cjs.js",
"cmd npm --prefix assets run build -- --format=iife --target=es2016 --minify --outfile=../priv/static/primer-live.min.js",
# Scoped CSS (discard js later)
"cmd SCOPED=1 npm --prefix assets run build -- --format=iife --target=es2016 --minify --outfile=../priv/static/primer-live-scoped.min.js",
# Scoped CSS (discard JS later)
"cmd SCOPED=1 npm --prefix assets run build -- --format=iife --target=es2016 --outfile=../priv/static/primer-live-scoped.js",
"cmd SCOPED=1 npm --prefix assets run build -- --format=iife --target=es2016 --minify --outfile=../priv/static/primer-live-scoped.min.js",
# Post-process scoped CSS
"cmd npm --prefix assets run post-process-scoped-css -- --file=priv/static/primer-live-scoped.css --prettify=true",
"cmd npm --prefix assets run post-process-scoped-css -- --file=priv/static/primer-live-scoped.min.css --prettify=false",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "primer-live",
"version": "0.9.0",
"version": "0.9.1",
"description": "JavaScript and CSS for PrimerLive",
"license": "MIT",
"module": "./priv/static/primer-live.esm.js",
Expand Down
Loading

0 comments on commit 61f8f50

Please sign in to comment.