@pindoba/astro-theme-mode-select 0.0.0-alpha-20241003230712

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # Astro Starter Kit: Component Package
2
+
3
+ This is a template for an Astro component library. Use this template for writing components to use in multiple projects or publish to NPM.
4
+
5
+ ```sh
6
+ npm create astro@latest -- --template component
7
+ ```
8
+
9
+ [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/non-html-pages)
10
+ [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/non-html-pages)
11
+ [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/component/devcontainer.json)
12
+
13
+ ## 🚀 Project Structure
14
+
15
+ Inside of your Astro project, you'll see the following folders and files:
16
+
17
+ ```text
18
+ /
19
+ ├── index.ts
20
+ ├── src
21
+ │ └── MyComponent.astro
22
+ ├── tsconfig.json
23
+ ├── package.json
24
+ ```
25
+
26
+ The `index.ts` file is the "entry point" for your package. Export your components in `index.ts` to make them importable from your package.
27
+
28
+ ## 🧞 Commands
29
+
30
+ All commands are run from the root of the project, from a terminal:
31
+
32
+ | Command | Action |
33
+ | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | `npm link` | Registers this package locally. Run `npm link my-component-library` in an Astro project to install your components |
35
+ | `npm publish` | [Publishes](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages#publishing-unscoped-public-packages) this package to NPM. Requires you to be [logged in](https://docs.npmjs.com/cli/v8/commands/npm-adduser) |
package/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // Do not write code directly here, instead use the `src` folder!
2
+ // Then, use this file to export everything you want your user to access.
3
+
4
+ import Component from "./src/theme-mode-select.astro";
5
+
6
+ export default Component;
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@pindoba/astro-theme-mode-select",
3
+ "version": "0.0.0-alpha-20241003230712",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./index.ts",
7
+ "./script": "./src/script.astro"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "index.ts"
12
+ ],
13
+ "keywords": [
14
+ "astro-component"
15
+ ],
16
+ "devDependencies": {
17
+ "@iconify-json/lucide": "^1.2.7",
18
+ "astro": "^4.15.11",
19
+ "unplugin-icons": "^0.19.3"
20
+ },
21
+ "peerDependencies": {
22
+ "astro": "^4.0.0"
23
+ },
24
+ "dependencies": {
25
+ "@astrojs/check": "^0.9.3",
26
+ "@types/sanitize-html": "^2.13.0",
27
+ "astro-icon": "^1.1.1",
28
+ "typescript": "^5.6.2",
29
+ "@pindoba/astro-button": "0.0.0-alpha-20241003230712",
30
+ "@pindoba/astro-group": "0.0.0-alpha-20241003230712",
31
+ "@pindoba/astro-radio": "0.0.0-alpha-20241003230712",
32
+ "@pindoba/astro-popover": "0.0.0-alpha-20241003230712",
33
+ "@pindoba/panda": "0.0.0-alpha-20241003230712",
34
+ "@pindoba/styles": "0.0.0-alpha-20241003230712",
35
+ "@pindoba/utils": "0.0.0-alpha-20241003230712"
36
+ },
37
+ "scripts": {
38
+ "lint": "eslint . --max-warnings 0",
39
+ "lint:fix": "eslint . --fix --max-warnings 0",
40
+ "format": "prettier --write .",
41
+ "format:fix": "prettier --write .",
42
+ "check": "astro check",
43
+ "dev": "astro check --watch",
44
+ "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist && rm -rf .astro"
45
+ }
46
+ }
package/src/env.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // eslint-disable-next-line @typescript-eslint/triple-slash-reference
2
+ /// <reference path="../.astro/types.d.ts" />
@@ -0,0 +1,14 @@
1
+ <script is:inline>
2
+ const systemThemeMode = window.matchMedia("(prefers-color-scheme: dark)")
3
+ .matches
4
+ ? "dark"
5
+ : "light";
6
+
7
+ const currentThemeMode = localStorage.getItem("theme-mode") || "system";
8
+
9
+ const theme =
10
+ currentThemeMode === "system" ? systemThemeMode : currentThemeMode;
11
+
12
+ document.documentElement.setAttribute("data-theme-mode", theme);
13
+ document.documentElement.style.colorScheme = theme;
14
+ </script>
@@ -0,0 +1,201 @@
1
+ ---
2
+ import Group from "@pindoba/astro-group";
3
+ import Radio from "@pindoba/astro-radio";
4
+ import Button from "@pindoba/astro-button";
5
+ import Popover from "@pindoba/astro-popover";
6
+ import { css } from "@pindoba/panda/css";
7
+ import { visuallyHidden } from "@pindoba/panda/patterns";
8
+ import LucideSunMoon from "~icons/lucide/sun-moon?raw";
9
+ import LucideSun from "~icons/lucide/sun?raw";
10
+ import LucideMoon from "~icons/lucide/moon?raw";
11
+
12
+ import { escapeSVG } from "@pindoba/utils/escapeSVG";
13
+
14
+ interface Props {
15
+ id?: string;
16
+ }
17
+
18
+ const { id = "theme-mode" }: Props = Astro.props;
19
+ ---
20
+
21
+ <script>
22
+ import { usePopover } from "@pindoba/astro-popover";
23
+ type ThemeMode = "system" | "dark" | "light";
24
+ class PindobaThemeModeSelect extends HTMLDivElement {
25
+ constructor() {
26
+ super();
27
+
28
+ const trigger = document.getElementById(`${this.id}-dialog-trigger`);
29
+ const radios = document.querySelectorAll<HTMLInputElement>(
30
+ `#${this.id}-system-radio[name="theme-mode"], #${this.id}-dark-radio[name="theme-mode"], #${this.id}-light-radio[name="theme-mode"]`,
31
+ );
32
+
33
+ let currentThemeMode: ThemeMode =
34
+ (localStorage.getItem("theme-mode") as ThemeMode) || "system";
35
+
36
+ const mediaMatcher = window.matchMedia("(prefers-color-scheme: dark)");
37
+ let systemThemeMode: ThemeMode = mediaMatcher.matches ? "dark" : "light";
38
+
39
+ const isThemeMode = (themeMode: string): themeMode is ThemeMode =>
40
+ ["system", "dark", "light"].includes(themeMode);
41
+
42
+ const applyThemeMode = (themeMode: ThemeMode) => {
43
+ if (isThemeMode(themeMode)) {
44
+ const mode = themeMode === "system" ? systemThemeMode : themeMode;
45
+
46
+ document.documentElement.setAttribute("data-theme-mode", mode);
47
+ localStorage.setItem("theme-mode", themeMode);
48
+
49
+ if (currentThemeMode !== "system") {
50
+ const radio = document.querySelector<HTMLInputElement>(
51
+ `#${this.id}-${themeMode}-radio[name="theme-mode"]`,
52
+ );
53
+ if (radio) radio.checked = true;
54
+ }
55
+
56
+ trigger?.setAttribute(
57
+ "aria-label",
58
+ `Change theme mode; The current theme mode is ${mode}`,
59
+ );
60
+ }
61
+ };
62
+
63
+ mediaMatcher.addEventListener("change", (event) => {
64
+ systemThemeMode = event.matches ? "dark" : "light";
65
+
66
+ if (currentThemeMode === "system") {
67
+ applyThemeMode(systemThemeMode);
68
+ }
69
+ });
70
+
71
+ radios.forEach((radio) => {
72
+ if (radio.id === currentThemeMode) {
73
+ radio.checked = true;
74
+ }
75
+
76
+ radio.addEventListener("change", (event) => {
77
+ const target = event.target as HTMLInputElement;
78
+ const themeMode = target.value as ThemeMode;
79
+ currentThemeMode = themeMode;
80
+ applyThemeMode(themeMode);
81
+ });
82
+ });
83
+
84
+ // Check if theme mode is stored in local storage
85
+ const storedThemeMode = localStorage.getItem("theme-mode");
86
+ if (storedThemeMode && isThemeMode(storedThemeMode)) {
87
+ applyThemeMode(storedThemeMode);
88
+ }
89
+
90
+ usePopover({
91
+ id: `${this.id}-dialog`,
92
+ trigger: trigger,
93
+ onOpen: () => {
94
+ const themeMode: ThemeMode =
95
+ (localStorage.getItem("theme-mode") as ThemeMode) || "system";
96
+
97
+ const radio = document.querySelector<HTMLInputElement>(
98
+ `#${this.id}-${themeMode}-radio[name="theme-mode"]`,
99
+ );
100
+ radio?.focus();
101
+ if (radio) {
102
+ radio.checked = true;
103
+ }
104
+ },
105
+ });
106
+ }
107
+ }
108
+
109
+ if (!customElements.get("pindoba-theme-mode-select")) {
110
+ customElements.define("pindoba-theme-mode-select", PindobaThemeModeSelect, {
111
+ extends: "div",
112
+ });
113
+ }
114
+ </script>
115
+ <div is="pindoba-theme-mode-select" id={id}>
116
+ <Button id=`${id}-dialog-trigger` size="md" emphasis="ghost">
117
+ <span
118
+ id=`${id}-trigger-dark-icon`
119
+ set:html={escapeSVG(String(LucideSun))}
120
+ class={css({
121
+ display: "none",
122
+ "html[data-theme-mode='dark'] &": {
123
+ display: "block",
124
+ },
125
+ })}
126
+ />
127
+ <span
128
+ id={`${id}-trigger-light-icon`}
129
+ set:html={escapeSVG(String(LucideMoon))}
130
+ class={css({
131
+ display: "none",
132
+ "html[data-theme-mode='light'] &": {
133
+ display: "block",
134
+ },
135
+ })}
136
+ />
137
+ </Button>
138
+
139
+ <Popover
140
+ id={`${id}-dialog`}
141
+ class={css({
142
+ width: "fit-content !important",
143
+ })}
144
+ showCloseButton={false}
145
+ >
146
+ <fieldset>
147
+ <legend class={visuallyHidden()}>Select a theme mode</legend>
148
+ <Group direction="vertical">
149
+ <Radio
150
+ appearance="button"
151
+ id={`${id}-system-radio`}
152
+ value={"system"}
153
+ name="theme-mode"
154
+ >
155
+ <div
156
+ class={css({
157
+ display: "flex",
158
+ alignItems: "center",
159
+ gap: "xs",
160
+ flexDirection: "row",
161
+ })}
162
+ >
163
+ <Fragment set:html={escapeSVG(String(LucideSunMoon))} /> System
164
+ </div></Radio
165
+ >
166
+ <Radio
167
+ appearance="button"
168
+ id=`${id}-dark-radio`
169
+ value={"dark"}
170
+ name="theme-mode"
171
+ ><div
172
+ class={css({
173
+ display: "flex",
174
+ alignItems: "center",
175
+ gap: "xs",
176
+ flexDirection: "row",
177
+ })}
178
+ >
179
+ <Fragment set:html={escapeSVG(String(LucideSun))} /> Dark
180
+ </div></Radio
181
+ >
182
+ <Radio
183
+ appearance="button"
184
+ id=`${id}-light-radio`
185
+ value={"light"}
186
+ name="theme-mode"
187
+ ><div
188
+ class={css({
189
+ display: "flex",
190
+ alignItems: "center",
191
+ gap: "xs",
192
+ flexDirection: "row",
193
+ })}
194
+ >
195
+ <Fragment set:html={escapeSVG(String(LucideMoon))} /> Light
196
+ </div></Radio
197
+ >
198
+ </Group>
199
+ </fieldset>
200
+ </Popover>
201
+ </div>