@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 +35 -0
- package/index.ts +6 -0
- package/package.json +46 -0
- package/src/env.d.ts +2 -0
- package/src/script.astro +14 -0
- package/src/theme-mode-select.astro +201 -0
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
|
+
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/non-html-pages)
|
|
10
|
+
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/non-html-pages)
|
|
11
|
+
[](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
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
package/src/script.astro
ADDED
|
@@ -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>
|