@skygraph/styles 0.1.0 → 0.2.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.
@@ -1,4 +1,25 @@
1
+ /* Checkbox — custom box element on top of a visually-hidden native input.
2
+ *
3
+ * The native `<input type="checkbox">` is kept in the DOM (for keyboard,
4
+ * form-association, accessibility, indeterminate) but visually hidden.
5
+ * A sibling `.sg-checkbox-box` element paints the actual UI so demos
6
+ * can rebrand through dedicated `--sg-checkbox-*` tokens without
7
+ * fighting the browser's `accent-color` limitations.
8
+ */
9
+
1
10
  .sg-checkbox {
11
+ /* Component-local tokens. Override these (not the globals) to rebrand
12
+ * one group of checkboxes — keeps `--sg-color-primary` intact for the
13
+ * rest of the page. */
14
+ --sg-checkbox-size: 16px;
15
+ --sg-checkbox-radius: 4px;
16
+ --sg-checkbox-border: var(--sg-color-border);
17
+ --sg-checkbox-border-hover: var(--sg-color-primary);
18
+ --sg-checkbox-bg: var(--sg-color-bg);
19
+ --sg-checkbox-bg-checked: var(--sg-color-primary);
20
+ --sg-checkbox-color-checked: var(--sg-color-white);
21
+
22
+ position: relative;
2
23
  display: inline-flex;
3
24
  align-items: center;
4
25
  gap: var(--sg-padding-sm);
@@ -13,11 +34,75 @@
13
34
  cursor: not-allowed;
14
35
  }
15
36
 
37
+ /* Visually hide the native checkbox but keep it focusable & screen-
38
+ * readable. Pointer events are forwarded to the wrapping <label>. */
16
39
  .sg-checkbox-input {
17
- width: 16px;
18
- height: 16px;
19
- accent-color: var(--sg-color-primary);
20
- cursor: inherit;
40
+ position: absolute;
41
+ width: var(--sg-checkbox-size);
42
+ height: var(--sg-checkbox-size);
43
+ margin: 0;
44
+ opacity: 0;
45
+ pointer-events: none;
46
+ }
47
+
48
+ .sg-checkbox-box {
49
+ position: relative;
50
+ flex-shrink: 0;
51
+ display: inline-block;
52
+ width: var(--sg-checkbox-size);
53
+ height: var(--sg-checkbox-size);
54
+ border: 1px solid var(--sg-checkbox-border);
55
+ border-radius: var(--sg-checkbox-radius);
56
+ background: var(--sg-checkbox-bg);
57
+ transition:
58
+ background var(--sg-transition-duration) var(--sg-transition-timing),
59
+ border-color var(--sg-transition-duration) var(--sg-transition-timing);
60
+ }
61
+
62
+ .sg-checkbox:hover .sg-checkbox-box {
63
+ border-color: var(--sg-checkbox-border-hover);
64
+ }
65
+
66
+ .sg-checkbox-input:focus-visible ~ .sg-checkbox-box {
67
+ outline: 2px solid var(--sg-color-primary);
68
+ outline-offset: 2px;
69
+ }
70
+
71
+ .sg-checkbox-input:checked ~ .sg-checkbox-box,
72
+ .sg-checkbox-input:indeterminate ~ .sg-checkbox-box {
73
+ background: var(--sg-checkbox-bg-checked);
74
+ border-color: var(--sg-checkbox-bg-checked);
75
+ }
76
+
77
+ /* Tick (checked) */
78
+ .sg-checkbox-input:checked ~ .sg-checkbox-box::after {
79
+ content: '';
80
+ position: absolute;
81
+ left: 5px;
82
+ top: 1px;
83
+ width: 5px;
84
+ height: 9px;
85
+ border: solid var(--sg-checkbox-color-checked);
86
+ border-width: 0 2px 2px 0;
87
+ transform: rotate(45deg);
88
+ }
89
+
90
+ /* Bar (indeterminate) */
91
+ .sg-checkbox-input:indeterminate ~ .sg-checkbox-box::after {
92
+ content: '';
93
+ position: absolute;
94
+ left: 3px;
95
+ right: 3px;
96
+ top: 50%;
97
+ height: 2px;
98
+ background: var(--sg-checkbox-color-checked);
99
+ transform: translateY(-50%);
100
+ }
101
+
102
+ .sg-checkbox-disabled .sg-checkbox-box {
103
+ background: var(--sg-color-bg-disabled, var(--sg-color-bg-secondary));
104
+ border-color: var(--sg-color-border);
105
+ opacity: 0.6;
21
106
  }
