@handijit/ui 1.0.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.
Files changed (174) hide show
  1. package/README.md +485 -571
  2. package/build/base.css +1 -1
  3. package/build/charts.cjs +1 -1
  4. package/build/charts.cjs.map +1 -1
  5. package/build/charts.css +1 -1
  6. package/build/charts.js +350 -349
  7. package/build/charts.js.map +1 -1
  8. package/build/chat.cjs +3 -3
  9. package/build/chat.cjs.map +1 -1
  10. package/build/chat.css +1 -1
  11. package/build/chat.js +344 -343
  12. package/build/chat.js.map +1 -1
  13. package/build/components/Accordion/style.css +1 -1
  14. package/build/components/Alert/style.css +1 -1
  15. package/build/components/Avatar/style.css +1 -1
  16. package/build/components/Badge/index.cjs +1 -1
  17. package/build/components/Badge/index.cjs.map +1 -1
  18. package/build/components/Badge/index.d.ts +8 -13
  19. package/build/components/Badge/index.js +1 -1
  20. package/build/components/Badge/index.js.map +1 -1
  21. package/build/components/Badge/style.css +1 -1
  22. package/build/components/BottomSheet/style.css +1 -1
  23. package/build/components/Breadcrumb/style.css +1 -1
  24. package/build/components/Button/index.cjs +1 -1
  25. package/build/components/Button/index.cjs.map +1 -1
  26. package/build/components/Button/index.js +2 -2
  27. package/build/components/Button/index.js.map +1 -1
  28. package/build/components/Button/style.css +1 -1
  29. package/build/components/Card/style.css +1 -1
  30. package/build/components/Carousel/style.css +1 -1
  31. package/build/components/Checkbox/style.css +1 -1
  32. package/build/components/Chip/style.css +1 -1
  33. package/build/components/ChipGroup/style.css +1 -1
  34. package/build/components/CodeBadge/style.css +1 -1
  35. package/build/components/Combobox/index.cjs +1 -1
  36. package/build/components/Combobox/index.cjs.map +1 -1
  37. package/build/components/Combobox/index.js +51 -51
  38. package/build/components/Combobox/index.js.map +1 -1
  39. package/build/components/Combobox/style.css +1 -1
  40. package/build/components/CommandPalette/style.css +1 -1
  41. package/build/components/CopyField/style.css +1 -1
  42. package/build/components/DataTable/style.css +1 -1
  43. package/build/components/DateField/index.d.ts +1 -1
  44. package/build/components/DateField/style.css +1 -1
  45. package/build/components/Directory/style.css +1 -1
  46. package/build/components/Disclosure/style.css +1 -1
  47. package/build/components/Divider/style.css +1 -1
  48. package/build/components/Drawer/style.css +1 -1
  49. package/build/components/EmptyState/style.css +1 -1
  50. package/build/components/Field/style.css +1 -1
  51. package/build/components/FileUpload/style.css +1 -1
  52. package/build/components/FilterBar/style.css +1 -1
  53. package/build/components/IconButton/index.cjs +1 -1
  54. package/build/components/IconButton/index.cjs.map +1 -1
  55. package/build/components/IconButton/index.d.ts +2 -2
  56. package/build/components/IconButton/index.js +1 -1
  57. package/build/components/IconButton/index.js.map +1 -1
  58. package/build/components/IconButton/style.css +1 -1
  59. package/build/components/Input/index.d.ts +1 -1
  60. package/build/components/Input/style.css +1 -1
  61. package/build/components/Kbd/style.css +1 -1
  62. package/build/components/Menu/style.css +1 -1
  63. package/build/components/Modal/style.css +1 -1
  64. package/build/components/PageHeader/style.css +1 -1
  65. package/build/components/Pagination/style.css +1 -1
  66. package/build/components/Panel/style.css +1 -1
  67. package/build/components/PasswordInput/style.css +1 -1
  68. package/build/components/Popover/style.css +1 -1
  69. package/build/components/Price/style.css +1 -1
  70. package/build/components/Progress/style.css +1 -1
  71. package/build/components/PromptDialog/style.css +1 -1
  72. package/build/components/QuantityStepper/style.css +1 -1
  73. package/build/components/Radio/style.css +1 -1
  74. package/build/components/RadioCard/style.css +1 -1
  75. package/build/components/RangeSlider/style.css +1 -1
  76. package/build/components/Rating/style.css +1 -1
  77. package/build/components/RatingInput/style.css +1 -1
  78. package/build/components/ScrollArea/style.css +1 -1
  79. package/build/components/SectionHeader/style.css +1 -1
  80. package/build/components/SegmentedControl/style.css +1 -1
  81. package/build/components/Select/index.cjs +1 -1
  82. package/build/components/Select/index.cjs.map +1 -1
  83. package/build/components/Select/index.js +28 -28
  84. package/build/components/Select/index.js.map +1 -1
  85. package/build/components/Select/style.css +1 -1
  86. package/build/components/Skeleton/style.css +1 -1
  87. package/build/components/Spinner/style.css +1 -1
  88. package/build/components/Stat/style.css +1 -1
  89. package/build/components/StatusDot/style.css +1 -1
  90. package/build/components/Steps/style.css +1 -1
  91. package/build/components/SummaryCard/style.css +1 -1
  92. package/build/components/Switch/index.d.ts +1 -1
  93. package/build/components/Switch/style.css +1 -1
  94. package/build/components/Table/style.css +1 -1
  95. package/build/components/TableCheckbox/style.css +1 -1
  96. package/build/components/Tabs/style.css +1 -1
  97. package/build/components/TagInput/style.css +1 -1
  98. package/build/components/TextLink/style.css +1 -1
  99. package/build/components/Textarea/style.css +1 -1
  100. package/build/components/Tile/style.css +1 -1
  101. package/build/components/Timeline/style.css +1 -1
  102. package/build/components/Toast/style.css +1 -1
  103. package/build/components/Tooltip/style.css +1 -1
  104. package/build/constants/breakpoint.constants.cjs +1 -1
  105. package/build/constants/breakpoint.constants.cjs.map +1 -1
  106. package/build/constants/breakpoint.constants.d.ts +13 -8
  107. package/build/constants/breakpoint.constants.js +7 -5
  108. package/build/constants/breakpoint.constants.js.map +1 -1
  109. package/build/enums/ui-size.enum.cjs.map +1 -1
  110. package/build/enums/ui-size.enum.d.ts +5 -2
  111. package/build/enums/ui-size.enum.js.map +1 -1
  112. package/build/enums/ui-variant.enum.cjs +1 -1
  113. package/build/enums/ui-variant.enum.cjs.map +1 -1
  114. package/build/enums/ui-variant.enum.d.ts +13 -13
  115. package/build/enums/ui-variant.enum.js +1 -1
  116. package/build/enums/ui-variant.enum.js.map +1 -1
  117. package/build/helpers/theme.helper.cjs +1 -23
  118. package/build/helpers/theme.helper.cjs.map +1 -1
  119. package/build/helpers/theme.helper.d.ts +13 -44
  120. package/build/helpers/theme.helper.js +31 -69
  121. package/build/helpers/theme.helper.js.map +1 -1
  122. package/build/hooks/useUiTheme.cjs +1 -1
  123. package/build/hooks/useUiTheme.cjs.map +1 -1
  124. package/build/hooks/useUiTheme.d.ts +4 -1
  125. package/build/hooks/useUiTheme.js +34 -28
  126. package/build/hooks/useUiTheme.js.map +1 -1
  127. package/build/hooks/useUiTokens.cjs +1 -1
  128. package/build/hooks/useUiTokens.cjs.map +1 -1
  129. package/build/hooks/useUiTokens.d.ts +8 -8
  130. package/build/hooks/useUiTokens.js +24 -27
  131. package/build/hooks/useUiTokens.js.map +1 -1
  132. package/build/index.cjs +1 -1
  133. package/build/index.d.ts +3 -5
  134. package/build/index.js +194 -202
  135. package/build/index.js.map +1 -1
  136. package/build/scss/_index.scss +4 -3
  137. package/build/scss/_mixins.scss +264 -146
  138. package/build/scss/_variables.scss +216 -24
  139. package/build/styles.css +67 -67
  140. package/build/theme/UiProvider.cjs +1 -1
  141. package/build/theme/UiProvider.cjs.map +1 -1
  142. package/build/theme/UiProvider.d.ts +10 -9
  143. package/build/theme/UiProvider.js +7 -25
  144. package/build/theme/UiProvider.js.map +1 -1
  145. package/build/theme/UiThemeScript.cjs +1 -1
  146. package/build/theme/UiThemeScript.cjs.map +1 -1
  147. package/build/theme/UiThemeScript.d.ts +13 -10
  148. package/build/theme/UiThemeScript.js +2 -2
  149. package/build/theme/UiThemeScript.js.map +1 -1
  150. package/build/theme/context.js +2 -2
  151. package/build/types/charts/core/palette.d.ts +6 -8
  152. package/build/types/chat/Legend/index.d.ts +3 -2
  153. package/build/types/theme.type.d.ts +6 -48
  154. package/build/types/types/theme.type.d.ts +6 -48
  155. package/package.json +10 -10
  156. package/build/scss/_colors.generated.scss +0 -119
  157. package/build/scss/_metrics.generated.scss +0 -49
  158. package/build/theme/initUi.cjs +0 -2
  159. package/build/theme/initUi.cjs.map +0 -1
  160. package/build/theme/initUi.d.ts +0 -7
  161. package/build/theme/initUi.js +0 -10
  162. package/build/theme/initUi.js.map +0 -1
  163. package/build/theme/palette.cjs +0 -2
  164. package/build/theme/palette.cjs.map +0 -1
  165. package/build/theme/palette.d.ts +0 -219
  166. package/build/theme/palette.js +0 -148
  167. package/build/theme/palette.js.map +0 -1
  168. package/build/theme/tokens.cjs +0 -2
  169. package/build/theme/tokens.cjs.map +0 -1
  170. package/build/theme/tokens.d.ts +0 -225
  171. package/build/theme/tokens.js +0 -384
  172. package/build/theme/tokens.js.map +0 -1
  173. package/build/types/theme/palette.d.ts +0 -219
  174. package/build/types/theme/tokens.d.ts +0 -225
package/README.md CHANGED
@@ -1,32 +1,46 @@
1
1
  # @handijit/ui
2
2
 
