@cogito.ai/astryx-web 0.0.0-stage → 0.1.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.
@@ -0,0 +1,223 @@
1
+ import { Button } from '@astryxdesign/core/Button'
2
+ import { EmptyState } from '@astryxdesign/core/EmptyState'
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 { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl'
8
+ import { Stack } from '@astryxdesign/core/Stack'
9
+ import { Text } from '@astryxdesign/core/Text'
10
+ import { TextInput } from '@astryxdesign/core/TextInput'
11
+ import { Timestamp } from '@astryxdesign/core/Timestamp'
12
+ import { VisuallyHidden } from '@astryxdesign/core/VisuallyHidden'
13
+ import { useTranslator } from '@astryxdesign/core/i18n'
14
+ import { useState } from 'react'
15
+ import { PageShell } from '@/components/layout'
16
+ import { useCurrentLocale } from '@/i18n'
17
+
18
+ const FILTERS = ['recent', 'all', 'archived'] as const
19
+ type Filter = (typeof FILTERS)[number]
20
+
21
+ const SAMPLE_ITEMS = [
22
+ {
23
+ id: 'brief',
24
+ titleKey: 'page.__PAGE__.item.brief.title',
25
+ metaKey: 'page.__PAGE__.item.brief.meta',
26
+ updatedAt: '2026-11-01T09:00:00.000Z',
27
+ },
28
+ {
29
+ id: 'notes',
30
+ titleKey: 'page.__PAGE__.item.notes.title',
31
+ metaKey: 'page.__PAGE__.item.notes.meta',
32
+ updatedAt: '2026-10-28T14:30:00.000Z',
33
+ },
34
+ {
35
+ id: 'archive',
36
+ titleKey: 'page.__PAGE__.item.archive.title',
37
+ metaKey: 'page.__PAGE__.item.archive.meta',
38
+ updatedAt: '2026-10-20T08:15:00.000Z',
39
+ },
40
+ ] as const
41
+ // Replace with feature data
42
+
43
+ function isFilter(value: string): value is Filter {
44
+ return FILTERS.some((filter) => filter === value)
45
+ }
46
+
47
+ export default function DockList() {
48
+ const locale = useCurrentLocale()
49
+ const t = useTranslator()
50
+ const [search, setSearch] = useState('')
51
+ const [filter, setFilter] = useState<Filter>('recent')
52
+ const hasItems: boolean = SAMPLE_ITEMS.length > 0
53
+
54
+ const visibleItems = SAMPLE_ITEMS.filter((item) => {
55
+ const query = search.trim().toLocaleLowerCase()
56
+ if (!query) return true
57
+ return `${t(item.titleKey)} ${t(item.metaKey)}`.toLocaleLowerCase().includes(query)
58
+ })
59
+
60
+ return (
61
+ <PageShell
62
+ title={t('page.__PAGE__.title')}
63
+ backHref={`/${locale}`}
64
+ backLabel={t('page.__PAGE__.back')}
65
+ actions={
66
+ <Button
67
+ label={t('page.__PAGE__.new')}
68
+ size="sm"
69
+ variant="primary"
70
+ onClick={() => undefined}
71
+ />
72
+ }
73
+ >
74
+ <Stack gap={4}>
75
+ <TextInput
76
+ label={t('page.__PAGE__.search.label')}
77
+ isLabelHidden
78
+ value={search}
79
+ onChange={setSearch}
80
+ placeholder={t('page.__PAGE__.search.placeholder')}
81
+ startIcon={<Icon icon="search" size="sm" />}
82
+ hasClear
83
+ />
84
+ <SegmentedControl
85
+ label={t('page.__PAGE__.filter.label')}
86
+ value={filter}
87
+ onChange={(value) => {
88
+ if (isFilter(value)) setFilter(value)
89
+ }}
90
+ layout="fill"
91
+ >
92
+ {FILTERS.map((candidate) => (
93
+ <SegmentedControlItem
94
+ key={candidate}
95
+ value={candidate}
96
+ label={t(`page.__PAGE__.filter.${candidate}`)}
97
+ />
98
+ ))}
99
+ </SegmentedControl>
100
+
101
+ {!hasItems ? (
102
+ <EmptyState
103
+ title={t('page.__PAGE__.empty.title')}
104
+ description={t('page.__PAGE__.empty.description')}
105
+ actions={
106
+ <Button label={t('page.__PAGE__.new')} variant="primary" onClick={() => undefined} />
107
+ }
108
+ />
109
+ ) : visibleItems.length === 0 ? (
110
+ <Text type="body" color="secondary">
111
+ {t('page.__PAGE__.noResults')}
112
+ </Text>
113
+ ) : (
114
+ <List
115
+ density="balanced"
116
+ hasDividers
117
+ header={
118
+ <VisuallyHidden>
119
+ <Heading level={2}>{t('page.__PAGE__.listTitle')}</Heading>
120
+ </VisuallyHidden>
121
+ }
122
+ >
123
+ {visibleItems.map((item) => (
124
+ <ListItem
125
+ key={item.id}
126
+ label={t(item.titleKey)}
127
+ description={
128
+ <Stack direction="horizontal" gap={2} vAlign="center" wrap="wrap">
129
+ <Text type="supporting" color="secondary">
130
+ {t(item.metaKey)}
131
+ </Text>
132
+ <Timestamp
133
+ value={item.updatedAt}
134
+ format="date"
135
+ type="supporting"
136
+ color="secondary"
137
+ />
138
+ </Stack>
139
+ }
140
+ onClick={() => undefined}
141
+ endContent={
142
+ <MoreMenu
143
+ label={t('page.__PAGE__.row.options')}
144
+ size="sm"
145
+ alignment="end"
146
+ presentation="adaptive"
147
+ items={[
148
+ {
149
+ id: 'archive',
150
+ label: t('page.__PAGE__.row.archive'),
151
+ icon: <Icon icon="close" size="sm" />,
152
+ },
153
+ ]}
154
+ />
155
+ }
156
+ />
157
+ ))}
158
+ </List>
159
+ )}
160
+
161
+ {visibleItems.length > 0 ? (
162
+ <Stack direction="horizontal" hAlign="center">
163
+ <Button label={t('page.__PAGE__.loadMore')} variant="ghost" onClick={() => undefined} />
164
+ </Stack>
165
+ ) : null}
166
+ </Stack>
167
+ </PageShell>
168
+ )
169
+ }
170
+
171
+ /*
172
+ astryx-messages
173
+ {
174
+ "en": {
175
+ "page.__PAGE__.title": "Documents",
176
+ "page.__PAGE__.back": "Back",
177
+ "page.__PAGE__.new": "New",
178
+ "page.__PAGE__.search.label": "Search documents",
179
+ "page.__PAGE__.search.placeholder": "Search documents",
180
+ "page.__PAGE__.filter.label": "Document view",
181
+ "page.__PAGE__.filter.recent": "Recent",
182
+ "page.__PAGE__.filter.all": "All",
183
+ "page.__PAGE__.filter.archived": "Archived",
184
+ "page.__PAGE__.empty.title": "No documents yet",
185
+ "page.__PAGE__.empty.description": "Create the first document to start this collection.",
186
+ "page.__PAGE__.noResults": "No documents match this search.",
187
+ "page.__PAGE__.listTitle": "Documents",
188
+ "page.__PAGE__.row.options": "Document actions",
189
+ "page.__PAGE__.row.archive": "Archive",
190
+ "page.__PAGE__.item.brief.title": "Project brief",
191
+ "page.__PAGE__.item.brief.meta": "Product",
192
+ "page.__PAGE__.item.notes.title": "Research notes",
193
+ "page.__PAGE__.item.notes.meta": "Research",
194
+ "page.__PAGE__.item.archive.title": "Team guidelines",
195
+ "page.__PAGE__.item.archive.meta": "Operations",
196
+ "page.__PAGE__.loadMore": "Load more"
197
+ },
198
+ "zh-CN": {
199
+ "page.__PAGE__.title": "文档",
200
+ "page.__PAGE__.back": "返回",
201
+ "page.__PAGE__.new": "新建",
202
+ "page.__PAGE__.search.label": "搜索文档",
203
+ "page.__PAGE__.search.placeholder": "搜索文档",
204
+ "page.__PAGE__.filter.label": "文档视图",
205
+ "page.__PAGE__.filter.recent": "最近",
206
+ "page.__PAGE__.filter.all": "全部",
207
+ "page.__PAGE__.filter.archived": "已归档",
208
+ "page.__PAGE__.empty.title": "暂无文档",
209
+ "page.__PAGE__.empty.description": "创建第一份文档来开始这个集合。",
210
+ "page.__PAGE__.noResults": "没有文档符合此搜索。",
211
+ "page.__PAGE__.listTitle": "文档",
212
+ "page.__PAGE__.row.options": "文档操作",
213
+ "page.__PAGE__.row.archive": "归档",
214
+ "page.__PAGE__.item.brief.title": "项目简介",
215
+ "page.__PAGE__.item.brief.meta": "产品",
216
+ "page.__PAGE__.item.notes.title": "研究笔记",
217
+ "page.__PAGE__.item.notes.meta": "研究",
218
+ "page.__PAGE__.item.archive.title": "团队指南",
219
+ "page.__PAGE__.item.archive.meta": "运营",
220
+ "page.__PAGE__.loadMore": "加载更多"
221
+ }
222
+ }
223
+ */
@@ -0,0 +1,19 @@
1
+ /** @type {import('@astryxdesign/cli/authoring').IntegrationTemplateDoc} */
2
+ export default {
3
+ type: 'page',
4
+ name: 'Settings page',
5
+ displayName: 'Settings page',
6
+ description:
7
+ 'A mobile-first settings surface with compact tabs, a wide side navigation, and one scrollable section panel.',
8
+ category: 'Settings - Sections',
9
+ keywords: [
10
+ 'settings page',
11
+ 'preferences',
12
+ 'sections',
13
+ 'mobile',
14
+ 'mobile settings',
15
+ 'account settings',
16
+ ],
17
+ replaces: 'settings',
18
+ isReady: true,
19
+ }
@@ -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
+ }