openuispec 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.
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/cli/index.ts +49 -0
- package/cli/init.ts +390 -0
- package/drift/index.ts +398 -0
- package/examples/taskflow/README.md +103 -0
- package/examples/taskflow/contracts/README.md +18 -0
- package/examples/taskflow/contracts/action_trigger.yaml +7 -0
- package/examples/taskflow/contracts/collection.yaml +7 -0
- package/examples/taskflow/contracts/data_display.yaml +7 -0
- package/examples/taskflow/contracts/feedback.yaml +7 -0
- package/examples/taskflow/contracts/input_field.yaml +7 -0
- package/examples/taskflow/contracts/nav_container.yaml +7 -0
- package/examples/taskflow/contracts/surface.yaml +7 -0
- package/examples/taskflow/contracts/x_media_player.yaml +185 -0
- package/examples/taskflow/flows/create_task.yaml +171 -0
- package/examples/taskflow/flows/edit_task.yaml +131 -0
- package/examples/taskflow/locales/en.json +158 -0
- package/examples/taskflow/openuispec.yaml +144 -0
- package/examples/taskflow/platform/android.yaml +32 -0
- package/examples/taskflow/platform/ios.yaml +39 -0
- package/examples/taskflow/platform/web.yaml +35 -0
- package/examples/taskflow/screens/calendar.yaml +23 -0
- package/examples/taskflow/screens/home.yaml +220 -0
- package/examples/taskflow/screens/profile_edit.yaml +70 -0
- package/examples/taskflow/screens/project_detail.yaml +65 -0
- package/examples/taskflow/screens/projects.yaml +142 -0
- package/examples/taskflow/screens/settings.yaml +178 -0
- package/examples/taskflow/screens/task_detail.yaml +317 -0
- package/examples/taskflow/tokens/color.yaml +88 -0
- package/examples/taskflow/tokens/elevation.yaml +27 -0
- package/examples/taskflow/tokens/icons.yaml +189 -0
- package/examples/taskflow/tokens/layout.yaml +156 -0
- package/examples/taskflow/tokens/motion.yaml +41 -0
- package/examples/taskflow/tokens/spacing.yaml +23 -0
- package/examples/taskflow/tokens/themes.yaml +34 -0
- package/examples/taskflow/tokens/typography.yaml +61 -0
- package/package.json +43 -0
- package/schema/custom-contract.schema.json +257 -0
- package/schema/defs/action.schema.json +272 -0
- package/schema/defs/adaptive.schema.json +13 -0
- package/schema/defs/common.schema.json +330 -0
- package/schema/defs/data-binding.schema.json +119 -0
- package/schema/defs/validation.schema.json +121 -0
- package/schema/flow.schema.json +164 -0
- package/schema/locale.schema.json +26 -0
- package/schema/openuispec.schema.json +287 -0
- package/schema/platform.schema.json +95 -0
- package/schema/screen.schema.json +346 -0
- package/schema/tokens/color.schema.json +104 -0
- package/schema/tokens/elevation.schema.json +84 -0
- package/schema/tokens/icons.schema.json +149 -0
- package/schema/tokens/layout.schema.json +170 -0
- package/schema/tokens/motion.schema.json +83 -0
- package/schema/tokens/spacing.schema.json +93 -0
- package/schema/tokens/themes.schema.json +92 -0
- package/schema/tokens/typography.schema.json +106 -0
- package/schema/validate.ts +258 -0
- package/spec/openuispec-v0.1.md +3677 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# TaskFlow — Projects Screen
|
|
3
|
+
# ============================================================
|
|
4
|
+
# Exercises: collection (grid), data_display (card),
|
|
5
|
+
# action_trigger, surface (modal for new project),
|
|
6
|
+
# adaptive layout (grid columns)
|
|
7
|
+
# ============================================================
|
|
8
|
+
|
|
9
|
+
projects:
|
|
10
|
+
semantic: "Lists all projects as a grid of cards"
|
|
11
|
+
|
|
12
|
+
data:
|
|
13
|
+
projects:
|
|
14
|
+
source: "api.projects.list"
|
|
15
|
+
|
|
16
|
+
layout:
|
|
17
|
+
type: scroll_vertical
|
|
18
|
+
safe_area: true
|
|
19
|
+
padding: "spacing.page_margin"
|
|
20
|
+
|
|
21
|
+
sections:
|
|
22
|
+
- id: header
|
|
23
|
+
layout: { type: row, justify: "space-between", align: "center" }
|
|
24
|
+
children:
|
|
25
|
+
- contract: data_display
|
|
26
|
+
variant: inline
|
|
27
|
+
props:
|
|
28
|
+
title: "$t:projects.title"
|
|
29
|
+
tokens_override:
|
|
30
|
+
title_style: "typography.heading_lg"
|
|
31
|
+
- contract: action_trigger
|
|
32
|
+
variant: tertiary
|
|
33
|
+
size: sm
|
|
34
|
+
props:
|
|
35
|
+
label: "$t:projects.new_project"
|
|
36
|
+
icon: "plus_circle"
|
|
37
|
+
action:
|
|
38
|
+
type: present
|
|
39
|
+
surface: "new_project_dialog"
|
|
40
|
+
|
|
41
|
+
- id: project_grid
|
|
42
|
+
margin_top: "spacing.md"
|
|
43
|
+
contract: collection
|
|
44
|
+
variant: grid
|
|
45
|
+
props:
|
|
46
|
+
data: "projects"
|
|
47
|
+
item_contract: data_display
|
|
48
|
+
item_variant: card
|
|
49
|
+
item_props_map:
|
|
50
|
+
title: "item.name"
|
|
51
|
+
subtitle: "$t:projects.task_count"
|
|
52
|
+
t_params:
|
|
53
|
+
count: "item.task_count"
|
|
54
|
+
leading:
|
|
55
|
+
icon: "item.icon"
|
|
56
|
+
color: "item.color"
|
|
57
|
+
size: 24
|
|
58
|
+
background: "item.color (10% opacity)"
|
|
59
|
+
radius: "spacing.sm"
|
|
60
|
+
padding: "spacing.sm"
|
|
61
|
+
interactive: true
|
|
62
|
+
|
|
63
|
+
empty_state:
|
|
64
|
+
icon: "folder_fill"
|
|
65
|
+
title: "$t:projects.empty_title"
|
|
66
|
+
body: "$t:projects.empty_body"
|
|
67
|
+
|
|
68
|
+
adaptive:
|
|
69
|
+
compact: { columns: 1 }
|
|
70
|
+
regular: { columns: 2 }
|
|
71
|
+
expanded: { columns: 3 }
|
|
72
|
+
|
|
73
|
+
action:
|
|
74
|
+
type: navigate
|
|
75
|
+
destination: "screens/project_detail"
|
|
76
|
+
params: { project_id: "item.id" }
|
|
77
|
+
|
|
78
|
+
surfaces:
|
|
79
|
+
new_project_dialog:
|
|
80
|
+
contract: surface
|
|
81
|
+
variant: modal
|
|
82
|
+
props:
|
|
83
|
+
title: "$t:projects.dialog_title"
|
|
84
|
+
size: sm
|
|
85
|
+
content:
|
|
86
|
+
- layout: { type: stack, spacing: "spacing.md" }
|
|
87
|
+
children:
|
|
88
|
+
- contract: input_field
|
|
89
|
+
input_type: text
|
|
90
|
+
props:
|
|
91
|
+
label: "$t:projects.field_name"
|
|
92
|
+
placeholder: "$t:projects.field_name_placeholder"
|
|
93
|
+
required: true
|
|
94
|
+
data_binding: "form.name"
|
|
95
|
+
|
|
96
|
+
- contract: input_field
|
|
97
|
+
input_type: select
|
|
98
|
+
props:
|
|
99
|
+
label: "$t:projects.field_color"
|
|
100
|
+
options:
|
|
101
|
+
- { value: "#5B4FE8", label: "Indigo" }
|
|
102
|
+
- { value: "#E8634F", label: "Coral" }
|
|
103
|
+
- { value: "#2D9D5E", label: "Green" }
|
|
104
|
+
- { value: "#D4920E", label: "Amber" }
|
|
105
|
+
- { value: "#3B82D4", label: "Blue" }
|
|
106
|
+
- { value: "#9B59B6", label: "Purple" }
|
|
107
|
+
data_binding: "form.color"
|
|
108
|
+
|
|
109
|
+
- contract: input_field
|
|
110
|
+
input_type: select
|
|
111
|
+
props:
|
|
112
|
+
label: "$t:projects.field_icon"
|
|
113
|
+
options:
|
|
114
|
+
- { value: "folder", label: "Folder" }
|
|
115
|
+
- { value: "briefcase", label: "Briefcase" }
|
|
116
|
+
- { value: "rocket", label: "Rocket" }
|
|
117
|
+
- { value: "star", label: "Star" }
|
|
118
|
+
- { value: "heart", label: "Heart" }
|
|
119
|
+
- { value: "lightbulb", label: "Lightbulb" }
|
|
120
|
+
data_binding: "form.icon"
|
|
121
|
+
|
|
122
|
+
- layout: { type: row, spacing: "spacing.sm", justify: "end" }
|
|
123
|
+
children:
|
|
124
|
+
- contract: action_trigger
|
|
125
|
+
variant: secondary
|
|
126
|
+
size: sm
|
|
127
|
+
props: { label: "$t:common.cancel" }
|
|
128
|
+
action: { type: dismiss }
|
|
129
|
+
- contract: action_trigger
|
|
130
|
+
variant: primary
|
|
131
|
+
size: sm
|
|
132
|
+
props: { label: "$t:common.create" }
|
|
133
|
+
action:
|
|
134
|
+
type: api_call
|
|
135
|
+
endpoint: "api.projects.create"
|
|
136
|
+
body: "form"
|
|
137
|
+
on_success:
|
|
138
|
+
type: sequence
|
|
139
|
+
actions:
|
|
140
|
+
- { type: dismiss }
|
|
141
|
+
- { type: feedback, variant: toast, message: "$t:projects.created", severity: success }
|
|
142
|
+
- { type: refresh, target: "screens/projects.projects" }
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# TaskFlow — Settings Screen
|
|
3
|
+
# ============================================================
|
|
4
|
+
# Exercises: collection (list with sections), data_display,
|
|
5
|
+
# input_field (toggle, select), feedback (dialog),
|
|
6
|
+
# action_trigger (destructive)
|
|
7
|
+
# ============================================================
|
|
8
|
+
|
|
9
|
+
settings:
|
|
10
|
+
semantic: "App preferences, account, and data management"
|
|
11
|
+
|
|
12
|
+
data:
|
|
13
|
+
user: { source: "api.auth.currentUser" }
|
|
14
|
+
preferences: { source: "api.preferences.get" }
|
|
15
|
+
|
|
16
|
+
layout:
|
|
17
|
+
type: scroll_vertical
|
|
18
|
+
safe_area: true
|
|
19
|
+
|
|
20
|
+
sections:
|
|
21
|
+
# ---------- Profile card ----------
|
|
22
|
+
- id: profile
|
|
23
|
+
padding: "spacing.page_margin"
|
|
24
|
+
contract: data_display
|
|
25
|
+
variant: card
|
|
26
|
+
interactive: true
|
|
27
|
+
props:
|
|
28
|
+
title: "{user.name}"
|
|
29
|
+
subtitle: "{user.email}"
|
|
30
|
+
leading:
|
|
31
|
+
media: "user.avatar"
|
|
32
|
+
fallback: { initials: "user.name", background: "color.brand.primary" }
|
|
33
|
+
size: 48
|
|
34
|
+
radius: 24
|
|
35
|
+
trailing: { icon: "chevron_right", color: "color.text.tertiary" }
|
|
36
|
+
action:
|
|
37
|
+
type: navigate
|
|
38
|
+
destination: "screens/profile_edit"
|
|
39
|
+
|
|
40
|
+
# ---------- Preferences section ----------
|
|
41
|
+
- id: preferences_section
|
|
42
|
+
margin_top: "spacing.lg"
|
|
43
|
+
padding_h: "spacing.page_margin"
|
|
44
|
+
layout: { type: stack, spacing: "spacing.none" }
|
|
45
|
+
children:
|
|
46
|
+
- contract: data_display
|
|
47
|
+
variant: inline
|
|
48
|
+
props: { title: "$t:settings.preferences" }
|
|
49
|
+
tokens_override:
|
|
50
|
+
title_style: "typography.overline"
|
|
51
|
+
title_color: "color.text.tertiary"
|
|
52
|
+
margin_bottom: "spacing.sm"
|
|
53
|
+
|
|
54
|
+
# Theme picker
|
|
55
|
+
- contract: input_field
|
|
56
|
+
input_type: select
|
|
57
|
+
props:
|
|
58
|
+
label: "$t:settings.theme"
|
|
59
|
+
options:
|
|
60
|
+
- { value: "system", label: "$t:settings.theme_system" }
|
|
61
|
+
- { value: "light", label: "$t:settings.theme_light" }
|
|
62
|
+
- { value: "dark", label: "$t:settings.theme_dark" }
|
|
63
|
+
- { value: "warm", label: "$t:settings.theme_warm" }
|
|
64
|
+
value: "preferences.theme"
|
|
65
|
+
data_binding: "preferences.theme"
|
|
66
|
+
action:
|
|
67
|
+
type: api_call
|
|
68
|
+
endpoint: "api.preferences.update"
|
|
69
|
+
params: { key: "theme", value: "preferences.theme" }
|
|
70
|
+
|
|
71
|
+
# Default priority
|
|
72
|
+
- contract: input_field
|
|
73
|
+
input_type: select
|
|
74
|
+
props:
|
|
75
|
+
label: "$t:settings.default_priority"
|
|
76
|
+
options:
|
|
77
|
+
- { value: "low", label: "$t:priority.low" }
|
|
78
|
+
- { value: "medium", label: "$t:priority.medium" }
|
|
79
|
+
- { value: "high", label: "$t:priority.high" }
|
|
80
|
+
value: "preferences.default_priority"
|
|
81
|
+
data_binding: "preferences.default_priority"
|
|
82
|
+
|
|
83
|
+
# Notifications toggle
|
|
84
|
+
- contract: input_field
|
|
85
|
+
input_type: toggle
|
|
86
|
+
props:
|
|
87
|
+
label: "$t:settings.notifications"
|
|
88
|
+
value: "preferences.notifications_enabled"
|
|
89
|
+
data_binding: "preferences.notifications_enabled"
|
|
90
|
+
|
|
91
|
+
# Due date reminders toggle
|
|
92
|
+
- contract: input_field
|
|
93
|
+
input_type: toggle
|
|
94
|
+
props:
|
|
95
|
+
label: "$t:settings.reminders"
|
|
96
|
+
value: "preferences.reminders_enabled"
|
|
97
|
+
helper_text: "$t:settings.reminders_helper"
|
|
98
|
+
data_binding: "preferences.reminders_enabled"
|
|
99
|
+
|
|
100
|
+
# ---------- Data section ----------
|
|
101
|
+
- id: data_section
|
|
102
|
+
margin_top: "spacing.xl"
|
|
103
|
+
padding_h: "spacing.page_margin"
|
|
104
|
+
layout: { type: stack, spacing: "spacing.none" }
|
|
105
|
+
children:
|
|
106
|
+
- contract: data_display
|
|
107
|
+
variant: inline
|
|
108
|
+
props: { title: "$t:settings.data" }
|
|
109
|
+
tokens_override:
|
|
110
|
+
title_style: "typography.overline"
|
|
111
|
+
title_color: "color.text.tertiary"
|
|
112
|
+
margin_bottom: "spacing.sm"
|
|
113
|
+
|
|
114
|
+
# Export data
|
|
115
|
+
- contract: data_display
|
|
116
|
+
variant: compact
|
|
117
|
+
interactive: true
|
|
118
|
+
props:
|
|
119
|
+
title: "$t:settings.export"
|
|
120
|
+
leading: { icon: "square_arrow_up", color: "color.text.secondary", size: 18 }
|
|
121
|
+
trailing: { icon: "chevron_right", color: "color.text.tertiary" }
|
|
122
|
+
action:
|
|
123
|
+
type: api_call
|
|
124
|
+
endpoint: "api.data.export"
|
|
125
|
+
on_success:
|
|
126
|
+
feedback: { variant: toast, message: "$t:settings.export_success", severity: success }
|
|
127
|
+
|
|
128
|
+
# Delete account
|
|
129
|
+
- contract: action_trigger
|
|
130
|
+
variant: ghost
|
|
131
|
+
full_width: true
|
|
132
|
+
props:
|
|
133
|
+
label: "$t:settings.delete_account"
|
|
134
|
+
icon: "trash"
|
|
135
|
+
tokens_override:
|
|
136
|
+
text: "color.semantic.danger"
|
|
137
|
+
action:
|
|
138
|
+
type: confirm
|
|
139
|
+
confirmation:
|
|
140
|
+
contract: feedback
|
|
141
|
+
variant: dialog
|
|
142
|
+
props:
|
|
143
|
+
title: "$t:settings.delete_title"
|
|
144
|
+
message: "$t:settings.delete_message"
|
|
145
|
+
severity: error
|
|
146
|
+
actions:
|
|
147
|
+
- label: "$t:common.cancel"
|
|
148
|
+
variant: secondary
|
|
149
|
+
action: { type: dismiss }
|
|
150
|
+
- label: "$t:settings.delete_confirm"
|
|
151
|
+
variant: destructive
|
|
152
|
+
action:
|
|
153
|
+
type: api_call
|
|
154
|
+
endpoint: "api.auth.deleteAccount"
|
|
155
|
+
on_success:
|
|
156
|
+
type: navigate
|
|
157
|
+
destination: "$root"
|
|
158
|
+
|
|
159
|
+
# ---------- App info ----------
|
|
160
|
+
- id: app_info
|
|
161
|
+
margin_top: "spacing.xl"
|
|
162
|
+
padding: "spacing.page_margin"
|
|
163
|
+
layout: { type: stack, spacing: "spacing.xs", align: "center" }
|
|
164
|
+
children:
|
|
165
|
+
- contract: data_display
|
|
166
|
+
variant: inline
|
|
167
|
+
props:
|
|
168
|
+
title: "$t:settings.app_version"
|
|
169
|
+
tokens_override:
|
|
170
|
+
title_style: "typography.caption"
|
|
171
|
+
title_color: "color.text.tertiary"
|
|
172
|
+
- contract: data_display
|
|
173
|
+
variant: inline
|
|
174
|
+
props:
|
|
175
|
+
title: "$t:settings.app_credit"
|
|
176
|
+
tokens_override:
|
|
177
|
+
title_style: "typography.caption"
|
|
178
|
+
title_color: "color.text.tertiary"
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# TaskFlow — Task Detail Screen
|
|
3
|
+
# ============================================================
|
|
4
|
+
# Exercises: data_display (hero, stat, compact), action_trigger,
|
|
5
|
+
# feedback (dialog), surface (sheet), input_field,
|
|
6
|
+
# adaptive layout (stat reflow, action arrangement)
|
|
7
|
+
# ============================================================
|
|
8
|
+
|
|
9
|
+
task_detail:
|
|
10
|
+
semantic: "Displays full task information with edit and delete actions"
|
|
11
|
+
|
|
12
|
+
params:
|
|
13
|
+
task_id: { type: string, required: true }
|
|
14
|
+
|
|
15
|
+
data:
|
|
16
|
+
task:
|
|
17
|
+
source: "api.tasks.getById"
|
|
18
|
+
params: { id: "params.task_id" }
|
|
19
|
+
|
|
20
|
+
layout:
|
|
21
|
+
type: scroll_vertical
|
|
22
|
+
safe_area: true
|
|
23
|
+
|
|
24
|
+
sections:
|
|
25
|
+
# ---------- Status + Priority header ----------
|
|
26
|
+
- id: header
|
|
27
|
+
padding: "spacing.page_margin"
|
|
28
|
+
layout: { type: stack, spacing: "spacing.md" }
|
|
29
|
+
children:
|
|
30
|
+
- contract: data_display
|
|
31
|
+
variant: hero
|
|
32
|
+
props:
|
|
33
|
+
title: "{task.title}"
|
|
34
|
+
badge:
|
|
35
|
+
text: "{task.status | format:status_label}"
|
|
36
|
+
severity: "{task.status | map:status_severity}"
|
|
37
|
+
metadata:
|
|
38
|
+
priority: "{task.priority | format:priority_label}"
|
|
39
|
+
|
|
40
|
+
# Stat cards — adaptive: grid on compact, row on regular+
|
|
41
|
+
- id: stat_group
|
|
42
|
+
layout:
|
|
43
|
+
adaptive:
|
|
44
|
+
compact:
|
|
45
|
+
type: grid
|
|
46
|
+
columns: 2
|
|
47
|
+
gap: "spacing.sm"
|
|
48
|
+
regular:
|
|
49
|
+
type: row
|
|
50
|
+
spacing: "spacing.sm"
|
|
51
|
+
children:
|
|
52
|
+
- contract: data_display
|
|
53
|
+
variant: stat
|
|
54
|
+
props:
|
|
55
|
+
title: "$t:task_detail.status"
|
|
56
|
+
body: "{task.status | format:status_label}"
|
|
57
|
+
leading:
|
|
58
|
+
icon: "circle_fill"
|
|
59
|
+
color: "color.status.{task.status}"
|
|
60
|
+
size: 10
|
|
61
|
+
|
|
62
|
+
- contract: data_display
|
|
63
|
+
variant: stat
|
|
64
|
+
props:
|
|
65
|
+
title: "$t:task_detail.priority"
|
|
66
|
+
body: "{task.priority | format:priority_label}"
|
|
67
|
+
leading:
|
|
68
|
+
icon: "flag_fill"
|
|
69
|
+
color: "color.priority.{task.priority}"
|
|
70
|
+
size: 14
|
|
71
|
+
|
|
72
|
+
- contract: data_display
|
|
73
|
+
variant: stat
|
|
74
|
+
props:
|
|
75
|
+
title: "$t:task_detail.due"
|
|
76
|
+
body: "{task.due_date | format:date_relative}"
|
|
77
|
+
|
|
78
|
+
# ---------- Description ----------
|
|
79
|
+
- id: description
|
|
80
|
+
padding_h: "spacing.page_margin"
|
|
81
|
+
margin_top: "spacing.md"
|
|
82
|
+
condition: "task.description != null"
|
|
83
|
+
layout: { type: stack, spacing: "spacing.xs" }
|
|
84
|
+
adaptive:
|
|
85
|
+
expanded:
|
|
86
|
+
max_width: 640
|
|
87
|
+
children:
|
|
88
|
+
- contract: data_display
|
|
89
|
+
variant: inline
|
|
90
|
+
props:
|
|
91
|
+
title: "$t:task_detail.description"
|
|
92
|
+
tokens_override:
|
|
93
|
+
title_style: "typography.overline"
|
|
94
|
+
title_color: "color.text.tertiary"
|
|
95
|
+
- contract: data_display
|
|
96
|
+
variant: inline
|
|
97
|
+
props:
|
|
98
|
+
title: "{task.description}"
|
|
99
|
+
tokens_override:
|
|
100
|
+
title_style: "typography.body"
|
|
101
|
+
|
|
102
|
+
# ---------- Media attachment ----------
|
|
103
|
+
- id: media_attachment
|
|
104
|
+
condition: "task.attachment.media_type != null"
|
|
105
|
+
padding_h: "spacing.page_margin"
|
|
106
|
+
margin_top: "spacing.md"
|
|
107
|
+
children:
|
|
108
|
+
- contract: x_media_player
|
|
109
|
+
variant: inline
|
|
110
|
+
props:
|
|
111
|
+
source: "{task.attachment.url}"
|
|
112
|
+
media_type: "{task.attachment.media_type}"
|
|
113
|
+
title: "{task.attachment.filename}"
|
|
114
|
+
poster: "{task.attachment.thumbnail}"
|
|
115
|
+
show_controls: true
|
|
116
|
+
tokens_override:
|
|
117
|
+
radius: "spacing.md"
|
|
118
|
+
adaptive:
|
|
119
|
+
compact: { variant: inline }
|
|
120
|
+
expanded: { variant: inline, max_width: 640 }
|
|
121
|
+
|
|
122
|
+
# ---------- Details section ----------
|
|
123
|
+
- id: details
|
|
124
|
+
padding_h: "spacing.page_margin"
|
|
125
|
+
margin_top: "spacing.lg"
|
|
126
|
+
layout: { type: stack, spacing: "spacing.none" }
|
|
127
|
+
adaptive:
|
|
128
|
+
expanded:
|
|
129
|
+
max_width: 640
|
|
130
|
+
children:
|
|
131
|
+
- contract: data_display
|
|
132
|
+
variant: inline
|
|
133
|
+
props:
|
|
134
|
+
title: "$t:task_detail.details"
|
|
135
|
+
tokens_override:
|
|
136
|
+
title_style: "typography.overline"
|
|
137
|
+
title_color: "color.text.tertiary"
|
|
138
|
+
|
|
139
|
+
# Project row
|
|
140
|
+
- contract: data_display
|
|
141
|
+
variant: compact
|
|
142
|
+
interactive: true
|
|
143
|
+
props:
|
|
144
|
+
title: "$t:task_detail.project"
|
|
145
|
+
trailing: "{task.project.name}"
|
|
146
|
+
leading: { icon: "folder", color: "color.text.secondary", size: 18 }
|
|
147
|
+
action:
|
|
148
|
+
type: navigate
|
|
149
|
+
destination: "screens/project_detail"
|
|
150
|
+
params: { project_id: "task.project_id" }
|
|
151
|
+
|
|
152
|
+
# Assignee row
|
|
153
|
+
- contract: data_display
|
|
154
|
+
variant: compact
|
|
155
|
+
interactive: true
|
|
156
|
+
props:
|
|
157
|
+
title: "$t:task_detail.assignee"
|
|
158
|
+
trailing: "{task.assignee.name | default:$t:task_detail.unassigned}"
|
|
159
|
+
leading: { icon: "person", color: "color.text.secondary", size: 18 }
|
|
160
|
+
action:
|
|
161
|
+
type: present
|
|
162
|
+
surface: "assignee_picker"
|
|
163
|
+
|
|
164
|
+
# Tags row
|
|
165
|
+
- contract: data_display
|
|
166
|
+
variant: compact
|
|
167
|
+
props:
|
|
168
|
+
title: "$t:task_detail.tags"
|
|
169
|
+
trailing:
|
|
170
|
+
contract: collection
|
|
171
|
+
variant: chips
|
|
172
|
+
props:
|
|
173
|
+
data: "task.tags"
|
|
174
|
+
item_contract: data_display
|
|
175
|
+
item_variant: inline
|
|
176
|
+
item_props_map:
|
|
177
|
+
title: "item"
|
|
178
|
+
leading: { icon: "tag", color: "color.text.secondary", size: 18 }
|
|
179
|
+
|
|
180
|
+
# Created row
|
|
181
|
+
- contract: data_display
|
|
182
|
+
variant: compact
|
|
183
|
+
props:
|
|
184
|
+
title: "$t:task_detail.created"
|
|
185
|
+
trailing: "{task.created_at | format:date}"
|
|
186
|
+
leading: { icon: "clock", color: "color.text.secondary", size: 18 }
|
|
187
|
+
|
|
188
|
+
# ---------- Actions ----------
|
|
189
|
+
- id: actions
|
|
190
|
+
padding: "spacing.page_margin"
|
|
191
|
+
margin_top: "spacing.xl"
|
|
192
|
+
layout:
|
|
193
|
+
adaptive:
|
|
194
|
+
compact:
|
|
195
|
+
type: stack
|
|
196
|
+
spacing: "spacing.sm"
|
|
197
|
+
regular:
|
|
198
|
+
type: row
|
|
199
|
+
spacing: "spacing.sm"
|
|
200
|
+
wrap: true
|
|
201
|
+
children:
|
|
202
|
+
- contract: action_trigger
|
|
203
|
+
variant: primary
|
|
204
|
+
props:
|
|
205
|
+
label: "$t:task_detail.edit"
|
|
206
|
+
icon: "pencil"
|
|
207
|
+
adaptive:
|
|
208
|
+
compact: { full_width: true }
|
|
209
|
+
regular: { full_width: false }
|
|
210
|
+
action:
|
|
211
|
+
type: navigate
|
|
212
|
+
destination: "flows/edit_task"
|
|
213
|
+
params: { task_id: "task.id" }
|
|
214
|
+
presentation: "sheet"
|
|
215
|
+
|
|
216
|
+
- contract: action_trigger
|
|
217
|
+
variant: secondary
|
|
218
|
+
props:
|
|
219
|
+
label: "$t:task_detail.toggle_status"
|
|
220
|
+
t_params:
|
|
221
|
+
is_done: "task.status == done"
|
|
222
|
+
icon: "{task.status == done ? 'arrow_uturn_left' : 'checkmark'}"
|
|
223
|
+
adaptive:
|
|
224
|
+
compact: { full_width: true }
|
|
225
|
+
regular: { full_width: false }
|
|
226
|
+
action:
|
|
227
|
+
type: api_call
|
|
228
|
+
endpoint: "api.tasks.toggleStatus"
|
|
229
|
+
params: { id: "task.id" }
|
|
230
|
+
optimistic:
|
|
231
|
+
target: "task.status"
|
|
232
|
+
value: "{task.status == done ? 'todo' : 'done'}"
|
|
233
|
+
revert_on_error: true
|
|
234
|
+
on_success:
|
|
235
|
+
type: feedback
|
|
236
|
+
variant: toast
|
|
237
|
+
message: "$t:task_detail.task_updated"
|
|
238
|
+
severity: success
|
|
239
|
+
duration: 3000
|
|
240
|
+
on_error:
|
|
241
|
+
type: feedback
|
|
242
|
+
variant: toast
|
|
243
|
+
message: "$t:task_detail.update_error"
|
|
244
|
+
severity: error
|
|
245
|
+
|
|
246
|
+
- contract: action_trigger
|
|
247
|
+
variant: destructive
|
|
248
|
+
props:
|
|
249
|
+
label: "$t:task_detail.delete"
|
|
250
|
+
icon: "trash"
|
|
251
|
+
adaptive:
|
|
252
|
+
compact: { full_width: true }
|
|
253
|
+
regular: { full_width: false }
|
|
254
|
+
action:
|
|
255
|
+
type: confirm
|
|
256
|
+
confirmation:
|
|
257
|
+
contract: feedback
|
|
258
|
+
variant: dialog
|
|
259
|
+
props:
|
|
260
|
+
title: "$t:task_detail.delete_title"
|
|
261
|
+
message: "$t:task_detail.delete_message"
|
|
262
|
+
t_params:
|
|
263
|
+
title: "task.title"
|
|
264
|
+
actions:
|
|
265
|
+
- label: "$t:common.cancel"
|
|
266
|
+
variant: secondary
|
|
267
|
+
action: { type: dismiss }
|
|
268
|
+
- label: "$t:common.delete"
|
|
269
|
+
variant: destructive
|
|
270
|
+
action:
|
|
271
|
+
type: api_call
|
|
272
|
+
endpoint: "api.tasks.delete"
|
|
273
|
+
params: { id: "task.id" }
|
|
274
|
+
on_success:
|
|
275
|
+
type: sequence
|
|
276
|
+
actions:
|
|
277
|
+
- type: navigate
|
|
278
|
+
destination: "$back"
|
|
279
|
+
- type: feedback
|
|
280
|
+
variant: toast
|
|
281
|
+
message: "$t:task_detail.task_deleted"
|
|
282
|
+
severity: neutral
|
|
283
|
+
|
|
284
|
+
# ---------- Surfaces ----------
|
|
285
|
+
surfaces:
|
|
286
|
+
assignee_picker:
|
|
287
|
+
contract: surface
|
|
288
|
+
props:
|
|
289
|
+
title: "$t:task_detail.assign_to"
|
|
290
|
+
content:
|
|
291
|
+
- contract: input_field
|
|
292
|
+
input_type: text
|
|
293
|
+
props:
|
|
294
|
+
label: "$t:task_detail.search_people"
|
|
295
|
+
placeholder: "$t:task_detail.search_people_placeholder"
|
|
296
|
+
icon: { ref: "search", position: leading }
|
|
297
|
+
- contract: collection
|
|
298
|
+
variant: list
|
|
299
|
+
props:
|
|
300
|
+
data: "api.users.list"
|
|
301
|
+
item_contract: data_display
|
|
302
|
+
item_variant: compact
|
|
303
|
+
item_props_map:
|
|
304
|
+
title: "item.name"
|
|
305
|
+
subtitle: "item.email"
|
|
306
|
+
leading:
|
|
307
|
+
media: "item.avatar"
|
|
308
|
+
fallback: { initials: "item.name", background: "color.brand.primary" }
|
|
309
|
+
interactive: true
|
|
310
|
+
action:
|
|
311
|
+
type: api_call
|
|
312
|
+
endpoint: "api.tasks.assignTo"
|
|
313
|
+
params: { task_id: "task.id", user_id: "item.id" }
|
|
314
|
+
on_success: { type: dismiss }
|
|
315
|
+
adaptive:
|
|
316
|
+
compact: { variant: sheet, detents: [medium] }
|
|
317
|
+
expanded: { variant: panel, width: 360 }
|