3
- Erişilebilir, tema güdümlü React bileşen kütüphanesi. SCSS Modules üzerine
4
- kurulu; tek çalışma zamanı bağımlılığı ikon seti (`react-bootstrap-icons`).
5
-
6
- - **Tek CSS dosyası, iki tema.** Açık ve koyu hazır gelir, her token dışarıdan
7
- ezilebilir.
8
- - **Yönlendirici enjekte edilir.** `next/link`, `react-router`, ham `<a>` —
9
- karar tüketicinin.
10
- - **Metin dışarıdan, ama BİR KEZ.** Kütüphane hiçbir dilde metin uydurmaz;
11
- kullanıcıya görünen dizeler sağlayıcıda tek yerde tanımlanır.
12
- - **Yerel öğeler korunur.** `<dialog>`, `<input type="range">`,
13
- `<input type="radio">` — odak tuzağı, klavye gezinmesi ve ekran okuyucu
14
- duyurusu tarayıcıdan gelir.
15
- - **Üç alt giriş, ana pakete sıfır bayt.** `@handijit/ui/chat` (sohbet ve
16
- araç arayüzleri), `@handijit/ui/charts` (bağımlılıksız SVG grafikleri),
17
- `@handijit/ui/scss` (token değişkenleri ve mixin'ler). Kullanmayan uygulama
18
- onları indirmez.
3
+ Accessible React components for Han Dijit applications: the site, the staff
4
+ panel and the desktop programs. Built on SCSS Modules and drawn with the tokens
5
+ of [`@handijit/design`](https://www.npmjs.com/package/@handijit/design).
6
+
7
+ - **One design source.** Colours, type, radii, spacing, motion and layers come
8
+ from `@handijit/design` (`--hd-*`). This package holds no palette of its own.
9
+ - **Light and dark.** Both themes ship; the choice rides on `data-theme`, the
10
+ contract `@handijit/design` defines.
11
+ - **The router is injected.** `next/link`, `react-router`, a plain `<a>`: the
12
+ application decides.
13
+ - **Text comes from outside, once.** The library invents no string in any
14
+ language; user-visible strings are defined in one place, on the provider.
15
+ - **Native elements are kept.** `<dialog>`, `<input type="range">`,
16
+ `<input type="radio">`: focus trapping, keyboard handling and screen reader
17
+ announcements come from the browser.
18
+ - **Three sub-entries, zero bytes in the main bundle.** `@handijit/ui/chat`
19
+ (chat and tool interfaces), `@handijit/ui/charts` (dependency-free SVG
20
+ charts), `@handijit/ui/scss` (token variables and mixins).
19
21
 
20
22
  ```bash
21
- npm install @handijit/ui @handijit/utils react-bootstrap-icons
23
+ npm install @handijit/ui @handijit/design @handijit/utils react-bootstrap-icons
22
24
  ```
23
25
 
24
- `react`, `react-dom`, `@handijit/utils` ve `react-bootstrap-icons`
25
- **peer** bağımlılıktır.
26
+ `react`, `react-dom`, `@handijit/design` (`^1.0.0`), `@handijit/utils`
27
+ (`^2.0.0`) and `react-bootstrap-icons` are **peer** dependencies. The design
28
+ package is a peer on purpose: an application has exactly one copy of the tokens,
29
+ and the site, the panel and the components all read the same custom properties.
30
+
31
+ Coming from 1.x? The migration is in [`CHANGELOG.md`](./CHANGELOG.md) under
32
+ 2.0.0, old name → new name for every token, variable, prop, variant and export.
26
33
 
27
34
  ---
28
35
 
29
- ## Hızlı başlangıç
36
+ ## Quick start
37
+
38
+ ```tsx
39
+ // Once, at the root of the application. The order matters.
40
+ import '@handijit/design/tokens.css'; // the --hd-* custom properties, both themes
41
+ import '@handijit/design/fonts.css'; // optional: Archivo, Inter, IBM Plex Mono
42
+ import '@handijit/ui/styles.css';
43
+ ```
30
44
 
31
45
  ```tsx
32
46
  import { Button, Field, Input, UIVariant } from '@handijit/ui';
@@ -40,210 +54,208 @@ import { Button, Field, Input, UIVariant } from '@handijit/ui';
40
54
  </Button>;
41
55
  ```
42
56
 
43
- Sağlayıcı **zorunlu değil**: paket kurulup hiçbir yapılandırma yapılmadan
44
- çalışır. Bağlantılar ham `<a>` olur, tema varsayılanlarda kalır.
57
+ The provider is **optional**: the package works with no configuration. Links
58
+ are plain `<a>` elements and labels are whatever each call passes.
45
59
 
46
- **CSS tek dosya olarak alınır.** Uygulamanın kökünde bir kez:
60
+ `@handijit/ui/styles.css` carries **no tokens**. Without
61
+ `@handijit/design/tokens.css` every colour resolves to nothing. `fonts.css` is
62
+ optional: load the three families your own way (`next/font`, self-hosted
63
+ `@font-face`) if you prefer, the tokens only name them.
47
64
 
48
- ```tsx
49
- import '@handijit/ui/styles.css';
50
- ```
65
+ ### A subset of the CSS
51
66
 
52
- Alt küme isteyen tüketici `base.css` (token'lar, kutu modeli, `@keyframes`) ile
53
- kullandığı bileşenlerin `components/<Ad>/style.css` dosyalarını **kendi
54
- derlemesinde tek dosyaya** birleştirir:
67
+ An application that wants less imports `base.css` (box model, `@keyframes`,
68
+ helpers) and the `components/<Name>/style.css` files of the components it uses,
69
+ and lets its own build join them:
55
70
 
56
71
  ```scss
57
- // styles/ui.scss — Sass düz CSS dosyalarını içine alır.
72
+ // styles/ui.scss: Sass inlines plain CSS files.
58
73
  @use '@handijit/ui/base.css';
59
74
  @use '@handijit/ui/components/Button/style.css';
60
75
  @use '@handijit/ui/components/Chip/style.css';
61
76
  ```
62
77
 
63
- JS modülleri CSS içe aktarmaz; bu bilinçli: Turbopack'te her istemci sınırının
64
- CSS'i ayrı dosyaya düşüyor ve render-blocking dosya sayısı LCP'yi bayttan çok
65
- etkiliyordu (`CHANGELOG.md` → 1.0.0).
78
+ The JavaScript modules do not import CSS. That is deliberate: under Turbopack
79
+ every client boundary's CSS landed in its own file, and the number of
80
+ render-blocking files hurt LCP more than the bytes did.
66
81
 
67
- Her bileşenin alt yolu var — `@handijit/ui/button`, `/chip-group`,
68
- `/combobox`… — ama cephe (`@handijit/ui`) saf yeniden dışa aktarım olduğu
69
- için paketleyici ikisinden de aynı dosyaya iner.
82
+ Every component also has a subpath (`@handijit/ui/button`, `/chip-group`,
83
+ `/combobox`, …). The main entry is a pure re-export, so a bundler reaches the
84
+ same file either way.
85
+
86
+ The sub-entries have their own stylesheets, imported **after** `styles.css`:
87
+ `@handijit/ui/chat.css`, `@handijit/ui/charts.css`.
70
88
 
71
89
  ---
72
90
 
73
- ## Tema
91
+ ## Theme
74
92
 
75
- ### Tema seçimi `<html>` üzerinde taşınır
93
+ ### The choice rides on `<html data-theme>`
76
94
 
77
95
  ```html
78
- <html data-ui-theme="dark">
96
+ <html data-theme="dark"></html>
79
97
  ```
80
98
 
81
- Öznitelik yoksa `prefers-color-scheme` yedeği devreye girer — ama kullanıcının
82
- **açık bir seçimi** varsa sistem tercihi onu ezmez.
99
+ This is the contract of `@handijit/design`, not a setting of this package:
100
+ `data-theme="light"` or `"dark"` on the root element picks the theme, and
101
+ without the attribute `prefers-color-scheme` decides. An explicit choice is
102
+ never overridden by the system setting. The attribute also works on any
103
+ element, to draw one region in the other theme.
83
104
 
84
- ### İlk boyamadan önce yazın — `UiThemeScript`
105
+ ### Write it before the first paint: `UiThemeScript`
85
106
 
86
107
  ```tsx
87
- // app/layout.tsx — sunucu düzeni, <head> içine.
108
+ // app/layout.tsx, a server layout, inside <head>.
88
109
  import { UiThemeScript } from '@handijit/ui';
89
110
 
90
111
  <head>
91
- <UiThemeScript storageKey="theme" />
112
+ <UiThemeScript nonce={nonce} />
92
113
  </head>;
93
114
  ```
94
115
 
95
- Kayıtlı açık seçimi (`light` / `dark`) `<html data-ui-theme>`e yazar; kayıt
96
- yoksa sistem tercihi izlenir. Satır içi betik HTML ayrıştırılırken çalışır.
116
+ It renders the boot script of `@handijit/design` (`THEME_BOOT_SCRIPT`) as an
117
+ inline `<script>`. The script reads `localStorage["hd-theme"]` (`light` or
118
+ `dark`; anything else means "follow the system"), writes `data-theme` while the
119
+ HTML is still being parsed, and follows the system setting when it changes.
120
+ Without it a user who chose dark sees one light frame.
97
121
 
98
- Bu iş sağlayıcıya bırakılamaz: sağlayıcı ağaç monte olduktan sonra çalışıyor ve
99
- koyu tema seçmiş kullanıcı o ana kadar **bir kare beyaz ekran** görüyor.
100
- ⚠ Bir **bileşen**, fonksiyon değil: paket `'use client'` taşıdığı için dışa
101
- verilen düz bir fonksiyon Next'in sunucu düzeninde çağrılamıyor.
122
+ It is a component and not a function because a plain function exported from a
123
+ package with `'use client'` files cannot be called from a Next.js server layout.
102
124
 
103
- ### Token'ları ezmek
125
+ ### Content Security Policy
104
126
 
105
- Yalnızca değiştirdiğiniz token'ı verin; gerisi varsayılanında kalır.
127
+ The boot script is the **only** element this library injects. It injects no
128
+ `<style>` element at all.
106
129
 
107
- ```tsx
108
- import { UiProvider } from '@handijit/ui';
109
-
110
- const THEME = {
111
- light: { link: '#0a58ca', 'ring-color': '#0d6efd' },
112
- dark: { link: '#6ea8fe' },
113
- fonts: { heading: 'Archivo, sans-serif', body: 'Inter, sans-serif' },
114
- // Ölçüler tema başına DEĞİL: bir markanın yuvarlaklığı açık temada
115
- // 12 px, koyu temada 8 px olmaz.
116
- metrics: { 'radius-md': '2px', 'radius-lg': '4px', 'duration-normal': '120ms' },
117
- };
130
+ | Policy style | What to do |
131
+ | ----------------- | --------------------------------------------------------------------------- |
132
+ | Nonce per request | `<UiThemeScript nonce={nonce} />` |
133
+ | Static hash | add `'${THEME_BOOT_SCRIPT_HASH}'` to `script-src`; the page stays cacheable |
134
+
135
+ ```ts
136
+ import { THEME_BOOT_SCRIPT_HASH } from '@handijit/ui'; // re-exported from @handijit/design
118
137
 
119
- <UiProvider theme={THEME} linkComponent={NextLink}>
120
- <App />
121
- </UiProvider>;
138
+ const policy = `script-src 'self' '${THEME_BOOT_SCRIPT_HASH}'`;
122
139
  ```
123
140
 
124
- Sağlayıcı ezmeyi çocuklarından önce bir `<style>` olarak **çizer**: sunucu
125
- çıktısında da vardır, ilk boyamada uygulanır; hidrasyondan sonra hiçbir renk
126
- değişmez. Bu yüzden sağlayıcı `<body>` içinde durur, `<html>`i saramaz.
141
+ `THEME_ATTRIBUTE`, `THEME_STORAGE_KEY`, `THEME_BOOT_SCRIPT` and
142
+ `THEME_BOOT_SCRIPT_HASH` are re-exports, not copies: the digest always matches
143
+ the script the installed design package ships.
127
144
 
128
- Ezme `@layer ui` içindedir: aynı token'ı kendi CSS'inizde ezerseniz
129
- (`html:has(.panel) { --ui-danger-solid: #dc2626 }`) sizinki özgüllüğe ve
130
- sıraya bakılmaksızın kazanır. Açık temaya verilen değer koyu temaya sızmaz;
131
- koyu temada da istiyorsanız `dark`a da yazın.
145
+ Some components position or size themselves with inline `style` attributes
146
+ (popover coordinates, progress width, virtual list offsets). A nonce cannot
147
+ cover an attribute; a policy that restricts inline styles needs
148
+ `style-src-attr 'unsafe-inline'`.
132
149
 
133
- React'in dışından (Storybook'tan, stil sayfalarından sonra çalışan bir
134
- betikten) aynı işi `initUi({ theme })` yapar; ezmeyi `<head>`in sonuna
135
- yazar. ⚠ Stil sayfalarınızdan **önce** çağırmayın: `@layer ui` belgede ilk
136
- kez orada görülürse katman sırası oradan kurulur ve sonra gelen
137
- `@layer reset, @handijit/ui;` bildiriminiz `reset`i kütüphanenin üstüne koyar.
150
+ ### Changing the theme
138
151
 
139
- **Kütüphane font YÜKLEMEZ.** `fonts` yalnızca `font-family` dizesi; yüklemeyi
140
- (`next/font`, `@font-face`, CDN) siz yaparsınız. Bir UI paketinin ağdan font
141
- çekmesi, sizin ölçemediğiniz bir istek demek.
152
+ ```tsx
153
+ const { scheme, preference, setScheme, toggle, isReady } = useUiTheme();
154
+ ```
142
155
 
143
- #### Ezilebilen ölçüler
156
+ | Field | |
157
+ | ------------ | ---------------------------------------------------------------------- |
158
+ | `scheme` | the **resolved** theme, the one on screen (`light` \| `dark`) |
159
+ | `preference` | the user's **choice** (`light` \| `dark` \| `system`) |
160
+ | `setScheme` | stores the choice and applies it; `'system'` removes the stored choice |
161
+ | `toggle` | flips between light and dark |
162
+ | `isReady` | `false` on the server and on the first client frame |
144
163
 
145
- `radius-*` · `space-0…9` · `font-size-2xs…4xl` · `leading-*` · `icon-xs…xl` ·
146
- `duration-*` · `ease-*` — tam liste `METRIC_TOKENS` içinde ve dışa veriliyor.
164
+ A three-state switch (Light / Dark / System) reads `preference`; if it read
165
+ `scheme`, "System" would show "Dark" as selected.
147
166
 
148
- Ezilemeyenler ve nedenleri: **kırılma noktaları** (`@media (max-width: var(--x))`
149
- geçersiz CSS), **katman (`z-*`)** (yığılma sırası, marka kararı değil) ve
150
- **dokunma hedefi** (44 px; yoğun kipte de küçülmemeli — WCAG 2.5.8).
167
+ `setScheme` writes where the boot script reads: `localStorage["hd-theme"]`
168
+ holds `light` or `dark`, "system" removes the key, and the attribute always
169
+ carries the resolved theme. A choice made through the hook therefore survives a
170
+ reload without a flash. Wait for `isReady` before drawing the switch: the
171
+ server does not know the choice, and a guessed switch mismatched on hydration
172
+ even when the guess was right.
151
173
 
152
- ### Bilgi yoğunluğu
174
+ ### Brand and product overrides
153
175
 
154
- ```html
155
- <html data-ui-density="compact"></html>
156
- ```
176
+ There is no theme prop. A value is changed where it is defined, as a custom
177
+ property, in the application's own stylesheet:
178
+
179
+ ```css
180
+ :root {
181
+ --hd-radius-md: 10px;
182
+ }
157
183
 
158
- Satır yüksekliği, dolgu ve punto bir kademe iner. Vitrin bir ekranda 8 satır,
159
- operasyon paneli 80 satır gösteriyor; ikisini tek ölçekle karşılamanın yolu yok.
160
- Kararı `initUi({ density: 'compact' })` da verebilir.
184
+ /* The same selector the design package uses, or the override loses on <html>. */
185
+ :root[data-theme='dark'] {
186
+ --hd-bg-page: #0b0d12;
187
+ }
188
+ ```
161
189
 
162
- **Yarıçap değişmez** (yuvarlaklık yoğunluğun değil markanın işi) ve **dokunma
163
- hedefi küçülmez**: görsel kutu daralır, `tap-target` örtüsü 44 px kalır.
190
+ Change the tokens of `@handijit/design` sparingly: the contrast of every text
191
+ and focus pair is measured against the published values.
164
192
 
165
- ### CSS'imizi ezmek — `@layer ui`
193
+ ### Overriding our CSS: `@layer ui`
166
194
 
167
- Kütüphane CSS'inin tamamı `@layer ui` içinde — tema ezmeleri
168
- (`UiProvider theme`, `initUi`) dahil. Katmanlı bir kural, katmansız
169
- bir kurala **her zaman yenilir** — özgüllüğe ve sıraya bakılmaksızın:
195
+ All of the library's CSS is inside `@layer ui`. A layered rule **always loses**
196
+ to an unlayered one, whatever the specificity or source order:
170
197
 
171
198
  ```css
172
- /* Bu kadarı yeter; `!important` ya da `.app .card .card` gerekmez. */
199
+ /* This is enough; no `!important`, no `.app .card .card`. */
173
200
  .my-card {
174
201
  padding: 0;
175
202
  }
176
203
  ```
177
204
 
178
- Katman olmadan sizin sınıfınız ile bizimki aynı özgüllükte (0,1,0) yarışıyordu
179
- ve kazananı **kaynak sırası** belirliyordu; o sıra da bundler'ınızın elinde —
180
- geliştirme ile üretim aynı olmak zorunda değil.
205
+ Without the layer your class and ours competed at the same specificity and the
206
+ winner was decided by source order, which is in your bundler's hands and need
207
+ not be the same in development and production.
208
+
209
+ ### Component variables
210
+
211
+ Besides the design tokens, a few components read custom properties of their own
212
+ (`var(--ui-…, default)`) so an application can restyle one component from
213
+ `:root` without a class selector:
214
+
215
+ | Component | Variables |
216
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
217
+ | focus ring | `--ui-ring-width` (2px) · `--ui-ring-offset` (2px; `-2px` pulls it inside) |
218
+ | inputs | `--ui-placeholder` (`text-3`) |
219
+ | modal scrim | `--ui-scrim-blur` |
220
+ | `Button` | `--ui-button-radius` · `-weight` · `-tracking` · `-wrap` (`nowrap`) |
221
+ | `Chip` | `--ui-chip-bg` · `-line` · `-fg` · `-radius` · `-weight` · `-selected-bg` · `-selected-line` · `-selected-fg` · `-selected-weight` |
222
+ | `Badge` | `--ui-badge-font` · `-weight` · `-tracking` · `-transform` · `-radius` · `-bg` · `-line` · `-fg` |
223
+ | `Stat` | `--ui-stat-label-font/-size/-weight/-tracking/-transform` · `-value-font/-weight/-tracking` · `-unit-font/-size/-tracking/-transform` |
224
+ | `Tabs` | `--ui-tab-weight` |
225
+ | `RadioCard` | `--ui-radio-card-line` · `-radius` |
226
+ | sticky parts | `--ui-header-height` (see below) |
181
227
 
182
- ### Temayı değiştirmek
183
-
184
- ```tsx
185
- const { scheme, preference, setScheme, toggle, isReady } = useUiTheme();
186
-
187
- useEffect(() => {
188
- if (isReady) localStorage.setItem('theme', preference);
189
- }, [preference, isReady]);
228
+ ```css
229
+ :root {
230
+ --ui-ring-offset: -2px;
231
+ --ui-button-weight: 500;
232
+ --ui-chip-radius: var(--hd-radius-pill);
233
+ --ui-badge-font: var(--hd-font-mono);
234
+ --ui-badge-transform: uppercase;
235
+ }
190
236
  ```
191
237
 
192
- | Alan | |
193
- | --- | --- |
194
- | `scheme` | **ÇÖZÜLMÜŞ** tema — ekranda çizili olan (`light` \| `dark`) |
195
- | `preference` | kullanıcının **SEÇİMİ** (`light` \| `dark` \| `system`) |
196
- | `setScheme` | `'system'` verildiğinde açık seçim SİLİNİR |
197
-
198
- Üç durumlu bir anahtar (Açık / Koyu / Sistem) `preference` okur; `scheme`
199
- okusaydı "Sistem" seçiliyken düğme "Koyu"yu işaretli gösterirdi. `system` bir
200
- değer değil **değerin yokluğu**: öznitelik silinir ve
201
- `:not([data-ui-theme])` sorgusu devreye girer.
238
+ `--ui-*` is the prefix of component-private variables only. Every global value
239
+ (colour, size, radius, duration, layer) is `--hd-*`.
202
240
 
203
- Kalıcılık **kancanın işi değil**: seçimi `localStorage`a mı, sunucuda
204
- okunabilsin diye bir çereze mi, kullanıcı profiline mi yazacağınız sizin
205
- kararınız. Kütüphane `localStorage`a yazsaydı sunucu tarafı onu okuyamayacağı
206
- için ilk boyama yine yanlış temada olurdu.
241
+ ### Reading tokens in JavaScript
207
242
 
208
- `isReady`: sunucu çıktısında ve hidrasyondan önceki ilk karede `false`. Tema
209
- anahtarını çizmeden önce bekleyin — sunucu hangi temanın seçili olduğunu
210
- bilmiyor ve bir tahminle çizilen anahtar, doğru tahmin edilse bile hidrasyonda
211
- uyuşmazlık üretiyordu.
212
-
213
- ### Token katmanları
214
-
215
- ```
216
- theme/palette.ts → ham hex (TEK hex kaynağı)
217
- theme/tokens.ts → anlamsal token → değer
218
- LIGHT_THEME / DARK_THEME (renk, temaya bağlı)
219
- METRIC_TOKENS (ölçü, temadan bağımsız)
220
- COMPACT_DENSITY (yoğun kipin ezdikleri)
221
- styles/*.generated → yukarıdakinden ÜRETİLİR (npm run tokens)
243
+ ```tsx
244
+ const colors = useUiTokens(['border', 'chart-1']); // { border: '#…', 'chart-1': '#…' }
222
245
  ```
223
246
 
224
- SCSS ve TypeScript tarafı elle tutulsaydı bir tarafta var olup diğerinde
225
- olmayan bir token çıkardı — ve ayrışma sessiz: bileşen `var(--ui-yok)`
226
- okuyup rengini kaybediyor, derleme yeşil dönüyor. Nöbetçi
227
- `theme/__tests__/tokens.contract.test.ts`.
228
-
229
- **Ölçüler CSS değişkeni olduğu için SCSS aritmetiği çalışmaz.**
230
- `$space-8 - $space-2` derlenmez — ve Sass hata da vermez: ifadeyi olduğu gibi
231
- geçirip tarayıcıya `var(--a)-var(--b)` yazar, tarayıcı bütün bildirimi atar.
232
- Hesap `calc()` içinde yazılır, negatif değer dahil:
233
-
234
- ```scss
235
- padding-inline-start: calc(#{$space-8} - #{$space-2});
236
- margin-inline: calc(-1 * #{$space-2});
237
- ```
247
+ `useUiTokens` returns the resolved values of design tokens and follows theme
248
+ changes; it is for canvas and third-party charts that cannot read `var()`. On
249
+ the server and the first frame it returns `currentColor`. The names are
250
+ `TokenName` of `@handijit/design`.
238
251
 
239
252
  ---
240
253
 
241
- ## Metinler
254
+ ## Labels
242
255
 
243
- Kütüphane "Kapat" yazamaz: hangi dilde, hangi üslupta, hangi terimle
244
- yazacağını bilmez. Ama aynı dizeyi yüz çağrı yerine dağıtmak da doğru değil —
245
- biri değiştiğinde doksan dokuzu eski kalıyor. Metinler **bir kez**, sağlayıcıda
246
- verilir:
256
+ The library cannot write "Close": it does not know the language, the tone or
257
+ the term. Spreading the same string over a hundred call sites is not right
258
+ either. Labels are given **once**, on the provider:
247
259
 
248
260
  ```tsx
249
261
  <UiProvider
@@ -283,37 +295,28 @@ verilir:
283
295
  >
284
296
  ```
285
297
 
286
- **Çözümleme sırası: prop → config → geliştirme uyarısı.** Bir prop verilmişse o
287
- kazanır; bağlama göre farklı olması gereken yerlerde (`closeLabel="Daha sonra"`)
288
- hâlâ prop geçilir.
298
+ **Resolution order: prop → provider → development warning.** A prop wins when
299
+ given; pass one where the wording depends on the context
300
+ (`closeLabel="Daha sonra"`).
289
301
 
290
- ### Config'e girmeyenler
302
+ Text that belongs to **one element** never goes on the provider, because it
303
+ differs at every call site: `Modal.title`, `ConfirmDialog.confirmLabel` ("Sil":
304
+ it must repeat the action; "OK" does not say what is being confirmed),
305
+ `IconButton.label`, `Select.label`, `Combobox.labels.placeholder`,
306
+ `TableCheckbox.label`, `ChipGroup.label`, `RatingInput.label`.
291
307
 
292
- Öğeye **özgü** hiçbir metin config'e girmez — her çağrı yerinde farklı olduğu
293
- için bir uygulama düzeyinde varsayılanı olamaz:
294
-
295
- `Modal.title` · `ConfirmDialog.confirmLabel` ("Sil" — eylemi tekrarlamak
296
- zorunda; "Tamam" kullanıcıya neyi onayladığını söylemez) · `IconButton.label` ·
297
- `Select.label` · `Combobox.labels.placeholder` · `TableCheckbox.label` ·
298
- `ChipGroup.label` · `RatingInput.label`
299
-
300
- ### Eksik metin nasıl görünür
301
-
302
- TypeScript sağlayıcının çalışma zamanında ne taşıdığını göremiyor, o yüzden
303
- prop'lar isteğe bağlı. Ne prop ne config varsa öğe adsız kalır ve bu geliştirme
304
- kipinde konsola düşer:
308
+ TypeScript cannot see what the provider carries at run time, so the props are
309
+ optional. With neither a prop nor a provider value the element stays unnamed
310
+ and, in development, the console says so once per key:
305
311
 
306
312
  ```
307
313
  [@handijit/ui] Eksik metin: Modal.closeLabel. Prop olarak geçin ya da
308
314
  <UiProvider labels={…}> içinde bir kez tanımlayın.
309
315
  ```
310
316
 
311
- Uyarı anahtar başına bir kez verilir; yirmi satırlık bir liste konsolu
312
- doldurmaz.
313
-
314
317
  ---
315
318
 
316
- ## Yönlendirici
319
+ ## Router
317
320
 
318
321
  ```tsx
319
322
  import NextLink from 'next/link';
@@ -321,41 +324,31 @@ import NextLink from 'next/link';
321
324
  <UiProvider linkComponent={NextLink}>…</UiProvider>;
322
325
  ```
323
326
 
324
- Bir bileşen kütüphanesi kendi yönlendiricisini **seçemez**: `next/link` import
325
- etmek paketi Next'e bağlar, `react-router` import etmek başka bir uygulamayı
326
- kırar.
327
-
328
- Yönlendiriciye özgü props `linkProps` ile geçer:
327
+ A component library cannot choose the router: importing `next/link` ties the
328
+ package to Next.js, importing `react-router` breaks another application.
329
+ Router-specific props travel through `linkProps`:
329
330
 
330
331
  ```tsx
331
332
  <Pagination buildHref={page => `?sayfa=${page}`} linkProps={{ scroll: false }} … />
332
333
  ```
333
334
 
334
- ---
335
-
336
- ## Sayfalamada başa dönme
337
-
338
- Sayfa değiştiren tıklamadan sonra pencereyi başa almak bir **prop**tur; çağıran
339
- tarafta kaydırma kodu yazılmaz:
335
+ ### Back to the top after a page change
340
336
 
341
337
  ```tsx
342
338
  <Pagination page={page} totalPages={totalPages} scrollTo="top" … />
343
339
  ```
344
340
 
345
- İki kipte de çalışır (`buildHref` ve `onPageChange`). Kaydırma anidir ve üç
346
- durumda **yapılmaz**: etkin sayfaya basıldığında, yeni sekmede açan tıklamada
347
- (Ctrl/Cmd/orta tuş) ve prop verilmediğinde.
341
+ It works in both modes (`buildHref` and `onPageChange`). The scroll is instant
342
+ and does **not** happen when the active page is pressed, when the click opens a
343
+ new tab (Ctrl/Cmd/middle button), or when the prop is absent. Do not pass it
344
+ for a list inside a page (reviews in a tab): it throws the reader to the top.
348
345
 
349
- ⚠ Sayfa içindeki bir listenin sayfalamasında (sekme içi değerlendirme listesi
350
- gibi) **verilmez** — kullanıcıyı okuduğu bölümden sayfanın tepesine atar.
346
+ ### Sticky header
351
347
 
352
- ---
353
-
354
- ## Yapışkan başlık
355
-
356
- Sayfa kaydırmasına yapışan bileşenler (`DirectoryGroup`, `DirectoryJump`,
357
- kapaklı `DataTable`) offset'i `--ui-header-height`ten okur ve **varsayılanı
358
- 0**. Kendi yapışkan bandınız varsa gerçek ölçüyü yazın:
348
+ Components that stick while the page scrolls (`DirectoryGroup`,
349
+ `DirectoryJump`, a `DataTable` with a caption) read their offset from
350
+ `--ui-header-height`, **0 by default**. If the application has a sticky band,
351
+ write its real height:
359
352
 
360
353
  ```tsx
361
354
  useEffect(() => {
@@ -370,126 +363,141 @@ useEffect(() => {
370
363
  }, []);
371
364
  ```
372
365
 
373
- Sabit bir sayı yazmayın: bant mobilde arama kutusu kendi satırına düştüğünde
374
- büyür, bir duyuru şeridi çıkıp kaybolduğunda değişir.
366
+ Do not write a constant: the band grows when the search box wraps on a phone
367
+ and changes when an announcement strip appears.
375
368
 
376
369
  ---
377
370
 
378
- ## Bileşen kataloğu
379
-
380
- | Grup | Bileşenler |
381
- | --- | --- |
382
- | Eylem | `Button` · `IconButton` · `TextLink` · `Tooltip` · `Menu` · `ContextMenu` · `Popover` |
383
- | Seçim | `Chip` · `ChipGroup` · `SegmentedControl` · `Select` · `Combobox` · `Checkbox` · `Switch` · `Radio` · `RadioCard` · `RangeSlider` · `RatingInput` · `QuantityStepper` · `TableCheckbox` |
384
- | Girdi | `Input` · `PasswordInput` · `Textarea` · `TagInput` · `DateField` · `FileUpload` · `Field` |
385
- | Yüzey | `Card` (+ `CardMedia`, `CardOverlay`, `CardBody`, `CardFooter`) · `SummaryCard` · `Panel` (+ `PanelForm`, `PanelRow`) · `PageHeader` · `SectionHeader` · `Tile` · `Directory` (+ `DirectoryGroup`, `DirectoryRow`, `DirectoryJump`) · `Carousel` · `ScrollArea` · `Disclosure` · `Accordion` · `Tabs` |
386
- | Geri bild. | `Alert` · `Badge` · `StatusDot` · `CodeBadge` · `Kbd` · `EmptyState` · `Skeleton` (+ `SkeletonCard`, `SkeletonRows`, `SkeletonTable`) · `Spinner` · `Progress` · `Steps` · `Divider` · `Avatar` · `Rating` · `Price` · `Stat` · `CopyField` · `Breadcrumb` · `ToastHub` (+ `ToastPortal`) |
387
- | Kipsel | `Modal` · `Drawer` · `BottomSheet` · `ConfirmDialog` · `PromptDialog` · `CommandPalette` |
388
- | Veri | `Table` (+ `TableScroller`) · `DataTable` (+ `DataTableRow`) · `FilterBar` (+ `FilterBarField`) · `Pagination` · `Timeline` |
389
- | Tema ve kancalar | `UiProvider` · `UiThemeScript` · `initUi` · `useUi` · `useUiTheme` · `useUiTokens` · `useAnnounce` · `useStickToBottom` · `usePositioning` · `useVirtualList` · `useListboxNavigation` · `hasOpenSurface` |
390
- | Sohbet — `/chat` | `ChatThread` · `ChatMessage` · `PromptInput` · `PromptSuggestions` · `Prose` · `CodeBlock` · `ReasoningBlock` (+ `ReasoningText`) · `ToolCall` (+ `ToolCallSection`) · `TerminalOutput` · `SourceList` · `Citation` · `MessageActions` · `AppBar` · `ActionList` (+ `ActionListItem`) · `SegmentBar` · `Legend` · `DescriptionList` |
391
- | Grafik — `/charts` | `LineChart` · `AreaChart` · `BarChart` · `ComposedChart` · `PieChart` · `ScatterPlot` · `Heatmap` · `BoxPlot` · `Histogram` · `RadarChart` |
392
-
393
- Alt girişlerin stilleri ayrı dosyada ve `styles.css`ten **sonra** içe aktarılır:
394
- `@handijit/ui/chat.css`, `@handijit/ui/charts.css`.
371
+ ## Component catalogue
372
+
373
+ | Group | Components |
374
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
375
+ | Action | `Button` · `IconButton` · `TextLink` · `Tooltip` · `Menu` · `ContextMenu` · `Popover` |
376
+ | Choice | `Chip` · `ChipGroup` · `SegmentedControl` · `Select` · `Combobox` · `Checkbox` · `Switch` · `Radio` · `RadioCard` · `RangeSlider` · `RatingInput` · `QuantityStepper` · `TableCheckbox` |
377
+ | Input | `Input` · `PasswordInput` · `Textarea` · `TagInput` · `DateField` · `FileUpload` · `Field` |
378
+ | Surface | `Card` (+ `CardMedia`, `CardOverlay`, `CardBody`, `CardFooter`) · `SummaryCard` · `Panel` (+ `PanelForm`, `PanelRow`) · `PageHeader` · `SectionHeader` · `Tile` · `Directory` (+ `DirectoryGroup`, `DirectoryRow`, `DirectoryJump`) · `Carousel` · `ScrollArea` · `Disclosure` · `Accordion` · `Tabs` |
379
+ | Feedback | `Alert` · `Badge` · `StatusDot` · `CodeBadge` · `Kbd` · `EmptyState` · `Skeleton` (+ `SkeletonCard`, `SkeletonRows`, `SkeletonTable`) · `Spinner` · `Progress` · `Steps` · `Divider` · `Avatar` · `Rating` · `Price` · `Stat` · `CopyField` · `Breadcrumb` · `ToastHub` (+ `ToastPortal`) |
380
+ | Modal | `Modal` · `Drawer` · `BottomSheet` · `ConfirmDialog` · `PromptDialog` · `CommandPalette` |
381
+ | Data | `Table` (+ `TableScroller`) · `DataTable` (+ `DataTableRow`) · `FilterBar` (+ `FilterBarField`) · `Pagination` · `Timeline` |
382
+ | Theme and hooks | `UiProvider` · `UiThemeScript` · `useUi` · `useUiTheme` · `useUiTokens` · `useAnnounce` · `useStickToBottom` · `usePositioning` · `useVirtualList` · `useListboxNavigation` · `hasOpenSurface` |
383
+ | Chat, `/chat` | `ChatThread` · `ChatMessage` · `PromptInput` · `PromptSuggestions` · `Prose` · `CodeBlock` · `ReasoningBlock` (+ `ReasoningText`) · `ToolCall` (+ `ToolCallSection`) · `TerminalOutput` · `SourceList` · `Citation` · `MessageActions` · `AppBar` · `ActionList` (+ `ActionListItem`) · `SegmentBar` · `Legend` · `DescriptionList` |
384
+ | Charts, `/charts` | `LineChart` · `AreaChart` · `BarChart` · `ComposedChart` · `PieChart` · `ScatterPlot` · `Heatmap` · `BoxPlot` · `Histogram` · `RadarChart` |
385
+
386
+ ### Variants and sizes
387
+
388
+ | `UIVariant` | Use |
389
+ | ----------- | ----------------------------------------------------------------------- |
390
+ | `PRIMARY` | the one expected action of the screen: cobalt fill |
391
+ | `ACCENT` | the conversion action (download, try, subscribe): coral fill, only that |
392
+ | `SECONDARY` | a neutral second action: grey border, body text |
393
+ | `OUTLINE` | a cobalt frame: an encouraged navigation that is not the primary action |
394
+ | `SOFT` | a neutral fill: a secondary action inside a card |
395
+ | `GHOST` | no surface: toolbar and inline secondary actions, in the link colour |
396
+ | `DANGER` | a destructive action |
397
+ | `ON_BAND` | an action on the brand band (deep cobalt in both themes) |
398
+
399
+ `UISize`: `XSMALL` 28px · `SMALL` 32px · `MEDIUM` 40px · `LARGE` 48px. Fields
400
+ are 40px with 15px text on desktop and 44px with 16px text on a narrow or touch
401
+ screen (no iOS zoom, a full touch target).
402
+
403
+ `IconButton variant`: `ghost` · `outline` · `solid` · `accent` · `danger`.
404
+ `Badge tone`: `neutral` · `success` · `danger` · `warning` · `info` ·
405
+ `highlight`, each as `soft`, `solid` or `outline`.
406
+
407
+ ### The number of options picks the element
408
+
409
+ | Options | Element |
410
+ | ------------- | ---------------------------------------------------- |
411
+ | ≤7 | `ChipGroup`: all on screen, one tap |
412
+ | 5–20 | `Select`: a list without search |
413
+ | 20+ | `Combobox`: type to search |
414
+ | A range | `RangeSlider` (+ a number input for the exact value) |
415
+ | A single flag | `Checkbox` |
416
+
417
+ ### Dropdown or bottom sheet
418
+
419
+ `Select` and `Combobox` open a **dropdown anchored to the trigger** on every
420
+ screen except a narrow touch one, where they open a **bottom sheet**. The rule
421
+ is one media query, exported as `SHEET_MEDIA_QUERY`:
422
+ `(max-width: 640px) and (pointer: coarse)`. A narrow desktop window, an
423
+ Electron window included, keeps the dropdown.
395
424
 
396
- ### Seçenek sayısı öğeyi belirler
425
+ ---
397
426
 
398
- | Seçenek sayısı | Öğe |
399
- | -------------- | ----------------------------------------------- |
400
- | ≤7 | `ChipGroup` — hepsi ekranda, tek dokunuş |
401
- | 5–20 | `Select` — aramasız liste; mobilde alt sayfa |
402
- | 20+ | `Combobox` — yazarak ara |
403
- | Aralık | `RangeSlider` (+ kesin değer için sayı girdisi) |
404
- | Tekil aç/kapa | `Checkbox` |
427
+ ## Decisions the system carries
405
428
 
406
- Hepsini onay kutusu listesi yapmak tek tip ama kötü bir arayüzdür: 1121 markayı
407
- da 3 seçeneği de aynı biçimde göstermek, ikisini de yanlış gösterir.
429
+ **Cobalt acts, coral converts, turquoise highlights.** Cobalt is the primary
430
+ action, the link, the selection and the focus ring. Coral is the conversion
431
+ button (`UIVariant.ACCENT` on `Button`, `variant="accent"` on `IconButton`) and
432
+ appears nowhere else. Turquoise marks a highlight or a live state
433
+ (`Badge tone="highlight"`, `Avatar tone="highlight"`); it is a fill, never body
434
+ text.
435
+ `colour-footprint.test.ts` turns a coral or turquoise token in any other
436
+ component's stylesheet into a failing test.
408
437
 
409
- ---
438
+ **One `PRIMARY` per screen.** `UIVariant` is a hierarchy of emphasis: two
439
+ filled buttons side by side and neither stands out.
440
+
441
+ **Status is a tint, an action is a fill or a border.** `Alert` and the soft
442
+ `Badge` sit on tinted grounds; when that distinction blurs, users try to click
443
+ status labels.
444
+
445
+ **Colour never carries meaning alone** (WCAG 1.4.1). Every state is also told
446
+ by a text, an icon or a mark: each `Alert` tone has its own icon, the required
447
+ star of `Field` has a readable text next to it.
448
+
449
+ **Icons come from `react-bootstrap-icons`, in the filled version.** A thin
450
+ outline disappears on a tint at small sizes. Where no filled twin exists (`X`,
451
+ `Check`, `Plus`, `Dash`) the heavy `*Lg` version is used, and `Caret*Fill`
452
+ instead of a chevron. Content icons (a category mark, a logo) always come from
453
+ outside as a `ReactNode` prop.
454
+
455
+ **Icon size is separate from text size.** The scale is its own set of values
456
+ (`$icon-xs` … `$icon-xl`: 14/16/18/20/24px); every box that carries an icon
457
+ takes its size from there.
458
+
459
+ **"Could not load" and "empty" are different states.** `DataTable` never draws
460
+ the empty message while `error` is set.
461
+
462
+ **A native element beats an imitation.** `<dialog>`, `<input type="range">`,
463
+ `<input type="radio">` are kept. The one exception is `<select>`: its list
464
+ cannot be styled and its mobile wheel looks like nothing else in the
465
+ application.
410
466
 
411
- ## Sistemin taşıdığı kararlar
412
-
413
- **Zemin mürekkep ve kâğıt, yeşil eylemdir.** Nötrler sıcak mürekkep ekseninde
414
- (açık sayfa `#faf9f5`, koyu sayfa `#141413`); markanın yeşili yalnızca birincil
415
- eylemde, bağlantıda, odak halkasında ve marka işaretinde. Seçim — etkin sayfa,
416
- seçili çip, sekme çizgisi, ilerleme, onay kutusu, anahtar — mürekkeptir.
417
- Yeşilin yayılmasını `green-footprint.test.ts` derleme hatasına çevirir.
418
-
419
- **Doygun dolgu = tıklanabilir.** Durum etiketleri **her zaman** tint zeminli
420
- (`Badge`, `Alert`), eylemler **her zaman** dolgulu veya çerçeveli. Bu ayrım
421
- bozulduğunda kullanıcı durum etiketine tıklamayı deniyor. `Badge`in `solid`
422
- varyantı durum tonlarında bilinçli olarak `soft` ile aynı şeyi verir.
423
-
424
- **Bir ekranda tek `PRIMARY`.** `UIVariant` bir vurgu hiyerarşisi: iki dolu düğme
425
- yan yana durduğunda hiçbiri öne çıkmaz. `UIVariant.CART` ekrandaki en parlak
426
- yeşil — dönüşüm eylemi; birincil eylemden (koyu çam) aydınlıkla ayrışır.
427
-
428
- **Renk tek başına anlam taşımaz** (WCAG 1.4.1). Her durum bir metin, ikon veya
429
- işaretle de bildirilir: `Alert`in her tonunun kendi ikonu var, `Field`in zorunlu
430
- yıldızının yanında okunabilir bir metin, `RatingInput`in yıldızlarının altında
431
- açıklama duruyor.
432
-
433
- **İkonlar `react-bootstrap-icons`ten, DOLU sürümüyle.** Kabuk simgeleri
434
- (`Modal`ın kapatma çarpısı, `Select`in oku, `Alert`in durum ikonu) bir zamanlar
435
- kütüphanenin içinde elle çizilmiş yollardı; bir ikon setinin bakımını üstlenmek
436
- hiçbir şey kazandırmıyordu — tüketici uygulamalar kendi içerik ikonlarını zaten
437
- aynı setten alıyor ve paket `external`, yani ikinci bir kopya inmiyor. Dolu
438
- (`*Fill`) sürüm kural: ince konturlu bir simge, tint zeminde ve küçük ölçüde
439
- tek piksele inip kayboluyor. Fill eşi olmayan simgelerde (`X`, `Check`, `Plus`,
440
- `Dash`) kalın `*Lg` sürümü kullanılır, chevron yerine dolu `Caret*Fill` —
441
- böylece bütün kabuk aynı çizgi ağırlığında kalır. İçerik ikonları (kategori
442
- simgesi, marka logosu) her zaman `ReactNode` prop'u olarak DIŞARIDAN gelir.
443
-
444
- **İkon ölçüsü metin puntosundan ayrıdır.** İkonlar `1em` ile ölçeklenir ama
445
- taşındıkları kutunun `font-size` değeri metin için seçilmiştir ve simge için
446
- küçük kalır: 40 px'lik bir `IconButton`ın ortasındaki 16 px'lik simge tıklanabilir
447
- görünmüyordu. Ölçek ayrı bir token dizisidir (`$icon-xs` … `$icon-xl`,
448
- 14/16/18/20/24 px) ve ikon taşıyan her kutu puntosunu oradan alır.
449
-
450
- **"Yüklenemedi" ile "boş" ayrı durumlardır.** `DataTable`da `error` verilmişken
451
- boş mesajı **asla** çizilmez; bir ağ hatasını "kayıt kalmamış" diye okutmak
452
- kullanıcıya yanlış karar verdirir.
453
-
454
- **Yerel öğe, taklit öğeden iyidir.** `<dialog>`, `<input type="range">`,
455
- `<input type="radio">` korunur. Tek istisna `<select>`: açılır listesinin içine
456
- hiçbir biçimde girilemiyor ve mobil tekerleği uygulamanın geri kalanına hiç
457
- benzemiyor. Bedeli (odak yönetimi, ok tuşları, `aria-activedescendant`) bir kez
458
- ödenip tek bileşene kapatıldı.
459
-
460
- **Dokunma hedefi en az 44×44 px** (WCAG 2.5.8). Görsel kutu daha küçük olabilir;
461
- tıklanabilir alan görünmez bir örtüyle korunur.
462
-
463
- **Geçişler 200 ms'yi geçmez** — tek istisna dipten açılan panel
464
- (`duration-slow`, 320 ms) — ve `prefers-reduced-motion` her animasyonda
465
- karşılanır.
466
-
467
- **Odak halkası `:focus-visible` ile.** `:focus` fareyle tıklandığında da halka
468
- gösterip görsel gürültü yaratıyor ve kullanıcılar bunu hata sanıyordu.
467
+ **A touch target is at least 44×44px** (WCAG 2.5.8). The visual box may be
468
+ smaller; an invisible overlay keeps the area.
469
+
470
+ **Motion uses the design durations** (`fast` 120ms, `base` 200ms, `slow`
471
+ 320ms for a sheet) and every animation honours `prefers-reduced-motion`.
472
+
473
+ **The focus ring is `:focus-visible`**, 2px of cobalt with a 2px offset.
469
474
 
470
475
  ---
471
476
 
472
477
  ## Next.js App Router
473
478
 
474
- Paket `'use client'` sınırını **kendi içinde ve dosya başına** çizer (6.0):
475
- kanca kullanmayan bileşenler (`Button`, `Chip`, `Badge`, `Card`, `Panel`,
476
- `Tile`, `TextLink`, `Kbd`, `Alert`, `EmptyState`, `IconButton`, `Progress`,
477
- `Skeleton`, `StatusDot`, `SummaryCard`, `SectionHeader`, `PageHeader`,
478
- `Divider`) sunucu bileşeni olarak çizilir ve istemciye JS indirmez; etkileşimli
479
- olanlar kendi yönergesini taşır, `children` sunucuda çizilip aktarılır. İnce
480
- `Client*` sarmalayıcıları gerekmez; `UIVariant`/`UISize` sunucuda da içe
481
- aktarılır.
479
+ The package draws the `'use client'` boundary **inside itself, per file**:
480
+ components without hooks (`Alert`, `Badge`, `Button`, `Card`, `Chip`,
481
+ `Divider`, `EmptyState`, `IconButton`, `Kbd`, `PageHeader`, `Panel`,
482
+ `Progress`, `SectionHeader`, `Skeleton`, `StatusDot`, `SummaryCard`,
483
+ `TextLink`, `Tile`) render as server components and send no JavaScript; the
484
+ interactive ones carry their own directive. No thin `Client*` wrappers are
485
+ needed, and `UIVariant` / `UISize` can be imported on the server.
482
486
 
483
487
  ```ts
484
- // next.config.ts — cephe saf olduğu için içe aktarım bileşen dosyasına kısa devre yapılır.
485
- experimental: { optimizePackageImports: ['@handijit/ui'] }
488
+ // next.config.ts: the main entry is pure, so imports short-circuit to the component file.
489
+ const nextConfig = {
490
+ experimental: { optimizePackageImports: ['@handijit/ui'] },
491
+ };
486
492
  ```
487
493
 
488
494
  ```tsx
489
- // app/layout.tsx — sunucu bileşeni
495
+ // app/layout.tsx, a server component
496
+ import '@handijit/design/tokens.css';
497
+ import '@handijit/design/fonts.css';
490
498
  import '@handijit/ui/styles.css';
491
- import '@handijit/ui/chat.css'; // yalnızca /chat kullanılıyorsa
492
- import '@handijit/ui/charts.css'; // yalnızca /charts kullanılıyorsa
499
+ import '@handijit/ui/chat.css'; // only if /chat is used
500
+ import '@handijit/ui/charts.css'; // only if /charts is used
493
501
  import { UiProvider, UiThemeScript } from '@handijit/ui';
494
502
  import NextLink from 'next/link';
495
503
 
@@ -497,7 +505,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
497
505
  return (
498
506
  <html lang="tr" suppressHydrationWarning>
499
507
  <head>
500
- <UiThemeScript storageKey="theme" />
508
+ <UiThemeScript />
501
509
  </head>
502
510
  <body>
503
511
  <UiProvider linkComponent={NextLink}>{children}</UiProvider>
@@ -507,141 +515,28 @@ export default function RootLayout({ children }: { children: React.ReactNode })
507
515
  }
508
516
  ```
509
517
 
510
- `suppressHydrationWarning`: tema betiği `<html>`e sunucunun yazmadığı bir
511
- öznitelik ekliyor; uyarı yalnızca o öğe için susturulur.
512
-
513
- ---
518
+ `suppressHydrationWarning`: the boot script adds an attribute to `<html>` that
519
+ the server did not write; the warning is silenced for that element only.
514
520
 
515
- ## Geliştirme
521
+ ## Electron
516
522
 
517
- ```bash
518
- nvm use # .nvmrc → 24
519
- npm install
520
- npm run verify # tokens → kontrast → typecheck → lint → kapsam → build → API → animasyon → boyut
521
- npm run playground # bileşen galerisi → http://localhost:5273
522
- ```
523
+ The same three stylesheet imports in the renderer entry, and the boot script in
524
+ the HTML shell. With the usual strict policy of a packaged program, allow the
525
+ script by hash (`THEME_BOOT_SCRIPT_HASH`), and keep `style-src-attr
526
+ 'unsafe-inline'` for the positioned components. With a mouse or a trackpad
527
+ `Select` and `Combobox` open dropdowns whatever the window width.
523
528
 
524
- | Komut | |
525
- | ------------------------- | --------------------------------------------------------------- |
526
- | `npm run tokens` | SCSS token dosyalarını `theme/tokens.ts`ten üretir |
527
- | `npm run build` | `build/` — ana giriş + `chat` + `charts` (ESM + CJS + `.d.ts` + CSS) ve `build/scss/` |
528
- | `npm run test` | Jest + Testing Library + `jest-axe` |
529
- | `npm run test:coverage` | kapsam kapısı — tüm `src/`: satır 89 · ifade 86 · fonksiyon 80 · dal 75 |
530
- | `npm run test:visual` | görsel regresyon (Playwright); `verify` İÇİNDE DEĞİL, aşağı bkz. |
531
- | `npm run test:device` | iOS (WebKit) + Android (Chromium) davranış nöbetçileri, aşağı bkz. |
532
- | `npm run check:contrast` | WCAG kontrast ölçümü, iki temada 210 çift |
533
- | `npm run check:palette` | palet tarifinin çıktısı mı + nötr chroma tavanı + tint ve kategori ayrışması |
534
- | `npm run check:api` | genel API nöbetçisi — dört giriş, dört taban çizgisi (ana 176 · sohbet 30 · grafik 22 · SCSS 252 ad) |
535
- | `npm run size` | paket boyutu bütçesi (`size-limit`) |
536
- | `npm run exports` | `package.json` `exports` haritasını üretir (`check:exports` doğrular, `build`in son adımı) |
537
- | `npm run playground` | bileşen galerisi (tema · RTL · yoğunluk anahtarı) |
538
- | `npm run lint` | ESLint (TS) + stylelint (SCSS) |
539
- | `npm run typecheck` | `tsc --noEmit` |
540
-
541
- ### Nöbetçiler ne koruyor
542
-
543
- - **Eksen taraması** — dışa verilen her bileşen, her anlamlı durumunda
544
- (`components/__tests__/a11y.test.tsx`). Deftere girmeyen yeni bir bileşen
545
- testi kırar.
546
- - **Klavye sözleşmesi** — tuş matrisleri her bileşenin JSDoc'unda, nöbetçisi
547
- `components/__tests__/keyboard.test.tsx`.
548
- - **Kontrast** — metin ≥ 4,5:1, ikon ve odak halkası ≥ 3:1. Eşiği tutmayan bir
549
- token derlemeyi kırar; düzeltme daima `palette.ts` düzeyinde yapılır.
550
- - **Token sözleşmesi** — açık/koyu anahtar kümeleri ve üretilmiş SCSS kaynakla
551
- eşleşir. Ayrışma bugüne kadar sessizdi.
552
- - **Paket boyutu** — tek bileşen import eden bir uygulamanın ne indirdiği
553
- ölçülür. Ölçülen (2026-10-10, `size-limit`): `Badge` **4,33 kB** · `Button`
554
- **5,12 kB** · `styles.css` **23,07 kB** · tüm paket **42,05 kB** gzip. Alt
555
- girişler ana pakette SAYILMAZ: sohbet **6,32 kB**, grafikler **14,02 kB**
556
- (yalnızca `LineChart` **7,19 kB**).
557
- ⚠ **Bütçeler `.size-limit.js`te** (paket 43 kB, `styles.css` 24 kB) ve
558
- başlıkta kalan pay dar. Bu satır bir dönem
559
- `Badge 2,7 / Button 3,4 / paket 22,2` diyordu — ölçüm değil, bayat bir
560
- kopyaydı ve okuyana **%70 boş alan** olduğunu düşündürüyordu. Yeni bir
561
- bileşen eklerken bütçenin fiilen dolu olduğu bilinerek girilir; `size-limit`
562
- kırmızıya döndüğünde doğru tepki limiti yükseltmek değil, ne eklendiğini
563
- ölçmektir (`npm run size`).
564
- - **Genel API** — her girişin dışa aktarım kümesi kendi taban çizgisiyle
565
- karşılaştırılır (`npm run check:api`): ana giriş `scripts/api-baseline.txt`
566
- (176), `chat` (30), `charts` (22), `scss` (252 `$değişken` + `@mixin`). Düşen
567
- bir ad **kırıcı**dır ve tüketiciler caret ile aldığı için bir sonraki
568
- `npm install`da derleme hatası olarak çıkar. Bilinçli değişiklikte
569
- `node scripts/check-api.mjs --update` + CLAUDE.md'ye gerekçe.
570
- - **Alt giriş sınırı** — `chat` ve `charts` ana girişi yalnızca PAKET ADIYLA
571
- alır (`subentry-boundary.test.ts`); göreli bir yol `UiProvider` bağlamının
572
- ve kipsel yüzey yığınının ikinci bir kopyasını paketlerdi.
573
- - **Yeşil ayak izi** — bileşen SCSS'inde yeşil token ailesine uzanan her
574
- bileşen gerekçeli bir izin listesinde (`green-footprint.test.ts`).
575
- - **Opacity ile soluklaştırma yasak** — `0`/`1` dışındaki her `opacity`
576
- derlemeyi kırar (`passive-opacity.test.ts`); pasiflik token'larla çizilir.
577
- - **iOS yakınlaştırması** — galerideki her metin girdisi dar ekranda ≥16px
578
- (`e2e/mobile.spec.ts`, hesaplanmış stil).
579
- - **Odak halkası ve hareket ölçeği** — stylelint, `focus-ring` mixin'i dışında
580
- `outline` ve bileşen SCSS'inde ham süre değeri (`0.7s`) yazılmasını
581
- engelliyor. Üç bileşen kendi halkasını, iki bileşen kendi süresini
582
- yazıyordu.
583
-
584
- ### Cihaz nöbetçileri — `npm run test:device`
529
+ ---
585
530
 
586
- ```bash
587
- npm run test:device:install # WebKit + Chromium ikilileri (bir kez)
588
- npm run test:device # ios (iPhone 14 · WebKit) + android (Pixel 7 · Chromium)
589
- ```
531
+ ## Chat components: `@handijit/ui/chat`
590
532
 
591
- Bu depodaki en pahalı hatalar masaüstünde **görünmüyordu**: alt sayfanın gövdesi
592
- çöküyor, dip şerit klavyenin altında kalıyor, filtre seçenekleri maskenin
593
- altında siliniyordu. Üçü de yerleşim/boyama hatası ve üçü de yalnızca gerçek bir
594
- yerleşim motorunda ölçülebiliyor — jsdom bunları hiç hesaplamıyor.
595
-
596
- **İki motor, çünkü bir motor yetmiyor.** iOS'taki her tarayıcı (Chrome dahil)
597
- WebKit; Chromium'da doğru olan orada doğru olmak zorunda değil.
598
-
599
- **Ekran görüntüsü değil sayı.** Bu koşu piksel karşılaştırmaz — yükseklik,
600
- taşma, odak ve hesaplanmış CSS değeri ölçer. Yani platformdan bağımsız,
601
- referans dosyası yok, `--update-snapshots` gerektirmiyor ve `test:visual`in
602
- aksine **CI'da olduğu gibi koşabilir**.
603
-
604
- **Emülasyonun dürüst sınırı.** Playwright'ın WebKit'i iOS Safari değil; çentik
605
- payı (`env(safe-area-inset-*)` burada hep 0), ekran klavyesinin görünen alanı
606
- daraltması, adres çubuğunun kaybolup gelmesi ve momentum kaydırma
607
- yakalanmıyor. O sınırın altında kalanlar için savunma CSS'te taban değerlerde
608
- (bkz. `bottom-sheet` mixin'i) ve onların nöbetçisi `e2e/bottom-sheet.spec.ts`.
609
-
610
- **Görsel regresyon `verify` içinde değil**: ekran görüntüsü platforma bağlı
611
- (yazı tipi tarama, alt piksel yumuşatma) ve macOS'te üretilmiş bir referans
612
- ubuntu üzerinde koşan CI'da her dosyada kırmızı döner. Referanslar platform
613
- başına saklanıyor (`e2e/__screenshots__/{platform}/`); CI'da açılacağı gün doğru
614
- yol, Playwright'ın resmi konteynerinde koşup `linux` referanslarını orada
615
- üretmek.
616
-
617
- > ⚠ **Referansları yenilerken `--update-snapshots=all` yazın, çıplak
618
- > `--update-snapshots` DEĞİL.** Ölçüldü (palet değişikliği turunda): çıplak
619
- > bayrak yalnızca **eksik** referansı yazıyor; eşleşmeyeni ne yeniden üretiyor
620
- > ne de kırıyor — koşu `40 passed` diyor, dosyalar eski renkte kalıyor ve
621
- > ardından gelen doğrulama koşusu **eski referansı eski referansla**
622
- > karşılaştırdığı için yeşil dönüyor. Yani sessizce yanlış bir "temiz" alınıyor.
623
- > `=all` mod, pikseli değişen her dosyayı yazar (bayt bayt aynı olanlara
624
- > dokunmaz, o yüzden dosya sayısı toplamdan az çıkabilir). Doğrulama:
625
- > yenilemeden sonra bir referansın baskın renklerini örnekleyin.
626
- > ⚠ `=changed` de yetmez: yalnızca eşiği (`maxDiffPixelRatio: 0.002`) aşan
627
- > farkı yazar — iki küçük yazının kalktığı bir değişiklik eşiğin altında kalıp
628
- > eski referans dosyada durur. Küçük bir değişiklikte bölümün referansını silip
629
- > `=missing` ile yazdırın.
630
-
631
- `master`a her push `npm publish` çalıştırır — **`package.json` sürümünü
632
- yükseltmeyi unutmayın**, aynı sürüm ikinci kez yayımlanamaz ve iş akışı kırmızı
633
- döner.
634
-
635
- ## Sohbet bileşenleri — `@handijit/ui/chat`
636
-
637
- Sohbet ve araç arayüzleri için ayrı bir giriş; ana paketi ve `styles.css`i
638
- büyütmez.
533
+ A separate entry for chat and tool interfaces; it adds nothing to the main
534
+ bundle or to `styles.css`.
639
535
 
640
536
  ```tsx
641
537
  import '@handijit/ui/styles.css';
642
- import '@handijit/ui/chat.css'; // styles.css'TEN SONRA
538
+ import '@handijit/ui/chat.css'; // AFTER styles.css
643
539
 
644
- import { UiProvider } from '@handijit/ui';
645
540
  import { ChatMessage, ChatThread, PromptInput } from '@handijit/ui/chat';
646
541
 
647
542
  <ChatThread
@@ -665,7 +560,7 @@ import { ChatMessage, ChatThread, PromptInput } from '@handijit/ui/chat';
665
560
  </ChatThread>;
666
561
  ```
667
562
 
668
- Metinler aynı sağlayıcıdan, `labels.chat` altında:
563
+ Labels come from the same provider, under `labels.chat`:
669
564
 
670
565
  ```tsx
671
566
  <UiProvider
@@ -688,58 +583,31 @@ Metinler aynı sağlayıcıdan, `labels.chat` altında:
688
583
  >
689
584
  ```
690
585
 
691
- `CodeBlock` kodu kendisi vurgular ve biçimlendirir — bağımlılık yok, renkler
692
- temayla döner:
586
+ `CodeBlock` highlights and formats the code itself, with no dependency:
693
587
 
694
588
  ```tsx
695
589
  <CodeBlock language="json" code={JSON.stringify(payload)} />
696
590
  ```
697
591
 
698
- JSON iki boşlukla yeniden girintilenir (değerler metin olarak korunur: büyük
699
- sayılar yuvarlanmaz), kenardaki boş satırlar atılır; başka dilde girintiye
700
- dokunulmaz. 30'dan fazla dil tanınır (ts/tsx, json, bash, python, yaml, css,
701
- html, sql, diff, go, rust…), tanınmayan dil düz çizilir. Kopyala düğmesi ekranda
702
- görüneni kopyalar. Renkler `chat.css`teki `--ui-syntax-keyword`, `-string`,
703
- `-number`, `-name`, `-type` değişkenleridir; CSS'te ezilir. Shiki düzeyinde
704
- doğruluk isterseniz önceden vurgulanmış HTML'i `html` olarak verin (güvenliği
705
- sizin) — yerleşik vurgulayıcının yerine geçer.
706
-
707
- Markdown ayrıştırması tüketicide kalır: `Prose` yalnızca tipografi kabıdır.
708
- Bileşen listesi ve kararlar: `CLAUDE.md` → "`@handijit/ui/chat`".
709
-
710
- ## SCSS API — `@handijit/ui/scss`
592
+ JSON is re-indented with two spaces (values are kept as text: large numbers are
593
+ not rounded) and blank lines at the edges are dropped; other languages keep
594
+ their indentation. More than 30 languages are recognised; an unknown one is
595
+ drawn plain. The token colours are design tokens (keyword `primary-text`,
596
+ string `success-fg`, number `warning-fg`, type `highlight-text`), so they
597
+ follow the theme and stay inside the measured text pairs. For Shiki-grade
598
+ accuracy pass pre-highlighted HTML as `html` (its safety is yours).
711
599
 
712
- Token değişkenleri ve mixin'ler kopyalanmadan alınır. Değişkenler
713
- `var(--ui-…)` dizeleridir; tema çalışma zamanında döner. Modül kural basmaz;
714
- `:root` token kuralları `base.css`tedir (bileşen modülleri onu kendileri getirir).
600
+ Markdown parsing stays in the application: `Prose` is a typography container
601
+ only.
715
602
 
716
- ```scss
717
- @use '@handijit/ui/scss' as ui;
718
-
719
- .panel {
720
- padding: ui.$space-4;
721
- border: 1px solid ui.$border;
722
-
723
- @include ui.focus-ring;
603
+ ## Charts: `@handijit/ui/charts`
724
604
 
725
- @media (max-width: ui.$breakpoint-tablet-portrait) {
726
- padding: ui.$space-3;
727
- }
728
- }
729
- ```
730
-
731
- Next.js (Turbopack ve webpack) paket `exports`'unu çözer. Başka bir derleyici
732
- çözemezse yol `node_modules/@handijit/ui/build/scss` ile `loadPaths`e verilip
733
- `@use 'index' as @handijit/ui` yazılabilir.
734
-
735
- ## Grafikler — `@handijit/ui/charts`
736
-
737
- On SVG grafiği, sıfır bağımlılık (çizim, ölçek ve istatistik kütüphanenin
738
- kendisinde). Ana paketi ve `styles.css`i büyütmez.
605
+ Ten SVG charts with no dependency (drawing, scales and statistics live in the
606
+ library).
739
607
 
740
608
  ```tsx
741
609
  import '@handijit/ui/styles.css';
742
- import '@handijit/ui/charts.css'; // styles.css'TEN SONRA
610
+ import '@handijit/ui/charts.css'; // AFTER styles.css
743
611
 
744
612
  import { BarChart, LineChart, PieChart } from '@handijit/ui/charts';
745
613
 
@@ -749,7 +617,7 @@ import { BarChart, LineChart, PieChart } from '@handijit/ui/charts';
749
617
  xKey="month"
750
618
  data={[
751
619
  { month: 'Oca', revenue: 120, cost: 90 },
752
- { month: 'Şub', revenue: 180, cost: null }, // null = veri yok: çizgi KESİLİR
620
+ { month: 'Şub', revenue: 180, cost: null }, // null = no data: the line BREAKS
753
621
  ]}
754
622
  series={[
755
623
  { key: 'revenue', label: 'Gelir' },
@@ -758,116 +626,162 @@ import { BarChart, LineChart, PieChart } from '@handijit/ui/charts';
758
626
  />;
759
627
  ```
760
628
 
761
- | Grafik | Ne zaman |
762
- |---|---|
763
- | `LineChart` · `AreaChart` | Zaman içinde eğilim; `AreaChart isStacked` toplamın parçaları |
764
- | `BarChart` | Kategorileri karşılaştırma; `orientation="horizontal"` uzun adlar için |
765
- | `ComposedChart` | Çubuk + çizgi; farklı birim `axis: 'right'` |
766
- | `PieChart` | Bir bütünün ≤6 parçası (`variant="donut"` varsayılan) |
767
- | `ScatterPlot` | İki değişkenin ilişkisi; `size` ile balon |
768
- | `Heatmap` | İki kategorik eksende yoğunluk (gün × saat) |
769
- | `BoxPlot` · `Histogram` | Dağılım: grupları karşılaştırma · tek değişkenin şekli |
770
- | `RadarChart` | Birkaç seriyi aynı eksen takımında; eksenler AYNI ölçekte |
771
-
772
- Her grafik tek bir sekme durağıdır: oklar öğeleri gezer (gelinen öğe
773
- duyurulur), `Home`/`End` uçlara gider, `Escape` vurguyu kaldırır. Ekran okuyucu
774
- grafiği gizli bir **veri tablosu** olarak da okur. Genişlik kapsayıcıdan ölçülür
775
- (`width` ile sabitlenebilir), yükseklik `height`. Sayılar `labels.locale`
776
- yereliyle biçimlenir; ipucu, duyuru ve tablo `formatValue` / `format` ile
777
- ezilir. Eksen bölmeleri bunu KULLANMAZ: kısa gösterimde ("12 B", "0,005") ve
778
- birimsiz kalır, basamak sayısı bölme adımından gelir. Renkler kategorik paletten
779
- (`tone`: `cat-1`…`cat-5`, `series-1`, `series-2`, `muted`).
780
-
781
- ## 1.0'a geçiş — `@ahmetilhn/hanui` 5.x'ten
782
-
783
- 1.0 iki şeyi birden getirir: **yeni ad** (`@ahmetilhn/hanui` → `@handijit/ui`; CSS öneki `hanui-` →
784
- `ui-`, token'lar `--hanui-*` → `--ui-*`, `@layer ui`, `data-ui-theme`, `UiProvider`/`useUi`/`initUi`;
785
- peer `@ahmetilhn/handy-utils` → `@handijit/utils`) ve 6.0 olarak hazırlanmış **paketleme** değişiklikleri.
786
- 6.0 bir **paketleme** sürümü: bileşen API'si değişmedi, kırılan şey paketin
787
- dosya düzeni, `'use client'` sınırı ve CSS'in inişi. Tam liste `CHANGELOG.md`.
788
-
789
- | 5.x | 6.0 |
790
- |---|---|
791
- | `import '@handijit/ui/styles.css'` | ESM'de **silinir** — bileşen modülleri `base.css` + kendi `style.css`ini getirir; CJS/paketleyicisiz kullanımda kalır |
792
- | tek `build/index.js`, tamamı `'use client'` | `build/components/<Ad>/index.js`; 18 bileşen yönergesiz (sunucuda çizilir), alt yol `@handijit/ui/<kebab-ad>` |
793
- | `build/index.d.ts` toplanmış | dosya başına `.d.ts`; `import type` aynı |
794
- | `Combobox` tetikleyicisi `role="button"`, arama kutusu `role="combobox"` | tetikleyici `role="combobox"` (`aria-expanded`, `aria-controls`, `aria-invalid`, `aria-required`), arama kutusu metin kutusu |
795
- | bağlantı `Button` pasifken `preventDefault` işleyicisi | `aria-disabled` + `tabIndex=-1` + `pointer-events: none`; işlev prop'u yok (sunucudan çizilebilir) |
796
- | site ezmesi sınıf seçicisiyle | **bileşen değişkenleri** (aşağıda) |
797
-
798
- ### Bileşen değişkenleri
799
-
800
- Token sözleşmesinin (`--ui-page`, `--ui-text`… 110 ad) dışında, tek bir
801
- bileşenin görünümünü tüketicinin `:root`ta ezdiği CSS özel özellikleri. Bileşen
802
- `var(--ui-…, varsayılan)` ile okur; yazılmazsa varsayılan geçerlidir.
803
-
804
- | Bileşen | Değişkenler |
805
- |---|---|
806
- | odak halkası | `--ui-ring-width` (2px) · `--ui-ring-offset` (2px; öğenin içine çekmek için `-2px`) |
807
- | girdiler | `--ui-placeholder` (`text-3`) |
808
- | kipsel perde | `--ui-scrim-blur` (3px) |
809
- | `Button` | `--ui-button-radius` · `-weight` · `-tracking` · `-wrap` (`nowrap`) |
810
- | `Chip` | `--ui-chip-bg` · `-line` · `-fg` · `-radius` · `-weight` · `-selected-bg` · `-selected-line` · `-selected-fg` · `-selected-weight` |
811
- | `Badge` | `--ui-badge-font` · `-weight` · `-tracking` · `-transform` · `-radius` (+ `-bg` · `-line` · `-fg`, 5.x) |
812
- | `Stat` | `--ui-stat-label-font/-size/-weight/-tracking/-transform` · `-value-font/-weight/-tracking` · `-unit-font/-size/-tracking/-transform` |
813
- | `Tabs` | `--ui-tab-weight` |
814
- | `RadioCard` | `--ui-radio-card-line` · `-radius` |
629
+ | Chart | When |
630
+ | ------------------------- | --------------------------------------------------------------- |
631
+ | `LineChart` · `AreaChart` | a trend over time; `AreaChart isStacked` for parts of a total |
632
+ | `BarChart` | comparing categories; `orientation="horizontal"` for long names |
633
+ | `ComposedChart` | bars and a line; a different unit on `axis: 'right'` |
634
+ | `PieChart` | at most six parts of a whole (`variant="donut"` by default) |
635
+ | `ScatterPlot` | the relation of two variables; a bubble with `size` |
636
+ | `Heatmap` | density on two categorical axes (day × hour) |
637
+ | `BoxPlot` · `Histogram` | distribution: comparing groups · the shape of one variable |
638
+ | `RadarChart` | a few series on one set of axes, all on the SAME scale |
639
+
640
+ Every chart is a single tab stop: arrows walk the items (each is announced),
641
+ `Home` / `End` go to the ends, `Escape` clears the highlight. A screen reader
642
+ also reads the chart as a hidden **data table**. Width is measured from the
643
+ container (or fixed with `width`); numbers are formatted with `labels.locale`.
644
+ Colours come from the data series of the design (`tone`: `chart-1` … `chart-6`,
645
+ `muted`).
646
+
647
+ ## SCSS API: `@handijit/ui/scss`
648
+
649
+ Variables and mixins for the application's own stylesheets. The variables are
650
+ `var(--hd-…)` references, so the theme turns at run time; the module emits no
651
+ rules.
652
+
653
+ ```scss
654
+ @use '@handijit/ui/scss' as ui;
815
655
 
816
- ```css
817
- :root {
818
- --ui-ring-offset: -2px;
819
- --ui-button-weight: 500;
820
- --ui-chip-radius: var(--ui-radius-pill);
821
- --ui-badge-font: var(--ui-font-mono);
822
- --ui-badge-transform: uppercase;
656
+ .panel {
657
+ padding: ui.$space-4;
658
+ border: 1px solid ui.$border;
659
+ border-radius: ui.$radius-md;
660
+
661
+ @include ui.type(small);
662
+ @include ui.focus-ring;
663
+
664
+ @media (max-width: ui.$breakpoint-md) {
665
+ padding: ui.$space-3;
666
+ }
823
667
  }
824
668
  ```
825
669
 
826
- ## 5.0'a geçiş
670
+ **The values are custom properties, so SCSS arithmetic does not work.**
671
+ `$space-8 - $space-2` compiles to `var(--a)-var(--b)` without an error and the
672
+ browser drops the declaration. Write the calculation in `calc()`:
827
673
 
828
- 5.0 bir **renk** sürümü: nötr eksen yeşilden sıcak mürekkebe geçti, yeşil
829
- yalnızca eylem, bağlantı ve odakta kaldı. Bileşen API'si değişmedi; kırılan şey
830
- token adları ve görünüm.
674
+ ```scss
675
+ padding-inline-start: calc(#{ui.$space-8} - #{ui.$space-2});
676
+ margin-inline: calc(-1 * #{ui.$space-2});
677
+ ```
678
+
679
+ Sass colour functions do not work either; mix in CSS:
680
+ `color-mix(in srgb, #{ui.$primary} 20%, transparent)`.
681
+
682
+ Next.js (Turbopack and webpack) resolves the package `exports`. For a compiler
683
+ that cannot, add `node_modules/@handijit/ui/build/scss` to `loadPaths` and
684
+ write `@use 'index' as ui`. An application that needs only the tokens can use
685
+ `@handijit/design/tokens.scss` directly.
686
+
687
+ ---
688
+
689
+ ## Development
690
+
691
+ ```bash
692
+ nvm use # .nvmrc → 24
693
+ npm install
694
+ npm run verify # contrast → typecheck → lint → coverage → build → API → animations → size
695
+ npm run playground # component gallery → http://localhost:5273
696
+ ```
831
697
 
832
- | 4.x token | 5.0 |
833
- |---|---|
834
- | `role`, `role-hover`, `role-active` | `selected`, `selected-hover`, `selected-active` (mürekkep) |
835
- | `on-role` | `on-selected` |
836
- | `role-tint` / `role-line` | `selected-tint` / `selected-line` |
837
- | `role-text` | `link` (+ `link-hover`) |
838
- | `shell*` | aynı ad, değer çamdan mürekkebe; yeni `on-shell`, `on-shell-2`, `on-shell-danger`, `shell-line` |
839
- | — | `cat-1…5`, `series-1/2` (kategorik palet) |
698
+ | Command | |
699
+ | ------------------------ | ------------------------------------------------------------------------------------------- |
700
+ | `npm run build` | `build/`: main entry, `chat`, `charts` (ESM + CJS + `.d.ts` + CSS) and `build/scss/` |
701
+ | `npm run test` | Jest + Testing Library + `jest-axe` |
702
+ | `npm run test:coverage` | coverage gate over all of `src/`: lines 89 · statements 86 · functions 80 · branches 75 |
703
+ | `npm run check:contrast` | WCAG contrast of the pairs the components draw, both themes (240 pairs) |
704
+ | `npm run check:api` | public API guard: four entries, four baselines (main 163 · chat 30 · charts 22 · SCSS 225) |
705
+ | `npm run size` | bundle budget (`size-limit`) |
706
+ | `npm run exports` | writes the `exports` map of `package.json` (`check:exports` verifies; last step of `build`) |
707
+ | `npm run lint` | ESLint (TypeScript) + stylelint (SCSS) |
708
+ | `npm run format:check` | Prettier over `src`, `playground`, `__tests__` |
709
+ | `npm run typecheck` | `tsc --noEmit` |
710
+ | `npm run test:visual` | visual regression (Playwright); NOT part of `verify`, see below |
711
+ | `npm run test:device` | iOS (WebKit) and Android (Chromium) behaviour guards |
712
+
713
+ ### What the guards protect
714
+
715
+ - **Design contract** (`__tests__/styles/design-contract.test.ts`): every
716
+ `--hd-*` name in the compiled stylesheets, the SCSS aliases and the
717
+ TypeScript sources exists in the installed `@handijit/design`; breakpoints
718
+ and layers match it; no stylesheet declares an `--hd-*` property or carries
719
+ a literal colour.
720
+ - **Colour footprint** (`colour-footprint.test.ts`): coral and turquoise tokens
721
+ are used only by the components on a justified allow-list.
722
+ - **Contrast**: text ≥ 4.5:1, icons and the focus ring ≥ 3:1, measured on the
723
+ values of the installed design package for every pair a component draws.
724
+ - **Accessibility scan** (`__tests__/components/a11y.test.tsx`): every exported
725
+ component in each meaningful state. A new component missing from the ledger
726
+ fails the test.
727
+ - **Keyboard contract**: the key matrix is in each component's JSDoc; its guard
728
+ is `keyboard.test.tsx`.
729
+ - **Control sizes** (`control-size.test.tsx`): `Input`, `DateField`,
730
+ `DateRange` and `Select` take their size from one mixin, and a text field
731
+ never drops below 16px on a narrow or touch screen.
732
+ - **Public API**: the exports of each entry are compared with a baseline. A
733
+ dropped name is **breaking** and surfaces as a build error at the consumer's
734
+ next install. For a deliberate change: `npm run build`, then
735
+ `node scripts/check-api.mjs --update`, and a line in the CHANGELOG.
736
+ - **Bundle size**: budgets in `.size-limit.js` (main entry 44 kB, `styles.css`
737
+ 25 kB gzip). When `size-limit` turns red, measure what was added before
738
+ raising a limit.
739
+ - **Sub-entry boundary**: `chat` and `charts` import the main entry by PACKAGE
740
+ NAME only (`subentry-boundary.test.ts`); a relative path would bundle a
741
+ second copy of the provider context and the modal stack.
742
+ - **No fading with opacity**: any `opacity` other than `0` / `1` fails
743
+ (`passive-opacity.test.ts`); a disabled state is drawn with tokens.
744
+ - **Focus ring and motion scale**: stylelint rejects `outline` outside the
745
+ `focus-ring` mixin and raw durations in component SCSS.
746
+
747
+ ### Device guards: `npm run test:device`
748
+
749
+ ```bash
750
+ npm run test:device:install # WebKit and Chromium binaries (once)
751
+ npm run test:device # ios (iPhone 14 · WebKit) + android (Pixel 7 · Chromium)
752
+ ```
840
753
 
841
- Eski adlara takma ad **yok**. Kendi CSS'inizde `--ui-role*` okuyan ya da
842
- `theme={{ light: { role: … } }}` ezmesi yazan yerler yeni adlara geçer.
843
- Görünüm değişiklikleri: açık sayfa krem (`#faf9f5`), koyu sayfa sıcak siyah
844
- (`#141413`), kart ve panel durgunken gölgesiz, `info` tonu nötr, `CodeBadge`
845
- mürekkep. Ayrıntı ve ölçümler: `CLAUDE.md` → "Renk kararları (5.0)".
754
+ The most expensive bugs of this repository were invisible on a desktop: a
755
+ collapsed sheet body, a bottom bar under the keyboard. They are layout and
756
+ paint bugs and can only be measured in a real layout engine. This run compares
757
+ numbers (height, overflow, focus, computed style), not pixels, so it needs no
758
+ reference files.
846
759
 
847
- ## 5.1 – 5.6'da eklenenler
760
+ **Visual regression is not part of `verify`**: screenshots depend on the
761
+ platform (font rasterisation), and a reference made on macOS fails on the
762
+ ubuntu CI runner. References are stored per platform under
763
+ `__tests__/e2e/__screenshots__/{platform}/`.
848
764
 
849
- Hepsi ekleme; 5.0'ın token sözleşmesi dışında kırıcı değişiklik yok.
765
+ > When refreshing references write `--update-snapshots=all`, not the bare
766
+ > flag: the bare flag writes only **missing** files and leaves mismatching ones
767
+ > in place while the run reports success.
850
768
 
851
- | Sürüm | Ne |
852
- |---|---|
853
- | 5.1 | `Disclosure`, `StatusDot`, `Kbd`; `useUiTokens`, `useStickToBottom`, `hasOpenSurface`; `Modal size="xl"`, `Button size={UISize.XSMALL}` + `shape="pill"`, `IconButton shape="round"`, `Textarea variant="bare"`, `Progress size="xs"`, etkileşimli `CodeBadge`, `Avatar` (`icon`, `shape="rounded"`, `tone="brand"`), `Stat variant="inline"`, `ScrollArea isFollowingEnd`; `CommandPalette` `hotkey` + komut kapanıştan SONRA çalışır |
854
- | 5.2 | `UiThemeScript`; `@handijit/ui/chat` — 20 sohbet bileşeni |
855
- | 5.3 | `@handijit/ui/charts` — 10 SVG grafiği; `@handijit/ui/scss`; `Input --technical` dar ekranda ≥16px; `QuantityStepper` ve `DataTable` pasif/dinlenme hâli opacity yerine token |
856
- | 5.3.2 | Düzeltme: kaydırma kapları kapsayıcı blok (`ChatThread`, `ScrollArea`, `TerminalOutput`, `ActionList` ve 12 kab daha) — görsel olarak gizli metin kabın dışına taşıp atalarını kaydırılabilir yapmıyor; grafik ekseni 0,1'in altındaki adımda sıfıra çökmüyor |
857
- | 5.4 | `CodeBlock` yerleşik vurgulayıcı ve biçimlendirici (bağımlılıksız, 30+ dil); sözdizimi renkleri `--ui-syntax-*` (yalnızca `chat.css`) |
858
- | 5.5 | `Input`, `DateField`, `DateRange` `size="sm"` (36px — `Select sm` ve `Button` SMALL ile tek hiza); SCSS `input-sm` mixin'i; yerel sayısal `size` niteliği bu üç girdinin tipinden çıktı |
859
- | 5.6 | `ContextMenu` — imleç noktasında açılan menü (sağ tık), `role="menu"` dışında başlık; `Menu` ile ortak yüzey; `usePositioning` öğesiz (nokta) çapayı da ölçer; düzeltme: `Menu` açılışta odağı gerçekten ilk öğeye taşır (gerçek tarayıcıda `<body>`de kalıyordu) |
769
+ ### Releasing
860
770
 
861
- Ayrıntı ve ölçülmüş gerekçeler: `CLAUDE.md` → "Göç ve `@deprecated` yollar",
862
- "`@handijit/ui/chat`", "`@handijit/ui/charts`".
771
+ Every push to `master` runs `.github/workflows/publish.yml`: it checks the formatting,
772
+ runs `npm run verify`, and publishes with the `NPM_ACCESS_TOKEN` repository
773
+ secret and npm provenance **when `package.json` carries a version that is not
774
+ on npm yet**; otherwise it skips. A published version gets a `vX.Y.Z` tag and a
775
+ GitHub Release whose notes are that version's section of `CHANGELOG.md`, so the
776
+ section must exist before the push.
863
777
 
864
- ## Tasarım sözleşmesi
778
+ ## Design contract
865
779
 
866
- Öğe seçimi (hangi durumda hangi bileşen), yüzey/yükseklik merdiveni, renk
867
- kararları, durum tintleri, tipografi rolleri, erişilebilirlik eşikleri, hareket
868
- ölçeği ve `@deprecated` göç yolları **`CLAUDE.md`** içindedir. O dosya
869
- kütüphaneyi kullanan uygulamalar için de normatiftir.
780
+ Which element to use when, the surface ladder, colour roles, typography roles,
781
+ accessibility thresholds and the motion scale are in **`CLAUDE.md`**. That file
782
+ is normative for applications that use the library too. The visual language
783
+ itself (palette, type, logo) belongs to `@handijit/design`.
870
784
 
871
- ## Lisans
785
+ ## License
872
786
 
873
787
  MIT