@cogito.ai/astryx-web 0.0.0-stage → 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.
Files changed (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -2
  3. package/astryx.integration.mjs +15 -0
  4. package/bin/astryx-web.mjs +353 -0
  5. package/docs/dock-accessibility.doc.mjs +24 -0
  6. package/docs/dock-components.doc.mjs +26 -0
  7. package/docs/dock-copy.doc.mjs +23 -0
  8. package/docs/dock-design-base.doc.mjs +35 -0
  9. package/docs/dock-environment.doc.mjs +24 -0
  10. package/docs/dock-layout.doc.mjs +25 -0
  11. package/docs/dock-product-stance.doc.mjs +25 -0
  12. package/docs/dock-refuse-list.doc.mjs +34 -0
  13. package/docs/dock-states.doc.mjs +23 -0
  14. package/docs/dock-style-archetypes.doc.mjs +104 -0
  15. package/docs/dock-touch-mobile.doc.mjs +27 -0
  16. package/docs/dock-visual-acceptance.doc.mjs +23 -0
  17. package/package.json +51 -3
  18. package/skills/astryx-web-design/SKILL.md +21 -0
  19. package/skills/astryx-web-design/references/acceptance.md +12 -0
  20. package/skills/astryx-web-design/references/design-direction.md +52 -0
  21. package/skills/astryx-web-design/references/page-building.md +18 -0
  22. package/templates/dock-editor-frame.doc.mjs +19 -0
  23. package/templates/dock-editor-frame.tsx +229 -0
  24. package/templates/dock-form-flow.doc.mjs +19 -0
  25. package/templates/dock-form-flow.tsx +169 -0
  26. package/templates/dock-landing.doc.mjs +11 -0
  27. package/templates/dock-landing.tsx +104 -0
  28. package/templates/dock-list.doc.mjs +20 -0
  29. package/templates/dock-list.tsx +223 -0
  30. package/templates/dock-settings.doc.mjs +19 -0
  31. package/templates/dock-settings.tsx +272 -0
  32. package/templates/dock-workbench.doc.mjs +11 -0
  33. package/templates/dock-workbench.tsx +193 -0
@@ -0,0 +1,272 @@
1
+ import { Heading } from '@astryxdesign/core/Heading'
2
+ import { Icon, type IconName } from '@astryxdesign/core/Icon'
3
+ import { Selector } from '@astryxdesign/core/Selector'
4
+ import { SideNav, SideNavItem, SideNavSection } from '@astryxdesign/core/SideNav'
5
+ import { Stack } from '@astryxdesign/core/Stack'
6
+ import { Switch } from '@astryxdesign/core/Switch'
7
+ import { Tab, TabList } from '@astryxdesign/core/TabList'
8
+ import { Text } from '@astryxdesign/core/Text'
9
+ import { VisuallyHidden } from '@astryxdesign/core/VisuallyHidden'
10
+ import { useTranslator } from '@astryxdesign/core/i18n'
11
+ import * as stylex from '@stylexjs/stylex'
12
+ import { useState, type ReactNode } from 'react'
13
+ import { PageContainer } from '@/components/layout'
14
+
15
+ const SECTIONS = [
16
+ { id: 'general', icon: 'wrench' },
17
+ { id: 'notifications', icon: 'info' },
18
+ { id: 'privacy', icon: 'eyeSlash' },
19
+ ] as const
20
+ type SectionId = (typeof SECTIONS)[number]['id']
21
+
22
+ const ROWS = {
23
+ general: [
24
+ {
25
+ id: 'theme',
26
+ icon: 'wrench',
27
+ titleKey: 'page.__PAGE__.general.theme.title',
28
+ descriptionKey: 'page.__PAGE__.general.theme.description',
29
+ },
30
+ ],
31
+ notifications: [
32
+ {
33
+ id: 'updates',
34
+ icon: 'info',
35
+ titleKey: 'page.__PAGE__.notifications.updates.title',
36
+ descriptionKey: 'page.__PAGE__.notifications.updates.description',
37
+ },
38
+ ],
39
+ privacy: [
40
+ {
41
+ id: 'history',
42
+ icon: 'eyeSlash',
43
+ titleKey: 'page.__PAGE__.privacy.history.title',
44
+ descriptionKey: 'page.__PAGE__.privacy.history.description',
45
+ },
46
+ ],
47
+ } as const
48
+
49
+ const styles = stylex.create({
50
+ pageColumn: {
51
+ display: 'flex',
52
+ flexDirection: 'column',
53
+ gap: '1rem',
54
+ flex: 1,
55
+ minHeight: 0,
56
+ width: '100%',
57
+ overflow: 'hidden',
58
+ '@container (min-width: 420px)': { gap: '1.5rem' },
59
+ },
60
+ layout: {
61
+ display: 'flex',
62
+ flexDirection: 'column',
63
+ gap: '1rem',
64
+ flex: 1,
65
+ minHeight: 0,
66
+ width: '100%',
67
+ '@container (min-width: 640px)': { flexDirection: 'row', gap: '1.5rem' },
68
+ },
69
+ wideNav: {
70
+ display: 'none',
71
+ '@container (min-width: 640px)': { display: 'block', width: '13rem', flexShrink: 0 },
72
+ },
73
+ sideNav: { width: '100%' },
74
+ narrowNav: {
75
+ width: '100%',
76
+ '@container (min-width: 640px)': { display: 'none' },
77
+ },
78
+ panel: {
79
+ flex: 1,
80
+ minHeight: 0,
81
+ minWidth: 0,
82
+ width: '100%',
83
+ overflowY: 'auto',
84
+ paddingBlockEnd: '1rem',
85
+ },
86
+ row: {
87
+ display: 'flex',
88
+ flexDirection: 'column',
89
+ alignItems: 'stretch',
90
+ gap: '0.75rem',
91
+ '@container (min-width: 480px)': {
92
+ flexDirection: 'row',
93
+ alignItems: 'center',
94
+ justifyContent: 'space-between',
95
+ },
96
+ },
97
+ copy: { minWidth: 0 },
98
+ control: {
99
+ flexShrink: 0,
100
+ '@container (max-width: 479px)': { width: '100%' },
101
+ },
102
+ })
103
+
104
+ function isSectionId(value: string): value is SectionId {
105
+ return SECTIONS.some((section) => section.id === value)
106
+ }
107
+
108
+ interface SettingsRowProps {
109
+ readonly icon: IconName
110
+ readonly title: string
111
+ readonly description: string
112
+ readonly control: ReactNode
113
+ }
114
+
115
+ function SettingsRow({ icon, title, description, control }: SettingsRowProps) {
116
+ return (
117
+ <Stack direction="horizontal" gap={2} vAlign="start" xstyle={styles.row}>
118
+ <Stack direction="horizontal" gap={2} vAlign="start" xstyle={styles.copy}>
119
+ <Icon icon={icon} size="sm" color="secondary" />
120
+ <Stack gap={0.5} xstyle={styles.copy}>
121
+ <Text type="body" weight="semibold">
122
+ {title}
123
+ </Text>
124
+ <Text type="supporting" color="secondary">
125
+ {description}
126
+ </Text>
127
+ </Stack>
128
+ </Stack>
129
+ <Stack xstyle={styles.control}>{control}</Stack>
130
+ </Stack>
131
+ )
132
+ }
133
+
134
+ export default function DockSettings() {
135
+ const t = useTranslator()
136
+ const [section, setSection] = useState<SectionId>('general')
137
+ const [theme, setTheme] = useState('neutral')
138
+ const [notifications, setNotifications] = useState(true)
139
+ const [history, setHistory] = useState(false)
140
+ const rows = ROWS[section]
141
+
142
+ return (
143
+ <PageContainer isFullHeight>
144
+ <VisuallyHidden>
145
+ <Heading level={1}>{t('page.__PAGE__.title')}</Heading>
146
+ </VisuallyHidden>
147
+ <Stack xstyle={styles.pageColumn}>
148
+ <Stack direction="horizontal" gap={4} xstyle={styles.layout}>
149
+ <TabList
150
+ aria-label={t('page.__PAGE__.sections')}
151
+ value={section}
152
+ onChange={(value) => {
153
+ if (isSectionId(value)) setSection(value)
154
+ }}
155
+ size="sm"
156
+ hasDivider
157
+ xstyle={styles.narrowNav}
158
+ >
159
+ {SECTIONS.map((candidate) => (
160
+ <Tab
161
+ key={candidate.id}
162
+ value={candidate.id}
163
+ label={t(`page.__PAGE__.section.${candidate.id}`)}
164
+ icon={<Icon icon={candidate.icon} size="sm" />}
165
+ />
166
+ ))}
167
+ </TabList>
168
+
169
+ <Stack xstyle={styles.wideNav}>
170
+ <SideNav aria-label={t('page.__PAGE__.sections')} xstyle={styles.sideNav}>
171
+ <SideNavSection title={t('page.__PAGE__.sections')} isHeaderHidden>
172
+ {SECTIONS.map((candidate) => (
173
+ <SideNavItem
174
+ key={candidate.id}
175
+ label={t(`page.__PAGE__.section.${candidate.id}`)}
176
+ icon={<Icon icon={candidate.icon} size="sm" />}
177
+ isSelected={candidate.id === section}
178
+ onClick={() => setSection(candidate.id)}
179
+ />
180
+ ))}
181
+ </SideNavSection>
182
+ </SideNav>
183
+ </Stack>
184
+
185
+ <Stack gap={4} isScrollable xstyle={styles.panel}>
186
+ {rows.map((row) => {
187
+ const control =
188
+ row.id === 'theme' ? (
189
+ <Selector
190
+ label={t('page.__PAGE__.general.theme.control')}
191
+ value={theme}
192
+ onChange={setTheme}
193
+ options={[
194
+ { value: 'neutral', label: t('page.__PAGE__.general.theme.neutral') },
195
+ { value: 'matcha', label: t('page.__PAGE__.general.theme.matcha') },
196
+ { value: 'stone', label: t('page.__PAGE__.general.theme.stone') },
197
+ ]}
198
+ />
199
+ ) : row.id === 'updates' ? (
200
+ <Switch
201
+ label={t('page.__PAGE__.notifications.updates.control')}
202
+ value={notifications}
203
+ onChange={setNotifications}
204
+ />
205
+ ) : (
206
+ <Switch
207
+ label={t('page.__PAGE__.privacy.history.control')}
208
+ value={history}
209
+ onChange={setHistory}
210
+ />
211
+ )
212
+
213
+ return (
214
+ <SettingsRow
215
+ key={row.id}
216
+ icon={row.icon}
217
+ title={t(row.titleKey)}
218
+ description={t(row.descriptionKey)}
219
+ control={control}
220
+ />
221
+ )
222
+ })}
223
+ </Stack>
224
+ </Stack>
225
+ </Stack>
226
+ </PageContainer>
227
+ )
228
+ }
229
+
230
+ /*
231
+ astryx-messages
232
+ {
233
+ "en": {
234
+ "page.__PAGE__.title": "Settings",
235
+ "page.__PAGE__.sections": "Settings sections",
236
+ "page.__PAGE__.section.general": "General",
237
+ "page.__PAGE__.section.notifications": "Notifications",
238
+ "page.__PAGE__.section.privacy": "Privacy",
239
+ "page.__PAGE__.general.theme.title": "Theme",
240
+ "page.__PAGE__.general.theme.description": "Choose the color system used across the workspace.",
241
+ "page.__PAGE__.general.theme.control": "Theme",
242
+ "page.__PAGE__.general.theme.neutral": "Neutral",
243
+ "page.__PAGE__.general.theme.matcha": "Matcha",
244
+ "page.__PAGE__.general.theme.stone": "Stone",
245
+ "page.__PAGE__.notifications.updates.title": "Product updates",
246
+ "page.__PAGE__.notifications.updates.description": "Receive occasional messages about new features.",
247
+ "page.__PAGE__.notifications.updates.control": "Product updates",
248
+ "page.__PAGE__.privacy.history.title": "Activity history",
249
+ "page.__PAGE__.privacy.history.description": "Keep recent activity available in this workspace.",
250
+ "page.__PAGE__.privacy.history.control": "Activity history"
251
+ },
252
+ "zh-CN": {
253
+ "page.__PAGE__.title": "设置",
254
+ "page.__PAGE__.sections": "设置分区",
255
+ "page.__PAGE__.section.general": "通用",
256
+ "page.__PAGE__.section.notifications": "通知",
257
+ "page.__PAGE__.section.privacy": "隐私",
258
+ "page.__PAGE__.general.theme.title": "主题",
259
+ "page.__PAGE__.general.theme.description": "选择整个工作区使用的色彩系统。",
260
+ "page.__PAGE__.general.theme.control": "主题",
261
+ "page.__PAGE__.general.theme.neutral": "中性",
262
+ "page.__PAGE__.general.theme.matcha": "抹茶",
263
+ "page.__PAGE__.general.theme.stone": "石色",
264
+ "page.__PAGE__.notifications.updates.title": "产品更新",
265
+ "page.__PAGE__.notifications.updates.description": "偶尔接收新功能消息。",
266
+ "page.__PAGE__.notifications.updates.control": "产品更新",
267
+ "page.__PAGE__.privacy.history.title": "活动历史",
268
+ "page.__PAGE__.privacy.history.description": "在此工作区保留最近活动。",
269
+ "page.__PAGE__.privacy.history.control": "活动历史"
270
+ }
271
+ }
272
+ */
@@ -0,0 +1,11 @@
1
+ /** @type {import('@astryxdesign/cli/authoring').IntegrationTemplateDoc} */
2
+ export default {
3
+ type: 'page',
4
+ name: 'Workbench page',
5
+ displayName: 'Workbench page',
6
+ description:
7
+ 'A second-level one-screen workbench with a fixed top bar, section rows, an inline add action, and configuration in a side drawer.',
8
+ category: 'Workspace - Workbench',
9
+ keywords: ['workbench page', 'workspace', 'top bar', 'sections', 'side drawer', 'mobile'],
10
+ isReady: true,
11
+ }
@@ -0,0 +1,193 @@
1
+ import { Button } from '@astryxdesign/core/Button'
2
+ import { Divider } from '@astryxdesign/core/Divider'
3
+ import { Heading } from '@astryxdesign/core/Heading'
4
+ import { Icon } from '@astryxdesign/core/Icon'
5
+ import { List, ListItem } from '@astryxdesign/core/List'
6
+ import { MoreMenu } from '@astryxdesign/core/MoreMenu'
7
+ import { Stack } from '@astryxdesign/core/Stack'
8
+ import { Text } from '@astryxdesign/core/Text'
9
+ import { TextInput } from '@astryxdesign/core/TextInput'
10
+ import { useTranslator } from '@astryxdesign/core/i18n'
11
+ import { useState } from 'react'
12
+ import { PageShell, SideDrawer } from '@/components/layout'
13
+ import { useCurrentLocale } from '@/i18n'
14
+
15
+ const SAMPLE_ROWS = [
16
+ {
17
+ id: 'primary',
18
+ titleKey: 'page.__PAGE__.row.primary.title',
19
+ descriptionKey: 'page.__PAGE__.row.primary.description',
20
+ },
21
+ {
22
+ id: 'secondary',
23
+ titleKey: 'page.__PAGE__.row.secondary.title',
24
+ descriptionKey: 'page.__PAGE__.row.secondary.description',
25
+ },
26
+ ] as const
27
+ // Replace with feature data
28
+
29
+ export default function DockWorkbench() {
30
+ const locale = useCurrentLocale()
31
+ const t = useTranslator()
32
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false)
33
+ const [draft, setDraft] = useState('')
34
+
35
+ return (
36
+ <PageShell
37
+ title={t('page.__PAGE__.title')}
38
+ backHref={`/${locale}`}
39
+ backLabel={t('page.__PAGE__.back')}
40
+ actions={
41
+ <Stack direction="horizontal" gap={1} vAlign="center">
42
+ <MoreMenu
43
+ label={t('page.__PAGE__.options')}
44
+ alignment="end"
45
+ items={[
46
+ {
47
+ id: 'reset',
48
+ label: t('page.__PAGE__.reset'),
49
+ icon: <Icon icon="close" size="sm" />,
50
+ onClick: () => setDraft(''),
51
+ },
52
+ ]}
53
+ />
54
+ <Button
55
+ label={t('page.__PAGE__.add')}
56
+ size="sm"
57
+ variant="primary"
58
+ onClick={() => setIsDrawerOpen(true)}
59
+ />
60
+ </Stack>
61
+ }
62
+ >
63
+ <Stack gap={4}>
64
+ <Stack as="section" gap={3}>
65
+ <Stack direction="horizontal" hAlign="between" vAlign="center" gap={2}>
66
+ <Heading level={2}>{t('page.__PAGE__.section.current')}</Heading>
67
+ <Button
68
+ label={t('page.__PAGE__.section.add')}
69
+ size="sm"
70
+ variant="ghost"
71
+ onClick={() => setIsDrawerOpen(true)}
72
+ />
73
+ </Stack>
74
+ <List density="balanced" hasDividers>
75
+ {SAMPLE_ROWS.map((row) => (
76
+ <ListItem
77
+ key={row.id}
78
+ label={t(row.titleKey)}
79
+ description={t(row.descriptionKey)}
80
+ endContent={
81
+ <MoreMenu
82
+ label={t('page.__PAGE__.row.options')}
83
+ size="sm"
84
+ alignment="end"
85
+ presentation="adaptive"
86
+ items={[
87
+ {
88
+ id: 'archive',
89
+ label: t('page.__PAGE__.row.archive'),
90
+ icon: <Icon icon="close" size="sm" />,
91
+ },
92
+ ]}
93
+ />
94
+ }
95
+ />
96
+ ))}
97
+ </List>
98
+ </Stack>
99
+
100
+ <Divider variant="subtle" />
101
+
102
+ <Stack as="section" gap={3}>
103
+ <Stack direction="horizontal" hAlign="between" vAlign="center" gap={2}>
104
+ <Heading level={2}>{t('page.__PAGE__.section.review')}</Heading>
105
+ <Button
106
+ label={t('page.__PAGE__.section.add')}
107
+ size="sm"
108
+ variant="ghost"
109
+ onClick={() => setIsDrawerOpen(true)}
110
+ />
111
+ </Stack>
112
+ <Text type="body" color="secondary">
113
+ {t('page.__PAGE__.section.empty')}
114
+ </Text>
115
+ </Stack>
116
+ </Stack>
117
+
118
+ <SideDrawer
119
+ isOpen={isDrawerOpen}
120
+ onOpenChange={setIsDrawerOpen}
121
+ label={t('page.__PAGE__.drawer.label')}
122
+ header={t('page.__PAGE__.drawer.title')}
123
+ >
124
+ <Stack gap={4}>
125
+ <TextInput
126
+ label={t('page.__PAGE__.field.name')}
127
+ value={draft}
128
+ onChange={setDraft}
129
+ placeholder={t('page.__PAGE__.field.placeholder')}
130
+ isRequired
131
+ />
132
+ <Stack direction="horizontal" hAlign="end">
133
+ <Button
134
+ label={t('page.__PAGE__.drawer.save')}
135
+ variant="primary"
136
+ onClick={() => setIsDrawerOpen(false)}
137
+ />
138
+ </Stack>
139
+ </Stack>
140
+ </SideDrawer>
141
+ </PageShell>
142
+ )
143
+ }
144
+
145
+ /*
146
+ astryx-messages
147
+ {
148
+ "en": {
149
+ "page.__PAGE__.title": "Workspace",
150
+ "page.__PAGE__.back": "Back",
151
+ "page.__PAGE__.options": "Workspace actions",
152
+ "page.__PAGE__.reset": "Clear draft",
153
+ "page.__PAGE__.add": "Add item",
154
+ "page.__PAGE__.section.current": "Current work",
155
+ "page.__PAGE__.section.review": "Needs review",
156
+ "page.__PAGE__.section.add": "Add",
157
+ "page.__PAGE__.section.empty": "Nothing needs review.",
158
+ "page.__PAGE__.row.primary.title": "Prepare release notes",
159
+ "page.__PAGE__.row.primary.description": "Updated today",
160
+ "page.__PAGE__.row.secondary.title": "Review access request",
161
+ "page.__PAGE__.row.secondary.description": "Waiting for a decision",
162
+ "page.__PAGE__.row.options": "Item actions",
163
+ "page.__PAGE__.row.archive": "Archive",
164
+ "page.__PAGE__.drawer.label": "Add a workspace item",
165
+ "page.__PAGE__.drawer.title": "New item",
166
+ "page.__PAGE__.field.name": "Name",
167
+ "page.__PAGE__.field.placeholder": "Describe the work",
168
+ "page.__PAGE__.drawer.save": "Add item"
169
+ },
170
+ "zh-CN": {
171
+ "page.__PAGE__.title": "工作台",
172
+ "page.__PAGE__.back": "返回",
173
+ "page.__PAGE__.options": "工作台操作",
174
+ "page.__PAGE__.reset": "清空草稿",
175
+ "page.__PAGE__.add": "添加事项",
176
+ "page.__PAGE__.section.current": "当前工作",
177
+ "page.__PAGE__.section.review": "待审核",
178
+ "page.__PAGE__.section.add": "添加",
179
+ "page.__PAGE__.section.empty": "没有待审核事项。",
180
+ "page.__PAGE__.row.primary.title": "准备发布说明",
181
+ "page.__PAGE__.row.primary.description": "今天更新",
182
+ "page.__PAGE__.row.secondary.title": "审核访问请求",
183
+ "page.__PAGE__.row.secondary.description": "等待决定",
184
+ "page.__PAGE__.row.options": "事项操作",
185
+ "page.__PAGE__.row.archive": "归档",
186
+ "page.__PAGE__.drawer.label": "添加工作台事项",
187
+ "page.__PAGE__.drawer.title": "新事项",
188
+ "page.__PAGE__.field.name": "名称",
189
+ "page.__PAGE__.field.placeholder": "描述这项工作",
190
+ "page.__PAGE__.drawer.save": "添加事项"
191
+ }
192
+ }
193
+ */