astryx-dracula 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/BRAND.md +30 -18
  2. package/README.md +22 -11
  3. package/USAGE.md +29 -10
  4. package/astryx-dracula.js +19 -13
  5. package/astryx-dracula.variants.d.ts +15 -0
  6. package/package.json +59 -17
  7. package/shared/auth-chrome-config.ts +18 -0
  8. package/shared/auth-chrome.tsx +25 -0
  9. package/shared/auth-copy.ts +22 -0
  10. package/shared/chaptered-doc-config.ts +92 -0
  11. package/shared/chaptered-doc.tsx +332 -0
  12. package/shared/chart-hues.ts +27 -0
  13. package/shared/chart-labels.tsx +44 -0
  14. package/shared/chart-legend.tsx +100 -0
  15. package/shared/chart-panel-style.ts +13 -0
  16. package/shared/data-bar.tsx +160 -0
  17. package/shared/gallery-image.ts +20 -0
  18. package/shared/login-demo.ts +7 -0
  19. package/shared/metric-delta.tsx +36 -0
  20. package/shared/order-desk.tsx +273 -0
  21. package/shared/revenue-chart.tsx +174 -0
  22. package/shared/scene-castle.tsx +322 -0
  23. package/shared/scene-frame.tsx +88 -0
  24. package/shared/scene-hues.ts +49 -0
  25. package/shared/scene-tile.tsx +128 -0
  26. package/shared/settings-data.ts +125 -0
  27. package/shared/settings-rows.tsx +179 -0
  28. package/shared/sparkline.tsx +94 -0
  29. package/shared/sso-icons.tsx +30 -0
  30. package/templates/ai-chat-landing.tsx +67 -32
  31. package/templates/ai-chat.tsx +41 -20
  32. package/templates/blank.tsx +5 -2
  33. package/templates/centered-hero.tsx +15 -124
  34. package/templates/classic-gallery.tsx +21 -65
  35. package/templates/contact-form.tsx +62 -40
  36. package/templates/dashboard-portfolio.tsx +146 -227
  37. package/templates/dashboard.tsx +123 -121
  38. package/templates/detail-page.tsx +101 -95
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +85 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +33 -21
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +54 -74
  45. package/templates/editor.tsx +119 -89
  46. package/templates/file-explorer.tsx +133 -84
  47. package/templates/form-two-column.tsx +62 -124
  48. package/templates/gallery-hero.tsx +17 -171
  49. package/templates/ide.tsx +27 -21
  50. package/templates/incident-console.tsx +105 -69
  51. package/templates/kanban-board.tsx +100 -41
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +38 -41
  54. package/templates/login-card.tsx +59 -86
  55. package/templates/login-split.tsx +88 -128
  56. package/templates/login-sso.tsx +107 -68
  57. package/templates/login.tsx +108 -52
  58. package/templates/messaging-shell.tsx +172 -143
  59. package/templates/mixed-gallery.tsx +31 -130
  60. package/templates/payment-form.tsx +369 -289
  61. package/templates/product-detail.tsx +40 -57
  62. package/templates/product-gallery.tsx +31 -40
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +145 -286
  66. package/templates/settings-sidebar.tsx +179 -267
  67. package/templates/settings.tsx +42 -13
  68. package/templates/shell-nav.tsx +32 -23
  69. package/templates/shell-side-nav.tsx +30 -19
  70. package/templates/shell-top-nav.tsx +81 -43
  71. package/templates/side-gallery.tsx +39 -93
  72. package/templates/table-grouped.tsx +63 -33
  73. package/templates/table-page-chart.tsx +24 -297
  74. package/templates/table-page-heatmap-status.tsx +157 -91
  75. package/templates/table-page-shoe-store-heatmap.tsx +19 -288
  76. package/templates/table-page.tsx +24 -6
  77. package/templates/table.tsx +24 -7
  78. package/templates/tech-report.template.mjs +6 -0
  79. package/templates/tech-report.tsx +1486 -0
  80. package/templates/theme-showcase.template.mjs +1 -1
  81. package/templates/theme-showcase.tsx +313 -249
  82. package/theme.css +76 -14
  83. package/tokens.css +425 -30