22
107
 
23
108
  .sg-checkbox-label {
@@ -220,13 +220,17 @@
220
220
 
221
221
  .sg-diagram-edge {
222
222
  stroke: var(--sg-diagram-edge-color);
223
- stroke-width: 1.75;
223
+ stroke-width: 1.5;
224
224
  fill: none;
225
- /* `linejoin` keeps orthogonal-routed edges crisp at corners; `linecap`
226
- * rounds the start/end so an edge feels finished where it meets the
227
- * node border instead of clipping abruptly. */
225
+ /* `linejoin: round` smooths orthogonal corners. `linecap: butt`
226
+ * means the path tip ends flush combined with the marker-end
227
+ * sitting at `refX/refY = 0`, the arrow glyph picks up exactly
228
+ * where the line stops, with no visible "pill" cap protruding
229
+ * beyond the arrowhead. Round caps used to leak a half-circle
230
+ * over the marker base and that was visible on every screenshot
231
+ * the user shared. */
228
232
  stroke-linejoin: round;
229
- stroke-linecap: round;
233
+ stroke-linecap: butt;
230
234
  transition:
231
235
  stroke var(--sg-transition-fast),
232
236
  stroke-width var(--sg-transition-fast);
@@ -236,7 +240,7 @@
236
240
  * naturally thins at the inflection point, which makes the same
237
241
  * stroke-width look lighter than its orthogonal counterpart. */
238
242
  .sg-diagram-edge-bezier {
239
- stroke-width: 2;
243
+ stroke-width: 1.75;
240
244
  }
241
245
 
242
246
  /* Dashed for manual-routed waypoints — visually distinguishes a hand-
@@ -248,7 +252,7 @@
248
252
  .sg-diagram-edge-hit:hover + .sg-diagram-edge,
249
253
  g[data-edge-id]:hover .sg-diagram-edge {
250
254
  stroke: var(--sg-diagram-edge-hover-stroke);
251
- stroke-width: 2.5;
255
+ stroke-width: 2.25;
252
256
  }
253
257
 
254
258
  /* Highlight edges connected to a selected node when the selection is
@@ -257,26 +261,35 @@ g[data-edge-id]:hover .sg-diagram-edge {
257
261
  * `selected` color on `:focus-within` of the SVG group. */
258
262
  g[data-edge-id]:focus-within .sg-diagram-edge {
259
263
  stroke: var(--sg-diagram-edge-color-selected);
260
- stroke-width: 2.5;
264
+ stroke-width: 2.25;
261
265
  }
262
266
 
263
267
  /* ─── Arrow markers ─────────────────────────────────────────────────── */
264
268
  /*
265
- * SVG markers don't inherit `stroke` from the path automatically the
266
- * `context-stroke` keyword in the marker `fill` solves it where
267
- * supported, and the fallback below keeps Firefox / Safari < TP rendering
268
- * the arrow in the right colour by using `currentColor`.
269
+ * The arrowhead is an "open V" two strokes meeting at the marker
270
+ * anchor, not a filled triangle. That gives the arrow visual cohesion
271
+ * with the line (same stroke), keeps it readable on dense graphs, and
272
+ * matches the look used by React Flow / draw.io / Lucidchart.
269
273
  *
270
- * `.sg-diagram-edges` carries `currentColor` from the token so the arrow
271
- * inherits the default colour; per-state overrides (`:hover`) below adjust
272
- * `currentColor` so the arrow lights up together with the line.
274
+ * SVG markers don't inherit `stroke` from the path automatically. We
275
+ * use `context-stroke` in the marker so modern browsers paint the arrow
276
+ * with the line colour, and rely on `currentColor` below as a fallback
277
+ * for Firefox / Safari versions that don't yet support context-stroke.
278
+ *
279
+ * `.sg-diagram-edges { color: ... }` is the lever per-edge state hooks
280
+ * (`:hover`) modulate so the arrow lights up together with the line.
273
281
  */
274
282
  .sg-diagram-edges {
275
283
  color: var(--sg-diagram-edge-color);
276
284
  }
277
285
 
286
+ /* Marker glyph: stroke takes the inherited `currentColor` so old
287
+ * browsers without context-stroke still render the arrow in the right
288
+ * colour. `fill: none` is essential — the marker is an open polyline,
289
+ * not a triangle. */
278
290
  .sg-diagram-arrow-fill {
279
- fill: currentColor;
291
+ stroke: currentColor;
292
+ fill: none;
280
293
  }
281
294
 
282
295
  g[data-edge-id]:hover {
@@ -51,6 +51,11 @@
51
51
  color: var(--sg-color-text-secondary);
52
52
  border-right: 1px solid var(--sg-color-border-secondary);
53
53
  box-sizing: border-box;
54
+ /* Clip the label when the tick is narrower than the rendered text
55
+ * (e.g. day scale with columnWidth=24) — without this, adjacent
56
+ * labels visually pile up on each other. */
57
+ overflow: hidden;
58
+ white-space: nowrap;
54
59
  }
55
60
 
56
61
  .sg-gantt-grid {
@@ -123,7 +128,7 @@
123
128
  /* Drop-shadow on the text increases legibility on saturated bar
124
129
  * fills (esp. the bright primary blue). Cheap, single-line filter
125
130
  * that doesn't disturb the bar background. */
126
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.20);
131
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
127
132
  }
128
133
 
129
134
  .sg-gantt-bar-resize {
@@ -23,7 +23,8 @@
23
23
  background: var(--sg-input-bg);
24
24
  border: 1px solid var(--sg-input-border);
25
25
  border-radius: var(--sg-input-radius);
26
- transition: color var(--sg-transition-duration) var(--sg-transition-timing),
26
+ transition:
27
+ color var(--sg-transition-duration) var(--sg-transition-timing),
27
28
  background var(--sg-transition-duration) var(--sg-transition-timing),
28
29
  border-color var(--sg-transition-duration) var(--sg-transition-timing),
29
30
  box-shadow var(--sg-transition-duration) var(--sg-transition-timing);
@@ -108,3 +109,77 @@
108
109
  bottom: 0;
109
110
  margin: auto 0;
110
111
  }
112
+
113
+ /* ---- Prefix / Suffix ---- */
114
+ .sg-input-prefix,
115
+ .sg-input-suffix {
116
+ display: inline-flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ color: var(--sg-color-text-secondary);
120
+ pointer-events: none;
121
+ }
122
+
123
+ .sg-input-prefix {
124
+ position: absolute;
125
+ left: var(--sg-padding-sm);
126
+ top: 0;
127
+ bottom: 0;
128
+ }
129
+
130
+ .sg-input-suffix {
131
+ position: absolute;
132
+ right: var(--sg-padding-sm);
133
+ top: 0;
134
+ bottom: 0;
135
+ }
136
+
137
+ .sg-input-wrapper:has(.sg-input-prefix) .sg-input {
138
+ padding-left: calc(var(--sg-padding-md) + 16px);
139
+ }
140
+
141
+ .sg-input-wrapper:has(.sg-input-suffix) .sg-input {
142
+ padding-right: calc(var(--sg-padding-md) + 16px);
143
+ }
144
+
145
+ /* ---- Clear button ---- */
146
+ .sg-input-wrapper:has(.sg-input-clear) .sg-input {
147
+ padding-right: calc(var(--sg-padding-md) + 18px);
148
+ }
149
+
150
+ .sg-input-clear {
151
+ position: absolute;
152
+ right: var(--sg-padding-xs);
153
+ top: 50%;
154
+ transform: translateY(-50%);
155
+ display: inline-flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ width: 18px;
159
+ height: 18px;
160
+ padding: 0;
161
+ margin: 0;
162
+ border: none;
163
+ border-radius: 50%;
164
+ background: var(--sg-color-fill-tertiary, rgba(0, 0, 0, 0.06));
165
+ color: var(--sg-color-text-secondary);
166
+ font-size: 14px;
167
+ line-height: 1;
168
+ cursor: pointer;
169
+ opacity: 0.7;
170
+ transition:
171
+ background var(--sg-transition-duration) var(--sg-transition-timing),
172
+ color var(--sg-transition-duration) var(--sg-transition-timing),
173
+ opacity var(--sg-transition-duration) var(--sg-transition-timing);
174
+ }
175
+
176
+ .sg-input-clear:hover {
177
+ background: var(--sg-color-fill, rgba(0, 0, 0, 0.15));
178
+ color: var(--sg-color-text);
179
+ opacity: 1;
180
+ }
181
+
182
+ .sg-input-clear:focus-visible {
183
+ outline: 2px solid var(--sg-color-primary);
184
+ outline-offset: 2px;
185
+ }
@@ -1,3 +1,10 @@
1
+ /* Radio — custom painted dot on top of a visually-hidden native input.
2
+ *
3
+ * Like Checkbox, the native `<input type="radio">` stays in the DOM for
4
+ * keyboard / form / a11y but is visually hidden. `.sg-radio-box` paints
5
+ * the circle so demos can rebrand through `--sg-radio-*` tokens without
6
+ * fighting `accent-color` limitations across browsers. */
7
+
1
8
  .sg-radio-group {
2
9
  display: flex;
3
10
  gap: var(--sg-padding-md);
@@ -8,12 +15,21 @@
8
15
  }
9
16
 
10
17
  .sg-radio {
18
+ --sg-radio-size: 16px;
19
+ --sg-radio-border: var(--sg-color-border);
20
+ --sg-radio-border-hover: var(--sg-color-primary);
21
+ --sg-radio-bg: var(--sg-color-bg);
22
+ --sg-radio-bg-checked: var(--sg-color-primary);
23
+ --sg-radio-dot-color: var(--sg-color-white);
24
+
25
+ position: relative;
11
26
  display: inline-flex;
12
27
  align-items: center;
13
28
  gap: var(--sg-padding-xs);
14
29
  cursor: pointer;
15
30
  font-size: var(--sg-font-size);
16
31
  color: var(--sg-color-text);
32
+ line-height: var(--sg-line-height);
17
33
  }
18
34
 
19
35
  .sg-radio-disabled {
@@ -22,11 +38,52 @@
22
38
  }
23
39
 
24
40
  .sg-radio-input {
25
- width: 16px;
26
- height: 16px;
41
+ position: absolute;
42
+ width: var(--sg-radio-size);
43
+ height: var(--sg-radio-size);
27
44
  margin: 0;
28
- accent-color: var(--sg-color-primary);
29
- cursor: inherit;
45
+ opacity: 0;
46
+ pointer-events: none;
47
+ }
48
+
49
+ .sg-radio-box {
50
+ position: relative;
51
+ flex-shrink: 0;
52
+ display: inline-block;
53
+ width: var(--sg-radio-size);
54
+ height: var(--sg-radio-size);
55
+ border: 1px solid var(--sg-radio-border);
56
+ border-radius: 50%;
57
+ background: var(--sg-radio-bg);
58
+ transition:
59
+ background var(--sg-transition-duration) var(--sg-transition-timing),
60
+ border-color var(--sg-transition-duration) var(--sg-transition-timing);
61
+ }
62
+
63
+ .sg-radio:hover .sg-radio-box {
64
+ border-color: var(--sg-radio-border-hover);
65
+ }
66
+
67
+ .sg-radio-input:focus-visible ~ .sg-radio-box {
68
+ outline: 2px solid var(--sg-color-primary);
69
+ outline-offset: 2px;
70
+ }
71
+
72
+ .sg-radio-input:checked ~ .sg-radio-box {
73
+ background: var(--sg-radio-bg-checked);
74
+ border-color: var(--sg-radio-bg-checked);
75
+ }
76
+
77
+ .sg-radio-input:checked ~ .sg-radio-box::after {
78
+ content: '';
79
+ position: absolute;
80
+ top: 50%;
81
+ left: 50%;
82
+ width: 6px;
83
+ height: 6px;
84
+ border-radius: 50%;
85
+ background: var(--sg-radio-dot-color);
86
+ transform: translate(-50%, -50%);
30
87
  }
31
88
 
32
89
  .sg-radio-label {
@@ -71,6 +71,11 @@
71
71
  .sg-rcal-main {
72
72
  background: var(--sg-color-bg);
73
73
  position: relative;
74
+ /* Clip absolutely positioned blocks (assignments / ticks) so they
75
+ * can't bleed onto the sidebar when their dates fall outside the
76
+ * visible `range`. Interactive drag also clamps to range, so this
77
+ * is a safety net for props-supplied out-of-range assignments. */
78
+ overflow: hidden;
74
79
  }
75
80
 
76
81
  .sg-rcal-header {
@@ -183,7 +188,7 @@
183
188
  transform: none;
184
189
  box-shadow:
185
190
  0 0 0 1px var(--sg-rcal-conflict-color),
186
- 0 2px 6px rgba(245, 34, 45, 0.20);
191
+ 0 2px 6px rgba(245, 34, 45, 0.2);
187
192
  }
188
193
 
189
194
  .sg-rcal-assignment-draggable {
@@ -75,7 +75,9 @@
75
75
  .sg-sfe-body {
76
76
  display: grid;
77
77
  grid-template-columns: var(--sg-sfe-palette-width) 1fr var(--sg-sfe-inspector-width);
78
+ flex: 1;
78
79
  min-height: 480px;
80
+ overflow: hidden;
79
81
  }
80
82
 
81
83
  /* Palette (left) ---------------------------------------------------------- */
@@ -113,7 +115,8 @@
113
115
  border-radius: var(--sg-sfe-radius);
114
116
  cursor: grab;
115
117
  user-select: none;
116
- transition: border-color var(--sg-transition-duration) var(--sg-transition-timing),
118
+ transition:
119
+ border-color var(--sg-transition-duration) var(--sg-transition-timing),
117
120
  box-shadow var(--sg-transition-duration) var(--sg-transition-timing);
118
121
  }
119
122
 
@@ -162,6 +165,8 @@
162
165
  display: flex;
163
166
  flex-direction: column;
164
167
  min-width: 0;
168
+ min-height: 0;
169
+ overflow: hidden;
165
170
  background: var(--sg-sfe-bg);
166
171
  }
167
172
 
@@ -204,7 +209,8 @@
204
209
  border: 1px solid transparent;
205
210
  border-radius: var(--sg-sfe-radius);
206
211
  cursor: pointer;
207
- transition: border-color var(--sg-transition-duration) var(--sg-transition-timing),
212
+ transition:
213
+ border-color var(--sg-transition-duration) var(--sg-transition-timing),
208
214
  background var(--sg-transition-duration) var(--sg-transition-timing);
209
215
  }
