@pokela/components 1.1.0 → 2.0.0

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/Accordion.js CHANGED
@@ -4,7 +4,7 @@ import { useId, useState } from 'react';
4
4
  import Icon from './Icon';
5
5
  export function Accordion({ number, title, description, meta, children, open, onToggle, labels, }) {
6
6
  const id = useId();
7
- return (_jsxs("div", { className: "accordion", children: [_jsxs("button", { type: "button", className: "list-row list-row--numbered list-row--expandable bleed", "aria-expanded": open, "aria-controls": id, onClick: onToggle, children: [_jsx("span", { className: "meta list-row__number", children: number }), _jsxs("span", { className: "list-row__main", children: [_jsx("span", { className: "list-row__title", children: title }), _jsx("span", { className: "list-row__description", children: description })] }), _jsx("span", { className: "meta list-row__meta", children: meta }), _jsx(Icon, { name: open ? 'minus' : 'plus', size: "l", className: "list-row__icon" }), _jsx("span", { className: "visually-hidden", children: open ? labels.close : labels.open })] }), open ? (_jsx("div", { id: id, className: "accordion__panel page", role: "region", "aria-label": title, children: children })) : null] }));
7
+ return (_jsxs("div", { className: "accordion", children: [_jsxs("button", { type: "button", className: "list-row list-row--numbered list-row--expandable bleed", "aria-expanded": open, "aria-controls": id, onClick: onToggle, children: [_jsx("span", { className: "meta list-row__number", children: number }), _jsxs("span", { className: "list-row__main", children: [_jsx("span", { className: "display-s list-row__title", children: title }), _jsx("span", { className: "body-s muted list-row__description", children: description })] }), _jsx("span", { className: "meta list-row__meta", children: meta }), _jsx(Icon, { name: open ? 'minus' : 'plus', size: "l", className: "list-row__icon" }), _jsx("span", { className: "visually-hidden", children: open ? labels.close : labels.open })] }), open ? (_jsx("div", { id: id, className: "accordion__panel page", role: "region", "aria-label": title, children: children })) : null] }));
8
8
  }
9
9
  /** Pitää huolen että vain yksi rivi on auki kerrallaan. */
10
10
  export function useAccordionGroup() {
package/ListRow.js CHANGED
@@ -30,5 +30,5 @@ export default function ListRow({ title, number, description, meta, size = 's',
30
30
  className,
31
31
  ]
32
32
  .filter(Boolean)
33
- .join(' '), children: [number ? _jsx("span", { className: "meta list-row__number", children: number }) : null, _jsxs("span", { className: "list-row__main", children: [_jsx("span", { className: "list-row__title", children: title }), description ? _jsx("span", { className: "list-row__description", children: description }) : null] }), meta ? _jsx("span", { className: "meta list-row__meta", children: meta }) : null] }));
33
+ .join(' '), children: [number ? _jsx("span", { className: "meta list-row__number", children: number }) : null, _jsxs("span", { className: "list-row__main", children: [_jsx("span", { className: `list-row__title ${size === 'm' ? 'display-m' : 'display-s'}`, children: title }), description ? _jsx("span", { className: "body-s muted list-row__description", children: description }) : null] }), meta ? _jsx("span", { className: "meta list-row__meta", children: meta }) : null] }));
34
34
  }
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @pokela/components
2
2
 
3
- React-komponentit: **Pokela Design System**, versio 1.1.0.
3
+ React-komponentit: **Pokela Design System**, versio 2.0.0.
4
4
 
5
5
  > Generoitu hakemistosta `components/` — samoista tiedostoista joita sivusto
6
6
  > ajaa. Älä muokkaa käsin: muutokset tehdään lähteeseen ja paketti
@@ -23,6 +23,8 @@ tarvita.
23
23
  - `Accordion`
24
24
  - `Grid`
25
25
  - `Reveal`
26
+ - `ThemeScript`
27
+ - `ThemeToggle`
26
28
 
27
29
  ### Linkit
28
30
 
@@ -41,7 +43,6 @@ import Link from 'next/link';
41
43
  - **Nav, Footer** — tarvitsevat sivuston sanakirjan ja reitityksen
42
44
  - **BeforeAfter** — rakentuu Median varaan, ja Media lukee kuvamanifestia
43
45
  - **ComponentView, PrintCv** — ovat riippumattomia mutta sivuston ominaisuuksia — lähdekoodinäkymä ja CV:n tulostusasu — eivät design systemin osia