@@ -1,4 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // AR*10 is the live count: GALLERY_SCENES has 10 entries and a bare .map renders
4
+ // all 10, with no slice. The header said 9 while 10 rendered.
5
+ // L > LC[p=6] > G[c={min:280} g8 a=center] > (V[g6] > (V[g3] > Tx"AFTER DARK"[t=supporting] + Hd"Make every night"[level=1] + Tx"The smallest rituals"[t=body]) + B.primary"Explore the night" + (V[g4] > D + (H[g6] > (V > Tx"12k+"[t=display-3] + Tx"Night shots"[t=supporting])*3))) + (G[c3 g3] > AR*10)
2
6
 
3
7
  import type {CSSProperties} from 'react';
4
8
  import {VStack, HStack, Layout, LayoutContent} from '@astryxdesign/core/Layout';
@@ -7,103 +11,33 @@ import {Button} from '@astryxdesign/core/Button';
7
11
  import {AspectRatio} from '@astryxdesign/core/AspectRatio';
8
12
  import {Grid} from '@astryxdesign/core/Grid';
9
13
  import {Divider} from '@astryxdesign/core/Divider';
10
-
11
- // NOTE: The only custom styling here is gallery fill + corner radius. It
12
- // exists because Astryx has no image primitive — AspectRatio exposes no
13
- // objectFit or radius props and there's no Image. Tracked in issue #2582;
14
- // replace these with component props once it lands.
15
- // Fills the AspectRatio box. No objectFit prop on AspectRatio (#2582).
16
- const galleryImage: CSSProperties = {
17
- width: '100%',
18
- height: '100%',
19
- display: 'block',
20
- };
21
- // Rounds the gallery corners. No radius prop on AspectRatio (#2582);
22
- // overflow clip masks the SVG scene to the rounded corners.
23
- const galleryImageClip: CSSProperties = {
24
- borderRadius: 'var(--radius-container)',
25
- overflow: 'clip',
26
- };
14
+ import {galleryImageClip} from 'astryx-dracula/shared/gallery-image';
15
+ import {SceneTile} from 'astryx-dracula/shared/scene-tile';
16
+ import {SCENE_TILE_ALTS} from 'astryx-dracula/shared/scene-hues';
17
+ import type {SceneHue} from 'astryx-dracula/shared/scene-hues';
27
18
 
28
19
  // ─── Gallery Data ─────────────────────────────────────────────────────────────
29
20
 