210
216
 
@@ -370,6 +376,8 @@
370
376
  border-top: 1px solid var(--sg-sfe-border-secondary);
371
377
  background: var(--sg-sfe-bg-secondary);
372
378
  max-height: 320px;
379
+ flex-shrink: 0;
380
+ overflow-y: auto;
373
381
  }
374
382
 
375
383
  .sg-sfe-tabs {
@@ -1,4 +1,10 @@
1
1
  .sg-switch {
2
+ /* Off state uses a dedicated token so demos can rebrand without
3
+ * touching `--sg-color-text-disabled` (which is also used by the
4
+ * disabled visual state — overriding it makes every control look
5
+ * disabled). */
6
+ --sg-switch-bg: var(--sg-color-fill, rgba(0, 0, 0, 0.25));
7
+ --sg-switch-bg-checked: var(--sg-color-primary);
2
8
  position: relative;
3
9
  display: inline-flex;
4
10
  align-items: center;
@@ -7,7 +13,7 @@
7
13
  padding: 0;
8
14
  border: none;
9
15
  border-radius: 11px;
10
- background: var(--sg-color-text-disabled);
16
+ background: var(--sg-switch-bg);
11
17
  cursor: pointer;
12
18
  transition: background var(--sg-transition-duration) var(--sg-transition-timing);
13
19
  }
@@ -19,7 +25,7 @@
19
25
  }