44
- - **ThemeToggle, ThemeScript** — teemanvaihto käyttää luokkaa `.nav__toggle`, joka on määritelty Navin tyyleissä. Kytkentä on lähteessä eikä paketoinnissa, joten se korjataan CSS:ää siistimällä eikä tätä listaa muuttamalla
45
46
 
46
47
  ## Tyylit
47
48
 
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Teeman palautus ennen ensimmäistä maalausta.
3
+ *
4
+ * Ilman tätä sivu välähtäisi vaaleana ennen kuin tallennettu tumma
5
+ * teema ehtii päälle. Skripti on tarkoituksella pieni ja synkroninen,
6
+ * ja se ajetaan <head>issä ennen tyylejä.
7
+ *
8
+ * Ilman tallennettua valintaa data-themeä ei aseteta lainkaan, jolloin
9
+ * tokens.css:n `color-scheme: light dark` seuraa käyttöjärjestelmää.
10
+ */
11
+ export declare const THEME_STORAGE_KEY = "pokela-theme";
12
+ export default function ThemeScript(): import("react/jsx-runtime").JSX.Element;
package/ThemeScript.js ADDED
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Teeman palautus ennen ensimmäistä maalausta.
4
+ *
5
+ * Ilman tätä sivu välähtäisi vaaleana ennen kuin tallennettu tumma
6
+ * teema ehtii päälle. Skripti on tarkoituksella pieni ja synkroninen,
7
+ * ja se ajetaan <head>issä ennen tyylejä.
8
+ *
9
+ * Ilman tallennettua valintaa data-themeä ei aseteta lainkaan, jolloin
10
+ * tokens.css:n `color-scheme: light dark` seuraa käyttöjärjestelmää.
11
+ */
12
+ export const THEME_STORAGE_KEY = 'pokela-theme';
13
+ const script = `(function(){try{var t=localStorage.getItem(${JSON.stringify(THEME_STORAGE_KEY)});if(t==='light'||t==='dark'){document.documentElement.setAttribute('data-theme',t)}}catch(e){}})()`;
14
+ export default function ThemeScript() {
15
+ return _jsx("script", { dangerouslySetInnerHTML: { __html: script } });
16
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Teemanvaihto tekstinappina — ei ikonia, ei väriä.
3
+ * Kierto: järjestelmä → vaalea → tumma → järjestelmä.
4
+ *
5
+ * `data-theme` puuttuu kun valinta on "järjestelmä", jolloin
6
+ * tokens.css:n light-dark() seuraa prefers-color-schemeä.
7
+ */
8
+ export default function ThemeToggle({ labels, }: {
9
+ labels: {
10
+ theme: string;
11
+ light: string;
12
+ dark: string;
13
+ system: string;
14
+ };
15
+ }): import("react/jsx-runtime").JSX.Element;
package/ThemeToggle.js ADDED
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ import { THEME_STORAGE_KEY } from './ThemeScript';
5
+ /**
6
+ * Teemanvaihto tekstinappina — ei ikonia, ei väriä.
7
+ * Kierto: järjestelmä → vaalea → tumma → järjestelmä.
8
+ *
9
+ * `data-theme` puuttuu kun valinta on "järjestelmä", jolloin
10
+ * tokens.css:n light-dark() seuraa prefers-color-schemeä.
11
+ */
12
+ export default function ThemeToggle({ labels, }) {
13
+ const [theme, setTheme] = useState('system');
14
+ const [mounted, setMounted] = useState(false);
15
+ useEffect(() => {
16
+ setMounted(true);
17
+ try {
18
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
19
+ if (stored === 'light' || stored === 'dark')
20
+ setTheme(stored);
21
+ }
22
+ catch {
23
+ /* private mode — pidetään järjestelmän teema */
24
+ }
25
+ }, []);
26
+ function apply(next) {
27
+ setTheme(next);
28
+ const root = document.documentElement;
29
+ if (next === 'system')
30
+ root.removeAttribute('data-theme');
31
+ else
32
+ root.setAttribute('data-theme', next);
33
+ try {
34
+ if (next === 'system')
35
+ localStorage.removeItem(THEME_STORAGE_KEY);
36
+ else
37
+ localStorage.setItem(THEME_STORAGE_KEY, next);
38
+ }
39
+ catch {
40
+ /* valinta jää tälle sivulataukselle */
41
+ }
42
+ }
43
+ const order = ['system', 'light', 'dark'];
44
+ const label = { system: labels.system, light: labels.light, dark: labels.dark }[theme];
45
+ return (_jsx("button", { type: "button", className: "meta meta--s toggle", onClick: () => apply(order[(order.indexOf(theme) + 1) % order.length]), "aria-label": `${labels.theme}: ${label}`,
46
+ /* Ennen hydraatiota näytetään neutraali merkintä, jottei
47
+ palvelimen ja selaimen tila eroa toisistaan. */
48
+ suppressHydrationWarning: true, children: mounted ? label : labels.theme }));
49
+ }
package/index.d.ts CHANGED
@@ -6,3 +6,5 @@ export { Accordion, useAccordionGroup } from './Accordion.js';
6
6
  export type { AccordionProps } from './Accordion.js';
7
7
  export { Grid, Col } from './Grid.js';
8
8
  export { default as Reveal } from './Reveal.js';
9
+ export { default as ThemeScript, THEME_STORAGE_KEY } from './ThemeScript.js';
10
+ export { default as ThemeToggle } from './ThemeToggle.js';
package/index.js CHANGED
@@ -4,3 +4,5 @@ export { default as ListRow } from './ListRow.js';
4
4
  export { Accordion, useAccordionGroup } from './Accordion.js';
5
5
  export { Grid, Col } from './Grid.js';
6
6
  export { default as Reveal } from './Reveal.js';
7
+ export { default as ThemeScript, THEME_STORAGE_KEY } from './ThemeScript.js';
8
+ export { default as ThemeToggle } from './ThemeToggle.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pokela/components",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "React-komponentit: Pokela Design System. Generoitu tiedostoista components/, ei käsin ylläpidetty.",
5
5
  "license": "MIT",
6
6
  "author": "Veli-Matti Pokela",
@@ -25,7 +25,7 @@
25
25
  "LICENSE"
26
26
  ],
