@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.
- package/README.md +485 -571
- package/build/base.css +1 -1
- package/build/charts.cjs +1 -1
- package/build/charts.cjs.map +1 -1
- package/build/charts.css +1 -1
- package/build/charts.js +350 -349
- package/build/charts.js.map +1 -1
- package/build/chat.cjs +3 -3
- package/build/chat.cjs.map +1 -1
- package/build/chat.css +1 -1
- package/build/chat.js +344 -343
- package/build/chat.js.map +1 -1
- package/build/components/Accordion/style.css +1 -1
- package/build/components/Alert/style.css +1 -1
- package/build/components/Avatar/style.css +1 -1
- package/build/components/Badge/index.cjs +1 -1
- package/build/components/Badge/index.cjs.map +1 -1
- package/build/components/Badge/index.d.ts +8 -13
- package/build/components/Badge/index.js +1 -1
- package/build/components/Badge/index.js.map +1 -1
- package/build/components/Badge/style.css +1 -1
- package/build/components/BottomSheet/style.css +1 -1
- package/build/components/Breadcrumb/style.css +1 -1
- package/build/components/Button/index.cjs +1 -1
- package/build/components/Button/index.cjs.map +1 -1
- package/build/components/Button/index.js +2 -2
- package/build/components/Button/index.js.map +1 -1
- package/build/components/Button/style.css +1 -1
- package/build/components/Card/style.css +1 -1
- package/build/components/Carousel/style.css +1 -1
- package/build/components/Checkbox/style.css +1 -1
- package/build/components/Chip/style.css +1 -1
- package/build/components/ChipGroup/style.css +1 -1
- package/build/components/CodeBadge/style.css +1 -1
- package/build/components/Combobox/index.cjs +1 -1
- package/build/components/Combobox/index.cjs.map +1 -1
- package/build/components/Combobox/index.js +51 -51
- package/build/components/Combobox/index.js.map +1 -1
- package/build/components/Combobox/style.css +1 -1
- package/build/components/CommandPalette/style.css +1 -1
- package/build/components/CopyField/style.css +1 -1
- package/build/components/DataTable/style.css +1 -1
- package/build/components/DateField/index.d.ts +1 -1
- package/build/components/DateField/style.css +1 -1
- package/build/components/Directory/style.css +1 -1
- package/build/components/Disclosure/style.css +1 -1
- package/build/components/Divider/style.css +1 -1
- package/build/components/Drawer/style.css +1 -1
- package/build/components/EmptyState/style.css +1 -1
- package/build/components/Field/style.css +1 -1
- package/build/components/FileUpload/style.css +1 -1
- package/build/components/FilterBar/style.css +1 -1
- package/build/components/IconButton/index.cjs +1 -1
- package/build/components/IconButton/index.cjs.map +1 -1
- package/build/components/IconButton/index.d.ts +2 -2
- package/build/components/IconButton/index.js +1 -1
- package/build/components/IconButton/index.js.map +1 -1
- package/build/components/IconButton/style.css +1 -1
- package/build/components/Input/index.d.ts +1 -1
- package/build/components/Input/style.css +1 -1
- package/build/components/Kbd/style.css +1 -1
- package/build/components/Menu/style.css +1 -1
- package/build/components/Modal/style.css +1 -1
- package/build/components/PageHeader/style.css +1 -1
- package/build/components/Pagination/style.css +1 -1
- package/build/components/Panel/style.css +1 -1
- package/build/components/PasswordInput/style.css +1 -1
- package/build/components/Popover/style.css +1 -1
- package/build/components/Price/style.css +1 -1
- package/build/components/Progress/style.css +1 -1
- package/build/components/PromptDialog/style.css +1 -1
- package/build/components/QuantityStepper/style.css +1 -1
- package/build/components/Radio/style.css +1 -1
- package/build/components/RadioCard/style.css +1 -1
- package/build/components/RangeSlider/style.css +1 -1
- package/build/components/Rating/style.css +1 -1
- package/build/components/RatingInput/style.css +1 -1
- package/build/components/ScrollArea/style.css +1 -1
- package/build/components/SectionHeader/style.css +1 -1
- package/build/components/SegmentedControl/style.css +1 -1
- package/build/components/Select/index.cjs +1 -1
- package/build/components/Select/index.cjs.map +1 -1
- package/build/components/Select/index.js +28 -28
- package/build/components/Select/index.js.map +1 -1
- package/build/components/Select/style.css +1 -1
- package/build/components/Skeleton/style.css +1 -1
- package/build/components/Spinner/style.css +1 -1
- package/build/components/Stat/style.css +1 -1
- package/build/components/StatusDot/style.css +1 -1
- package/build/components/Steps/style.css +1 -1
- package/build/components/SummaryCard/style.css +1 -1
- package/build/components/Switch/index.d.ts +1 -1
- package/build/components/Switch/style.css +1 -1
- package/build/components/Table/style.css +1 -1
- package/build/components/TableCheckbox/style.css +1 -1
- package/build/components/Tabs/style.css +1 -1
- package/build/components/TagInput/style.css +1 -1
- package/build/components/TextLink/style.css +1 -1
- package/build/components/Textarea/style.css +1 -1
- package/build/components/Tile/style.css +1 -1
- package/build/components/Timeline/style.css +1 -1
- package/build/components/Toast/style.css +1 -1
- package/build/components/Tooltip/style.css +1 -1
- package/build/constants/breakpoint.constants.cjs +1 -1
- package/build/constants/breakpoint.constants.cjs.map +1 -1
- package/build/constants/breakpoint.constants.d.ts +13 -8
- package/build/constants/breakpoint.constants.js +7 -5
- package/build/constants/breakpoint.constants.js.map +1 -1
- package/build/enums/ui-size.enum.cjs.map +1 -1
- package/build/enums/ui-size.enum.d.ts +5 -2
- package/build/enums/ui-size.enum.js.map +1 -1
- package/build/enums/ui-variant.enum.cjs +1 -1
- package/build/enums/ui-variant.enum.cjs.map +1 -1
- package/build/enums/ui-variant.enum.d.ts +13 -13
- package/build/enums/ui-variant.enum.js +1 -1
- package/build/enums/ui-variant.enum.js.map +1 -1
- package/build/helpers/theme.helper.cjs +1 -23
- package/build/helpers/theme.helper.cjs.map +1 -1
- package/build/helpers/theme.helper.d.ts +13 -44
- package/build/helpers/theme.helper.js +31 -69
- package/build/helpers/theme.helper.js.map +1 -1
- package/build/hooks/useUiTheme.cjs +1 -1
- package/build/hooks/useUiTheme.cjs.map +1 -1
- package/build/hooks/useUiTheme.d.ts +4 -1
- package/build/hooks/useUiTheme.js +34 -28
- package/build/hooks/useUiTheme.js.map +1 -1
- package/build/hooks/useUiTokens.cjs +1 -1
- package/build/hooks/useUiTokens.cjs.map +1 -1
- package/build/hooks/useUiTokens.d.ts +8 -8
- package/build/hooks/useUiTokens.js +24 -27
- package/build/hooks/useUiTokens.js.map +1 -1
- package/build/index.cjs +1 -1
- package/build/index.d.ts +3 -5
- package/build/index.js +194 -202
- package/build/index.js.map +1 -1
- package/build/scss/_index.scss +4 -3
- package/build/scss/_mixins.scss +264 -146
- package/build/scss/_variables.scss +216 -24
- package/build/styles.css +67 -67
- package/build/theme/UiProvider.cjs +1 -1
- package/build/theme/UiProvider.cjs.map +1 -1
- package/build/theme/UiProvider.d.ts +10 -9
- package/build/theme/UiProvider.js +7 -25
- package/build/theme/UiProvider.js.map +1 -1
- package/build/theme/UiThemeScript.cjs +1 -1
- package/build/theme/UiThemeScript.cjs.map +1 -1
- package/build/theme/UiThemeScript.d.ts +13 -10
- package/build/theme/UiThemeScript.js +2 -2
- package/build/theme/UiThemeScript.js.map +1 -1
- package/build/theme/context.js +2 -2
- package/build/types/charts/core/palette.d.ts +6 -8
- package/build/types/chat/Legend/index.d.ts +3 -2
- package/build/types/theme.type.d.ts +6 -48
- package/build/types/types/theme.type.d.ts +6 -48
- package/package.json +10 -10
- package/build/scss/_colors.generated.scss +0 -119
- package/build/scss/_metrics.generated.scss +0 -49
- package/build/theme/initUi.cjs +0 -2
- package/build/theme/initUi.cjs.map +0 -1
- package/build/theme/initUi.d.ts +0 -7
- package/build/theme/initUi.js +0 -10
- package/build/theme/initUi.js.map +0 -1
- package/build/theme/palette.cjs +0 -2
- package/build/theme/palette.cjs.map +0 -1
- package/build/theme/palette.d.ts +0 -219
- package/build/theme/palette.js +0 -148
- package/build/theme/palette.js.map +0 -1
- package/build/theme/tokens.cjs +0 -2
- package/build/theme/tokens.cjs.map +0 -1
- package/build/theme/tokens.d.ts +0 -225
- package/build/theme/tokens.js +0 -384
- package/build/theme/tokens.js.map +0 -1
- package/build/types/theme/palette.d.ts +0 -219
- package/build/types/theme/tokens.d.ts +0 -225
package/README.md
CHANGED
|
@@ -1,32 +1,46 @@
|
|
|
1
1
|
# @handijit/ui
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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/
|
|
25
|
-
**peer**
|
|
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
|
-
##
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
import '@handijit/ui/styles.css';
|
|
50
|
-
```
|
|
65
|
+
### A subset of the CSS
|
|
51
66
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
`/combobox
|
|
69
|
-
|
|
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
|
-
##
|
|
91
|
+
## Theme
|
|
74
92
|
|
|
75
|
-
###
|
|
93
|
+
### The choice rides on `<html data-theme>`
|
|
76
94
|
|
|
77
95
|
```html
|
|
78
|
-
<html data-
|
|
96
|
+
<html data-theme="dark"></html>
|
|
79
97
|
```
|
|
80
98
|
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
###
|
|
105
|
+
### Write it before the first paint: `UiThemeScript`
|
|
85
106
|
|
|
86
107
|
```tsx
|
|
87
|
-
// app/layout.tsx
|
|
108
|
+
// app/layout.tsx, a server layout, inside <head>.
|
|
88
109
|
import { UiThemeScript } from '@handijit/ui';
|
|
89
110
|
|
|
90
111
|
<head>
|
|
91
|
-
<UiThemeScript
|
|
112
|
+
<UiThemeScript nonce={nonce} />
|
|
92
113
|
</head>;
|
|
93
114
|
```
|
|
94
115
|
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
###
|
|
125
|
+
### Content Security Policy
|
|
104
126
|
|
|
105
|
-
|
|
127
|
+
The boot script is the **only** element this library injects. It injects no
|
|
128
|
+
`<style>` element at all.
|
|
106
129
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
120
|
-
<App />
|
|
121
|
-
</UiProvider>;
|
|
138
|
+
const policy = `script-src 'self' '${THEME_BOOT_SCRIPT_HASH}'`;
|
|
122
139
|
```
|
|
123
140
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
(
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
152
|
+
```tsx
|
|
153
|
+
const { scheme, preference, setScheme, toggle, isReady } = useUiTheme();
|
|
154
|
+
```
|
|
142
155
|
|
|
143
|
-
|
|
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
|
-
|
|
146
|
-
`
|
|
164
|
+
A three-state switch (Light / Dark / System) reads `preference`; if it read
|
|
165
|
+
`scheme`, "System" would show "Dark" as selected.
|
|
147
166
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
###
|
|
174
|
+
### Brand and product overrides
|
|
153
175
|
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
###
|
|
193
|
+
### Overriding our CSS: `@layer ui`
|
|
166
194
|
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
/*
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
`
|
|
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
|
-
##
|
|
254
|
+
## Labels
|
|
242
255
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
(Ctrl/Cmd/
|
|
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
|
-
|
|
350
|
-
gibi) **verilmez** — kullanıcıyı okuduğu bölümden sayfanın tepesine atar.
|
|
346
|
+
### Sticky header
|
|
351
347
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
374
|
-
|
|
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
|
-
##
|
|
379
|
-
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
|
|
|
383
|
-
|
|
|
384
|
-
|
|
|
385
|
-
|
|
|
386
|
-
|
|
|
387
|
-
|
|
|
388
|
-
|
|
|
389
|
-
|
|
|
390
|
-
|
|
|
391
|
-
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
425
|
+
---
|
|
397
426
|
|
|
398
|
-
|
|
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
|
-
|
|
407
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
`
|
|
477
|
-
`
|
|
478
|
-
`
|
|
479
|
-
|
|
480
|
-
`
|
|
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
|
|
485
|
-
|
|
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
|
|
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'; //
|
|
492
|
-
import '@handijit/ui/charts.css'; //
|
|
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
|
|
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`:
|
|
511
|
-
|
|
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
|
-
##
|
|
521
|
+
## Electron
|
|
516
522
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
592
|
-
|
|
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
|
|
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
|
-
|
|
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`
|
|
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
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
|
|
713
|
-
|
|
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
|
-
|
|
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
|
-
|
|
726
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
|
762
|
-
|
|
763
|
-
| `LineChart` · `AreaChart` |
|
|
764
|
-
| `BarChart`
|
|
765
|
-
| `ComposedChart`
|
|
766
|
-
| `PieChart`
|
|
767
|
-
| `ScatterPlot`
|
|
768
|
-
| `Heatmap`
|
|
769
|
-
| `BoxPlot` · `Histogram`
|
|
770
|
-
| `RadarChart`
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
(`width`
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
|
|
817
|
-
:
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
|
|
|
833
|
-
|
|
834
|
-
| `
|
|
835
|
-
| `
|
|
836
|
-
| `
|
|
837
|
-
| `
|
|
838
|
-
| `
|
|
839
|
-
|
|
|
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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
(
|
|
845
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
862
|
-
|
|
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
|
-
##
|
|
778
|
+
## Design contract
|
|
865
779
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
##
|
|
785
|
+
## License
|
|
872
786
|
|
|
873
787
|
MIT
|