20
26
 
21
27
  .sg-switch-checked {
22
- background: var(--sg-color-primary);
28
+ background: var(--sg-switch-bg-checked);
23
29
  }
24
30
 
25
31
  .sg-switch-disabled {
@@ -71,6 +71,25 @@
71
71
  position: relative;
72
72
  }
73
73
 
74
+ /* Mobile fallback — when there's no explicit `scroll.x`, the table tries
75
+ * to flex into the available width which, on narrow viewports, collapses
76
+ * cells into illegible vertical stacks. Forcing horizontal overflow with
77
+ * `width: max-content` on the inner grid restores tabular layout and
78
+ * exposes a native horizontal scrollbar so users can pan instead of
79
+ * losing data. Only applies when `.sg-table-scroll-x` wasn't already set
80
+ * (which means the consumer opted-in to a controlled scroll width). */
81
+ @media (max-width: 640px) {
82
+ .sg-table-scroll:not(.sg-table-scroll-x) {
83
+ overflow-x: auto;
84
+ }
85
+ .sg-table-scroll:not(.sg-table-scroll-x) .sg-table-grid,
86
+ .sg-table-scroll:not(.sg-table-scroll-x) .sg-table-pinned-top,
87
+ .sg-table-scroll:not(.sg-table-scroll-x) .sg-table-pinned-bottom {
88
+ min-width: 100%;
89
+ width: max-content;
90
+ }
91
+ }
92
+
74
93
  /* Grid base.
75
94
  * The grid carries a solid base background so body cells (transparent by
76
95
  * default) and fixed-sticky cells draw over the same colour. Without this,
@@ -117,6 +136,19 @@
117
136
  display: flex;
118
137
  align-items: center;
119
138
  gap: var(--sg-padding-xs);
139
+ /* В узких колонках с одновременно sortable + filters заголовок мог
140
+ * "наезжать" на filter-trigger / resize-handle: длинный title + ▲▼
141
+ * не помещались, sort и filter сливались. Скрываем переполнение и
142
+ * усекаем сам title через .sg-table-th-title — иконки всегда видны. */
143
+ overflow: hidden;
144
+ }
145
+
146
+ .sg-table-th-title {
147
+ flex: 0 1 auto;
148
+ min-width: 0;
149
+ overflow: hidden;
150
+ text-overflow: ellipsis;
151
+ white-space: nowrap;
120
152
  }
121
153
 
122
154
  .sg-table-th-sortable {
@@ -143,6 +175,13 @@
143
175
  line-height: 8px;
144
176
  color: var(--sg-color-text-quaternary, var(--sg-color-text-tertiary));
145
177
  transition: color var(--sg-transition-duration);
178
+ /* Defensive: keep the sort glyph compact even if the locale ships a long
179
+ * aria-quality label instead of a single ▲/▼ glyph. The cell would
180
+ * otherwise stretch by the full label width and clash with the column
181
+ * title or filter trigger. */
182
+ max-width: 1em;
183
+ overflow: hidden;
184
+ white-space: nowrap;
146
185
  }
147
186
 
148
187
  .sg-table-sort-current {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skygraph/styles",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "SkyGraph framework-agnostic CSS — tokens, themes, transitions, print and 60+ component stylesheets",
5
5
  "license": "MIT",
6
6
  "type": "module",