27
27
  "dependencies": {
28
- "@pokela/tokens": "^1.1.0"
28
+ "@pokela/tokens": "^2.0.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "react": ">=18"
@@ -20,11 +20,10 @@
20
20
  .detail__role { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-32); }
21
21
  @media (min-width: 900px) { .detail__role { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-40); } }
22
22
  .detail__role-col { display: flex; flex-direction: column; gap: var(--space-12); }
23
- .detail__role-title { margin: 0; font-size: var(--text-body-l); }
24
23
  .detail__role-note { color: var(--ink-faint); margin: 0; }
25
24
 
26
25
  .detail__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-8); }
27
- .detail__list li { font-size: var(--text-body-s); line-height: var(--lh-body-s); padding-left: var(--space-16); position: relative; }
26
+ .detail__list li { padding-left: var(--space-16); position: relative; }
28
27
  .detail__list li::before { content: '—'; position: absolute; left: 0; color: var(--ink-faint); }
29
28
  .detail__list--muted li { color: var(--ink-muted); }
30
29
 
@@ -33,7 +32,7 @@
33
32
  .detail__sections-body { display: flex; flex-direction: column; }
34
33
  .detail__section { padding-block: var(--space-24); border-top: var(--hairline) solid var(--line); }
35
34
  .detail__section:first-child { border-top: 0; padding-top: 0; }
36
- .detail__section-title { margin: 0 0 var(--space-8); font-size: var(--text-body-l); font-weight: var(--weight-500); }
35
+ .detail__section-title { margin: 0 0 var(--space-8); }
37
36
  .detail__section-body { margin: 0; color: var(--ink-muted); }
38
37
 
39
38
  .detail__media { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-24); }
package/styles/base.css CHANGED
@@ -53,6 +53,14 @@ img, svg, video { display: block; max-width: 100%; }
53
53
  .body-l { font-size: var(--text-body-l); line-height: var(--lh-body-l); margin: 0; }
54
54
  .body-s { font-size: var(--text-body-s); line-height: var(--lh-body-s); margin: 0; }
55
55
 
56
+ /* Pieni otsikko joka ei ole display: UI-fontti painolla 500. Tätä
57
+ yhdistelmää käytettiin neljässä paikassa ilman nimeä, joten Figmassa
58
+ ei voinut olla sille tyyliä — nimetön ei ole peilattavissa.
59
+
60
+ Fonttiperhe on mukana vaikka body antaa sen perintönä — <h3> ja <h4>
61
+ eivät peri sitä kaikissa selaimissa. */
62
+ .title { font-family: var(--font-ui); font-size: var(--text-body-l); line-height: var(--lh-body-l); font-weight: var(--weight-500); margin: 0; }
63
+
56
64
  /* Leipätekstin mitta. Ei koskaan yli 62 merkkiä. */