30
- // One Dracula accent per tile from the fixed categorical vocabulary.
31
- const GALLERY_SCENES = [
32
- {alt: 'Moonlit ridge trail under a harvest moon', hue: 'var(--dracula-cyan)'},
33
- {alt: 'Late portrait in violet lamplight', hue: 'var(--dracula-pink)'},
34
- {
35
- alt: 'Lamplit reading nook after midnight',
36
- hue: 'var(--dracula-yellow)',
37
- },
38
- {alt: 'Fog rolling over the night pines', hue: 'var(--dracula-purple)'},
39
- {alt: 'Dancer caught mid-step in stage pink', hue: 'var(--dracula-pink)'},
40
- {
41
- alt: 'Kitchen table set for a midnight feast',
42
- hue: 'var(--dracula-orange)',
43
- },
44
- {alt: 'Castle silhouette over sleeping hills', hue: 'var(--dracula-cyan)'},
45
- {alt: 'Attic window glowing amber at 2am', hue: 'var(--dracula-yellow)'},
46
- {alt: 'Cellar shelves lined with bottled dusk', hue: 'var(--dracula-green)'},
21
+ // One Dracula accent per tile from the fixed categorical vocabulary; alts are
22
+ // shared with classic-gallery via SCENE_TILE_ALTS.
23
+ const GALLERY_SCENES: Array<{alt: string; hue: SceneHue}> = [
24
+ {alt: SCENE_TILE_ALTS[0], hue: 'var(--dracula-cyan)'},
25
+ {alt: SCENE_TILE_ALTS[1], hue: 'var(--dracula-pink)'},
26
+ {alt: SCENE_TILE_ALTS[2], hue: 'var(--dracula-yellow)'},
27
+ {alt: SCENE_TILE_ALTS[3], hue: 'var(--dracula-green)'},
28
+ {alt: SCENE_TILE_ALTS[4], hue: 'var(--dracula-pink)'},
29
+ {alt: SCENE_TILE_ALTS[5], hue: 'var(--dracula-orange)'},
30
+ {alt: SCENE_TILE_ALTS[6], hue: 'var(--dracula-cyan)'},
31
+ {alt: SCENE_TILE_ALTS[7], hue: 'var(--dracula-yellow)'},
32
+ {alt: SCENE_TILE_ALTS[8], hue: 'var(--dracula-green)'},
47
33
  ];
48
34
 
49
- // ─── Gallery Art ────────────────────────────────────────────────────────────
50
-
51
- function NightScene({alt, hue, index}: {alt: string; hue: string; index: number}) {
52
- const moonX = 90 + ((index * 53) % 220);
53
- const moonY = 62 + ((index * 29) % 60);
54
- const hillA = 190 + ((index * 13) % 40);
55
- const hillB = 215 + ((index * 17) % 40);
56
- return (
57
- <svg
58
- viewBox="0 0 400 300"
59
- preserveAspectRatio="xMidYMid slice"
60
- style={galleryImage}
61
- role="img"
62
- aria-label={alt}>
63
- <rect width="400" height="300" fill="var(--dracula-bg-light)" />
64
- <g fill="var(--dracula-purple)" opacity={0.55}>
65
- <circle cx={40 + ((index * 37) % 320)} cy={30} r={2} />
66
- <circle cx={120 + ((index * 23) % 200)} cy={52} r={1.6} />
67
- <circle cx={260 + ((index * 11) % 110)} cy={26} r={2.2} />
68
- <circle cx={330} cy={70 + ((index * 7) % 30)} r={1.6} />
69
- </g>
70
- <circle cx={moonX} cy={moonY} r={34} fill={hue} opacity={0.9} />
71
- <circle
72
- cx={moonX - 12}
73
- cy={moonY - 8}
74
- r={28}
75
- fill="var(--dracula-bg-light)"
76
- opacity={0.55}
77
- />
78
- <path
79
- d={`M0 ${hillA} Q100 ${hillA - 50} 200 ${hillA - 10} T400 ${hillA - 30} V300 H0 Z`}
80
- fill="var(--dracula-current-line)"
81
- />
82
- <path
83
- d={`M0 ${hillB} Q120 ${hillB - 40} 240 ${hillB} T400 ${hillB - 20} V300 H0 Z`}
84
- fill="var(--dracula-bg)"
85
- />
86
- <g
87
- transform={`translate(${60 + ((index * 41) % 280)} ${hillB - 34})`}
88
- fill="none"
89
- stroke={hue}
90
- strokeWidth="4"
91
- strokeLinecap="round"
92
- strokeLinejoin="round">
93
- <rect x="-22" y="-22" width="44" height="44" rx={4} />
94
- <circle cx="9" cy="-9" r="2" fill={hue} stroke="none" />
95
- <path d="M-17 15 L-4 0 L5 9 L10 4 L17 12" />
96
- </g>
97
- </svg>
98
- );
99
- }
100
-
101
35
  // ─── Stat Block ─────────────────────────────────────────────────────────────
102
36
 
103
37
  function StatBlock({value, label}: {value: string; label: string}) {
104
38
  return (
105
39
  <VStack gap={0}>
106
- <Text type="large" weight="bold" hasTabularNumbers>
40
+ <Text type="display-3" weight="semibold" hasTabularNumbers>
107
41
  {value}
108
42
  </Text>
109
43
  <Text type="supporting" color="secondary">
@@ -120,7 +54,12 @@ function ImageGrid() {
120
54
  <Grid columns={3} gap={3}>
121
55
  {GALLERY_SCENES.map((scene, index) => (
122
56
  <AspectRatio key={scene.alt} ratio={1} style={galleryImageClip}>
123
- <NightScene alt={scene.alt} hue={scene.hue} index={index} />
57
+ <SceneTile
58
+ label={scene.alt}
59
+ hue={scene.hue}
60
+ size="sm"
61
+ index={index}
62
+ />
124
63
  </AspectRatio>
125
64
  ))}
126
65
  </Grid>
@@ -129,18 +68,27 @@ function ImageGrid() {
129
68
 
130
69
  // ─── Main Page ──────────────────────────────────────────────────────────────
131
70
 
71
+ // A definite ancestor for `Layout height="fill"`. 100dvh, not minHeight: '100%':
72
+ // a percentage min-height against an indefinite containing block computes to 0
73
+ // (CSS 2.1 10.5), so the soft failure would survive and the document would keep
74
+ // the scroll. LayoutContent is overflow:auto, so the definite height lands as a
75
+ // content-pane scroller rather than a clip. Same shape as dashboard.tsx and
76
+ // editor.tsx:302.
77
+ const pageStyle: CSSProperties = {height: '100dvh'};
78
+
132
79
  export default function SideGallery() {
133
80
  return (
134
81
  <Layout
82
+ style={pageStyle}
135
83
  height="fill"
136
84
  contentWidth={1400}
137
85
  content={
138
86
  <LayoutContent padding={6}>
139
- <Grid columns={{minWidth: 360, repeat: 'fit'}} gap={8} align="center">
87
+ <Grid columns={{minWidth: 280, repeat: 'fit'}} gap={8} align="center">
140
88
  {/* Left side: Text + CTA */}
141
89
  <VStack gap={6} vAlign="center">
142
90
  <VStack gap={3}>
143
- <Text type="supporting" color="secondary" weight="semibold">
91
+ <Text type="supporting" color="secondary">
144
92
  AFTER DARK
145
93
  </Text>
146
94
  <Heading level={1}>
@@ -155,9 +103,7 @@ export default function SideGallery() {
155
103
  </Text>
156
104
  </VStack>
157
105
 
158
- <HStack gap={3} vAlign="center">
159
- <Button label="Explore the night" variant="primary" />
160
- </HStack>
106
+ <Button label="Explore the night" variant="primary" />
161
107
 
162
108
  <VStack gap={4}>
163
109
  <Divider />
@@ -1,4 +1,10 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > (LH[divider] > V[g=4] > (H[j=between a=center] > Hd"Night-shift issues"[level=1] + B.primary"Raise issue"[opens=#raise-issue]) + (H[g=2 a=center] > PS + Po > B.secondary"View options")) + (LC[p=0] > T[hover] > (TR > THC*7) + (TR > TC*7)*4) + (LP > V[g=4] > (H > Tx[t=supporting] + IB"Close panel") + (V[g=1] > Hd[level=2] + Tx[t=body]) + ML + (D + (V[g=2] > Tx"Labels"[t=label] + (H[g=2] > Tk*2)))) ;; Dlg#raise-issue > (DH"Raise issue" + (LC[p=4] > V[g=4] > TI*4) + (LF > H[j=end g=2] > B.secondary"Cancel" + B.primary"Raise issue"))
4
+
5
+ /**
6
+ * Table Grouped — the night-shift issue tracker: a grouped, collapsible issue table with a PowerSearch bar and a resizable detail inspector. (Frame/responsive/container: see XLE header above.)
7
+ */
2
8
 
3
9
  import React, {useState, useMemo} from 'react';
4
10
  import {useResizable, ResizeHandle} from '@astryxdesign/core/Resizable';
@@ -25,11 +31,12 @@ import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
25
31
  import {Popover} from '@astryxdesign/core/Popover';
26
32
  import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
27
33
  import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
28
- import {Center} from '@astryxdesign/core/Center';
29
34
  import {Icon} from '@astryxdesign/core/Icon';
30
35
  import {StatusDot} from '@astryxdesign/core/StatusDot';
31
36
  import {Divider} from '@astryxdesign/core/Divider';
32
37
  import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
38
+ import {Token} from '@astryxdesign/core/Token';
39
+ import {useMediaQuery} from '@astryxdesign/core/hooks';
33
40
  import {
34
41
  Table,
35
42
  TableRow,
@@ -85,9 +92,9 @@ interface TaskRow extends Record<string, unknown> {
85
92
 
86
93
  const STATUS_DOT_VARIANT: Record<
87
94
  TaskStatus,
88
- 'success' | 'accent' | 'neutral' | 'warning'
95
+ 'success' | 'neutral' | 'warning' | 'info'
89
96
  > = {
90
- in_progress: 'accent',
97
+ in_progress: 'info',
91
98
  todo: 'warning',
92
99
  backlog: 'neutral',
93
100
  done: 'success',
@@ -95,10 +102,10 @@ const STATUS_DOT_VARIANT: Record<
95
102
 
96
103
  const PRIORITY_COLOR: Record<
97
104
  TaskPriority,
98
- 'primary' | 'secondary' | 'disabled'
105
+ 'error' | 'warning' | 'secondary' | 'disabled'
99
106
  > = {
100
- urgent: 'primary',
101
- high: 'primary',
107
+ urgent: 'error',
108
+ high: 'warning',
102
109
  medium: 'secondary',
103
110
  low: 'disabled',
104
111
  none: 'disabled',
@@ -124,7 +131,7 @@ const allTasks: TaskRow[] = [
124
131
  {
125
132
  id: '2',
126
133
  taskId: 'T235040470',
127
- title: 'Use Projects to organize work for features or releases',
134
+ title: 'Chart the crypt to organize haunts for rituals or releases',
128
135
  subtitle: '',
129
136
  status: 'in_progress',
130
137
  priority: 'medium',
@@ -139,7 +146,7 @@ const allTasks: TaskRow[] = [
139
146
  {
140
147
  id: '3',
141
148
  taskId: 'T235040471',
142
- title: 'Use Cycles to focus work over n-weeks',
149
+ title: 'Follow moon cycles to focus haunts over n-weeks',
143
150
  subtitle: '',
144
151
  status: 'in_progress',
145
152
  priority: 'medium',
@@ -154,7 +161,7 @@ const allTasks: TaskRow[] = [
154
161
  {
155
162
  id: '4',
156
163
  taskId: 'T235040472',
157
- title: 'Testing code',
164
+ title: 'Test the gate wards before moonrise',
158
165
  subtitle: 'Update castle gate integration',
159
166
  status: 'todo',
160
167
  priority: 'medium',
@@ -229,7 +236,7 @@ const allTasks: TaskRow[] = [
229
236
  {
230
237
  id: '9',
231
238
  taskId: 'T235040477',
232
- title: 'Invite your teammates',
239
+ title: 'Invite your fellow familiars to the crypt',
233
240
  subtitle: '',
234
241
  status: 'todo',
235
242
  priority: 'low',
@@ -244,7 +251,7 @@ const allTasks: TaskRow[] = [
244
251
  {
245
252
  id: '10',
246
253
  taskId: 'T235040478',
247
- title: 'Next steps',
254
+ title: 'Next rites after moonrise',
248
255
  subtitle: '',
249
256
  status: 'todo',
250
257
  priority: 'none',
@@ -454,7 +461,7 @@ const allTasks: TaskRow[] = [
454
461
  {
455
462
  id: '24',
456
463
  taskId: 'T235040492',
457
- title: 'Add dark mode support to dashboard',
464
+ title: 'Audit dashboard contrast against the Dracula ramp',
458
465
  subtitle: '',
459
466
  status: 'backlog',
460
467
  priority: 'low',
@@ -586,7 +593,7 @@ function groupTasks(
586
593
  }
587
594
  const map = new Map<string, TaskRow[]>();
588
595
  for (const task of tasks) {
589
- const key = String(task[groupBy]) || '—';
596
+ const key = String(task[groupBy]) || 'None';
590
597
  let group = map.get(key);
591
598
  if (!group) {
592
599
  group = [];
@@ -628,7 +635,7 @@ const columns: TableColumn<TaskRow>[] = [
628
635
  {
629
636
  key: 'project',
630
637
  header: 'Project',
631
- width: pixel(144),
638
+ width: proportional(1),
632
639
  },
633
640
  {
634
641
  key: 'created',
@@ -729,7 +736,7 @@ function TaskDetailPanel({
729
736
  </HStack>
730
737
 
731
738
  <VStack gap={1}>
732
- <Heading level={3}>{task.title}</Heading>
739
+ <Heading level={2}>{task.title}</Heading>
733
740
  {task.subtitle && (
734
741
  <Text type="body" color="secondary">
735
742
  {task.subtitle}
@@ -739,10 +746,11 @@ function TaskDetailPanel({
739
746
 
740
747
  <MetadataList label={{position: 'start'}}>
741
748
  <MetadataListItem label="Status">
742
- <HStack gap={2} vAlign="center">
749
+ <HStack gap={1} vAlign="center">
743
750
  <StatusDot
744
751
  variant={STATUS_DOT_VARIANT[task.status]}
745
752
  label={STATUS_LABEL[task.status]}
753
+ isPulsing={task.status === 'in_progress'}
746
754
  />
747
755
  <Text type="body">{STATUS_LABEL[task.status]}</Text>
748
756
  </HStack>
@@ -768,7 +776,7 @@ function TaskDetailPanel({
768
776
  <Text type="body">{task.project}</Text>
769
777
  ) : (
770
778
  <Text type="supporting" color="secondary">
771
- —
779
+ None
772
780
  </Text>
773
781
  )}
774
782
  </MetadataListItem>
@@ -787,7 +795,7 @@ function TaskDetailPanel({
787
795
  <Text type="label">Labels</Text>
788
796
  <HStack gap={2}>
789
797
  {task.tags.map(tag => (
790
- <Badge key={tag} variant="neutral" label={tag} />
798
+ <Token key={tag} color="yellow" label={tag} />
791
799
  ))}
792
800
  </HStack>
793
801
  </VStack>
@@ -813,6 +821,10 @@ export default function TableGrouped() {
813
821
  () => new Set(GROUP_ORDER as string[]),
814
822
  );
815
823
 
824
+ // Responsive contract (see file header): below 1024px the inspector would
825
+ // squeeze the grouped table, so it steps aside entirely.
826
+ const isNarrow = useMediaQuery('(max-width: 1024px)');
827
+
816
828
  const filtered = useMemo(() => {
817
829
  return applyFilters(powerSearchFilters, allTasks);
818
830
  }, [powerSearchFilters, applyFilters]);
@@ -822,11 +834,13 @@ export default function TableGrouped() {
822
834
  [filtered, groupBy],
823
835
  );
824
836
 
825
- const groupKeys = useMemo(() => Array.from(grouped.keys()), [grouped]);
837
+ const groupKeys = Array.from(grouped.keys());
826
838
 
839
+ // Expand every group whenever the grouping itself changes. Keyed on the
840
+ // memoized Map, not on groupKeys, which is a fresh array each render.
827
841
  React.useEffect(() => {
828
- setExpandedGroups(new Set(groupKeys));
829
- }, [groupKeys]);
842
+ setExpandedGroups(new Set(grouped.keys()));
843
+ }, [grouped]);
830
844
 
831
845
  const toggleGroup = (key: string) => {
832
846
  setExpandedGroups(prev => {
@@ -854,16 +868,21 @@ export default function TableGrouped() {
854
868
  <Layout
855
869
  height="fill"
856
870
  header={
857
- <LayoutHeader hasDivider padding={4}>
871
+ <LayoutHeader hasDivider padding={6}>
858
872
  <VStack gap={4}>
859
873
  <HStack gap={3} vAlign="center">
860
874
  <StackItem size="fill">
861
- <Heading level={1}>Night-shift issues</Heading>
875
+ {/* maxLines={1}: this is the worst case in the set — 18
876
+ characters at 35px is 378px of a 342px header column, so
877
+ it wraps to THREE lines and the header measures 180px
878
+ (21.3% of a 390x844 viewport). Core wires maxLines to
879
+ useTruncation, so the full title stays reachable in a
880
+ Tooltip (SC 1.4.10). */}
881
+ <Heading level={1} type="display-2" maxLines={1}>Night-shift issues</Heading>
862
882
  </StackItem>
863
883
  <Button
864
884
  label="Raise issue"
865
885
  variant="primary"
866
- size="lg"
867
886
  onClick={() => setDialogOpen(true)}
868
887
  />
869
888
  </HStack>
@@ -873,14 +892,14 @@ export default function TableGrouped() {
873
892
  config={powerSearchConfig}
874
893
  filters={powerSearchFilters}
875
894
  onChange={newFilters => setPowerSearchFilters(newFilters)}
876
- placeholder="Filter issues..."
895
+ placeholder="Filter issues…"
877
896
  resultCount={`${filtered.length} issue${filtered.length !== 1 ? 's' : ''}`}
878
897
  />
879
898
  </StackItem>
880
899
  <Popover
881
900
  placement="below"
882
901
  alignment="end"
883
- width={320}
902
+ width="min(20rem, calc(100vw - 2 * var(--space-viewport)))"
884
903
  label="Grouping options"
885
904
  content={
886
905
  <VStack gap={4}>
@@ -898,7 +917,7 @@ export default function TableGrouped() {
898
917
  </RadioList>
899
918
  </VStack>
900
919
  }>
901
- <Button label="View Options" variant="secondary" size="md" />
920
+ <Button label="View options" variant="secondary" size="md" />
902
921
  </Popover>
903
922
  </HStack>
904
923
  </VStack>
@@ -949,7 +968,7 @@ export default function TableGrouped() {
949
968
  size="sm"
950
969
  color="secondary"
951
970
  />
952
- <Text type="body" weight="bold">
971
+ <Text type="body" weight="semibold">
953
972
  {getGroupLabel(groupBy, key)}
954
973
  </Text>
955
974
  <Badge
@@ -966,12 +985,21 @@ export default function TableGrouped() {
966
985
  key={task.id}
967
986
  onClick={() => setSelectedTask(task)}>
968
987
  <TableCell>
969
- <Center axis="horizontal">
988
+ {/* StatusDot paints nothing but an 8px dot — its
989
+ label is aria-label on a role="img" span — so
990
+ four states here read as hue alone. Same
991
+ dot-plus-word pairing as the summary row at
992
+ :751-757. */}
993
+ <HStack gap={1} vAlign="center">
970
994
  <StatusDot
971
995
  variant={STATUS_DOT_VARIANT[task.status]}
972
996
  label={STATUS_LABEL[task.status]}
997
+ isPulsing={task.status === 'in_progress'}
973
998
  />
974
- </Center>
999
+ <Text type="supporting" color="secondary">
1000
+ {STATUS_LABEL[task.status]}
1001
+ </Text>
1002
+ </HStack>
975
1003
  </TableCell>
976
1004
  <TableCell>
977
1005
  <HStack gap={3} vAlign="center" wrap="wrap">
@@ -1003,7 +1031,7 @@ export default function TableGrouped() {
1003
1031
  </Text>
1004
1032
  ) : (
1005
1033
  <Text type="supporting" color="secondary">
1006
- —
1034
+ None
1007
1035
  </Text>
1008
1036
  )}
1009
1037
  </TableCell>
@@ -1085,6 +1113,7 @@ export default function TableGrouped() {
1085
1113
  </LayoutContent>
1086
1114
  }
1087
1115
  end={
1116
+ !isNarrow &&
1088
1117
  selectedTask && (
1089
1118
  <>
1090
1119
  <ResizeHandle
@@ -1103,9 +1132,10 @@ export default function TableGrouped() {
1103
1132
  />
1104
1133
  <Dialog isOpen={dialogOpen} onOpenChange={open => setDialogOpen(open)}>
1105
1134
  <Layout
1135
+ height="auto"
1106
1136
  header={
1107
1137
  <DialogHeader
1108
- title="Raise an issue"
1138
+ title="Raise issue"
1109
1139
  onOpenChange={open => setDialogOpen(open)}
1110
1140
  />
1111
1141
  }
@@ -1159,7 +1189,7 @@ export default function TableGrouped() {
1159
1189
  onClick={() => setDialogOpen(false)}
1160
1190
  />
1161
1191
  <Button
1162
- label="Raise"
1192
+ label="Raise issue"
1163
1193
  variant="primary"
1164
1194
  size="md"
1165
1195
  onClick={() => setDialogOpen(false)}