57
65
  .measure { max-width: var(--measure); }
58
66
 
@@ -70,7 +78,16 @@ img, svg, video { display: block; max-width: 100%; }
70
78
  color: var(--ink-faint);
71
79
  }
72
80
  .meta--s { font-size: var(--text-meta-s); line-height: var(--lh-meta-s); letter-spacing: var(--tr-meta-s); }
73
- .meta--ink { color: var(--ink); }
81
+
82
+ /* ---- värit -----------------------------------------------------------
83
+ Nimetty tokenin mukaan, ei paikan. Seitsemän komponenttiluokkaa teki
84
+ tämän omalla nimellään — seitsemän nimeä yhdelle tehtävälle.
85
+
86
+ Järjestys on merkitsevä: .meta asettaa oman värinsä, ja nämä ovat
87
+ samaa tarkkuutta, joten niiden on tultava sen jälkeen voittaakseen. */
88
+ .ink { color: var(--ink); }
89
+ .muted { color: var(--ink-muted); }
90
+ .faint { color: var(--ink-faint); }
74
91
 
75
92
  a { color: inherit; text-decoration: none; }
76
93
  a:hover { text-decoration: underline; text-underline-offset: var(--space-4); }
@@ -165,6 +182,27 @@ a:hover { text-decoration: underline; text-underline-offset: var(--space-4); }
165
182
  border-bottom: var(--hairline) solid currentColor;
166
183
  }
167
184
 
185
+ /* Hiljainen kytkin: teemanvaihto ja vastaavat. Ei .btn-perheen jäsen —
186
+ typografia tulee .meta:sta ja nappimaisuus riisutaan kokonaan, joten
187
+ se ei kilpaile sivun oikeiden toimintojen kanssa. Kosketusalue on
188
+ silti täysi: padding kasvattaa sen ja yhtä suuri negatiivinen margin
189
+ kumoaa vaikutuksen taittoon.
190
+
191
+ Ei ollut navigaatiokohtainen, vaikka asui aiemmin nav.css:ssä nimellä
192
+ .nav__toggle. Nimi esti ThemeTogglen viennin komponenttipakettiin,
193
+ koska komponentti olisi tarvinnut Navin tyylit. */
194
+ .toggle {
195
+ background: none;
196
+ border: 0;
197
+ padding: var(--space-12);
198
+ margin: calc(var(--space-12) * -1);
199
+ min-width: var(--tap-min);
200
+ min-height: var(--tap-min);
201
+ cursor: pointer;
202
+ transition: color var(--dur-fast) var(--ease-standard);
203
+ }
204
+ .toggle:hover { color: var(--ink); }
205
+
168
206
  /* Tekstinapilla ei ole pintaa jota kääntää, joten hover on sama liike
169
207
  kuin .case-linkillä: viiva paksunee. Padding kevenee saman verran
170
208
  kuin viiva kasvaa, jotta napin korkeus ei muutu eikä ympärillä oleva
@@ -64,18 +64,7 @@
64
64
  min-width: 0;
65
65
  }
66
66
 
67
- .list-row__title {
68
- font-family: var(--font-display);
69
- font-size: var(--text-display-s);
70
- line-height: var(--lh-display-s);
71
- letter-spacing: var(--tr-display-s);
72
- }
73
67
 
74
- .list-row__description {
75
- font-size: var(--text-body-s);
76
- line-height: var(--lh-body-s);
77
- color: var(--ink-muted);
78
- }
79
68
 
80
69
  /* ---- numerollinen -------------------------------------------------- */
81
70
 
@@ -103,11 +92,6 @@
103
92
  .list-row--m.list-row--numbered { grid-template-columns: var(--space-56) minmax(0, 1fr) 260px; }
104
93
  }
105
94
  .list-row--m .list-row__main { gap: var(--space-12); }
106
- .list-row--m .list-row__title {
107
- font-size: var(--text-display-m);
108
- line-height: var(--lh-display-m);
109
- letter-spacing: var(--tr-display-m);
110
- }
111
95
  .list-row--m .list-row__description { max-width: var(--measure); }
112
96
 
113
97
  /* ---- avautuva ------------------------------------------------------