@prsm/mono-components 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.
Files changed (90) hide show
  1. package/.claude/settings.local.json +13 -0
  2. package/.lore +83 -0
  3. package/histoire.config.js +43 -0
  4. package/package.json +39 -0
  5. package/postcss.config.js +6 -0
  6. package/src/components/Badge.vue +36 -0
  7. package/src/components/Button.vue +44 -0
  8. package/src/components/Checkbox.vue +51 -0
  9. package/src/components/CheckboxCards.vue +61 -0
  10. package/src/components/CodeEditor.vue +299 -0
  11. package/src/components/Collapsible.vue +69 -0
  12. package/src/components/CollapsibleGroup.vue +38 -0
  13. package/src/components/Combobox.vue +179 -0
  14. package/src/components/ContextMenu.vue +65 -0
  15. package/src/components/ContextMenuPanel.vue +115 -0
  16. package/src/components/DataTable.vue +326 -0
  17. package/src/components/Dropdown.vue +127 -0
  18. package/src/components/GhostInput.vue +29 -0
  19. package/src/components/Input.vue +23 -0
  20. package/src/components/KeyValue.vue +149 -0
  21. package/src/components/LabeledTextarea.vue +64 -0
  22. package/src/components/LabeledTextareaGroup.vue +14 -0
  23. package/src/components/Mention.vue +79 -0
  24. package/src/components/Modal.vue +109 -0
  25. package/src/components/MultiCombobox.vue +209 -0
  26. package/src/components/NavTree.vue +98 -0
  27. package/src/components/NumberInput.vue +128 -0
  28. package/src/components/PopConfirm.vue +94 -0
  29. package/src/components/Popover.vue +53 -0
  30. package/src/components/RadioCards.vue +37 -0
  31. package/src/components/RadioGroup.vue +57 -0
  32. package/src/components/RangeSlider.vue +165 -0
  33. package/src/components/ScrollBox.vue +78 -0
  34. package/src/components/SectionHeader.vue +18 -0
  35. package/src/components/Select.vue +187 -0
  36. package/src/components/Switch.vue +85 -0
  37. package/src/components/Tabs.vue +34 -0
  38. package/src/components/TagInput.vue +80 -0
  39. package/src/components/Textarea.vue +97 -0
  40. package/src/components/ToastContainer.vue +104 -0
  41. package/src/components/ToggleButtons.vue +45 -0
  42. package/src/components/ToggleGroup.vue +30 -0
  43. package/src/components/Tooltip.vue +56 -0
  44. package/src/components/Tree.vue +188 -0
  45. package/src/composables/toast.js +54 -0
  46. package/src/composables/useClickOutside.js +23 -0
  47. package/src/composables/useMention.js +291 -0
  48. package/src/composables/usePointerDrag.js +39 -0
  49. package/src/histoire-setup.js +1 -0
  50. package/src/index.js +43 -0
  51. package/src/style.css +96 -0
  52. package/stories/Badge.story.vue +24 -0
  53. package/stories/Button.story.vue +45 -0
  54. package/stories/Checkbox.story.vue +31 -0
  55. package/stories/CheckboxCards.story.vue +51 -0
  56. package/stories/CodeEditor.story.vue +71 -0
  57. package/stories/Collapsible.story.vue +84 -0
  58. package/stories/Combobox.story.vue +44 -0
  59. package/stories/ContextMenu.story.vue +59 -0
  60. package/stories/DataTable.story.vue +185 -0
  61. package/stories/Dropdown.story.vue +49 -0
  62. package/stories/GhostInput.story.vue +24 -0
  63. package/stories/Input.story.vue +23 -0
  64. package/stories/KeyValue.story.vue +104 -0
  65. package/stories/LabeledTextarea.story.vue +44 -0
  66. package/stories/Mention.story.vue +166 -0
  67. package/stories/Modal.story.vue +86 -0
  68. package/stories/MultiCombobox.story.vue +76 -0
  69. package/stories/NavTree.story.vue +184 -0
  70. package/stories/NumberInput.story.vue +31 -0
  71. package/stories/Overview.story.vue +85 -0
  72. package/stories/PopConfirm.story.vue +39 -0
  73. package/stories/RadioCards.story.vue +66 -0
  74. package/stories/RadioGroup.story.vue +52 -0
  75. package/stories/RangeSlider.story.vue +75 -0
  76. package/stories/ScrollBox.story.vue +54 -0
  77. package/stories/SectionHeader.story.vue +22 -0
  78. package/stories/Select.story.vue +34 -0
  79. package/stories/Switch.story.vue +42 -0
  80. package/stories/Tabs.story.vue +34 -0
  81. package/stories/TagInput.story.vue +54 -0
  82. package/stories/Textarea.story.vue +28 -0
  83. package/stories/Toast.story.vue +28 -0
  84. package/stories/ToggleButtons.story.vue +57 -0
  85. package/stories/ToggleGroup.story.vue +34 -0
  86. package/stories/Tooltip.story.vue +55 -0
  87. package/stories/Tree.story.vue +115 -0
  88. package/tailwind.config.js +9 -0
  89. package/tailwind.preset.js +79 -0
  90. package/vite.config.js +6 -0
@@ -0,0 +1,185 @@
1
+ <script setup>
2
+ import { ref, reactive } from "vue"
3
+ import { logEvent } from "histoire/client"
4
+ import DataTable from "../src/components/DataTable.vue"
5
+ import Badge from "../src/components/Badge.vue"
6
+ import Dropdown from "../src/components/Dropdown.vue"
7
+
8
+ function makeUsers(count) {
9
+ const names = ["Alice Chen", "Bob Smith", "Carol Wu", "Dave Kim", "Eve Park", "Frank Lee", "Grace Liu", "Hank Zhao", "Iris Wang", "Jack Yang", "Kate Tan", "Leo Ng", "Mia Choi", "Nate Lin", "Olga Sun"]
10
+ const roles = ["admin", "editor", "viewer"]
11
+ const statuses = ["active", "inactive", "pending"]
12
+ return Array.from({ length: count }, (_, i) => ({
13
+ id: i + 1,
14
+ name: names[i % names.length],
15
+ email: `${names[i % names.length].toLowerCase().replace(" ", ".")}@example.com`,
16
+ role: roles[i % roles.length],
17
+ status: statuses[i % statuses.length],
18
+ active: i % 3 !== 2,
19
+ score: Math.floor(Math.random() * 100)
20
+ }))
21
+ }
22
+
23
+ const users = reactive(makeUsers(47))
24
+ const loading = ref(false)
25
+ const lastEdit = ref(null)
26
+ const lastAction = ref(null)
27
+
28
+ const columns = [
29
+ { key: "id", label: "#", width: "50px", sortable: true },
30
+ { key: "name", label: "Name", sortable: true, editable: true },
31
+ { key: "email", label: "Email", sortable: true },
32
+ { key: "role", label: "Role", type: "enum", options: ["admin", "editor", "viewer"], editable: true },
33
+ { key: "status", label: "Status", sortable: true },
34
+ { key: "active", label: "Active", type: "boolean", editable: true, width: "70px" },
35
+ { key: "score", label: "Score", type: "number", sortable: true, editable: true, width: "80px" }
36
+ ]
37
+
38
+ const badgeColumns = [
39
+ { key: "id", label: "#", width: "50px", sortable: true },
40
+ { key: "name", label: "Name", sortable: true },
41
+ { key: "email", label: "Email", sortable: true },
42
+ { key: "status", label: "Status", sortable: true },
43
+ { key: "score", label: "Score", sortable: true, width: "80px" }
44
+ ]
45
+
46
+ const actionColumns = [
47
+ { key: "id", label: "#", width: "50px" },
48
+ { key: "name", label: "Name", sortable: true },
49
+ { key: "email", label: "Email", sortable: true },
50
+ { key: "status", label: "Status", sortable: true },
51
+ { key: "actions", label: "", width: "40px" }
52
+ ]
53
+
54
+ const rowActions = [
55
+ { value: "edit", label: "Edit", icon: "material-symbols:edit-outline" },
56
+ { value: "duplicate", label: "Duplicate", icon: "material-symbols:content-copy" },
57
+ { value: "delete", label: "Delete", icon: "material-symbols:delete-outline" }
58
+ ]
59
+
60
+ function handleRowUpdate({ key, column, value }) {
61
+ const row = users.find(r => r.id === key)
62
+ if (row) row[column] = value
63
+ lastEdit.value = { key, column, value }
64
+ logEvent("update:row", { key, column, value })
65
+ }
66
+
67
+ function handleAction(option, row) {
68
+ lastAction.value = { action: option.value, row: row.name, id: row.id }
69
+ logEvent("action", { action: option.value, row: row.name, id: row.id })
70
+ }
71
+
72
+ function simulateLoading() {
73
+ loading.value = true
74
+ setTimeout(() => { loading.value = false }, 2000)
75
+ }
76
+ </script>
77
+
78
+ <template>
79
+ <Story title="DataTable">
80
+ <Variant title="Full Featured">
81
+ <div class="max-w-4xl">
82
+ <DataTable
83
+ :columns="columns"
84
+ :rows="users"
85
+ :page-size="10"
86
+ filterable
87
+ striped
88
+ @update:row="handleRowUpdate"
89
+ />
90
+ </div>
91
+ <template #controls>
92
+ <div class="text-xs text-fg-2 p-2">
93
+ <template v-if="lastEdit">
94
+ last edit: row {{ lastEdit.key }} . {{ lastEdit.column }} = {{ lastEdit.value }}
95
+ </template>
96
+ <template v-else>click an underlined cell to edit, click anywhere in a boolean cell to toggle</template>
97
+ </div>
98
+ </template>
99
+ </Variant>
100
+ <Variant title="Row Actions (Dropdown)">
101
+ <div class="max-w-3xl">
102
+ <DataTable
103
+ :columns="actionColumns"
104
+ :rows="users"
105
+ :page-size="8"
106
+ filterable
107
+ >
108
+ <template #cell-status="{ value }">
109
+ <Badge :variant="value === 'active' ? 'success' : value === 'pending' ? 'warning' : 'neutral'">
110
+ {{ value }}
111
+ </Badge>
112
+ </template>
113
+ <template #cell-actions="{ row }">
114
+ <Dropdown
115
+ inline
116
+ icon="material-symbols:more-horiz"
117
+ :options="rowActions"
118
+ @select="opt => handleAction(opt, row)"
119
+ />
120
+ </template>
121
+ </DataTable>
122
+ </div>
123
+ <template #controls>
124
+ <div class="text-xs text-fg-2 p-2">
125
+ <template v-if="lastAction">
126
+ {{ lastAction.action }} on {{ lastAction.row }} (#{{ lastAction.id }})
127
+ </template>
128
+ <template v-else>click the ... icon to see row actions</template>
129
+ </div>
130
+ </template>
131
+ </Variant>
132
+ <Variant title="Custom Cell Slot (Badge)">
133
+ <div class="max-w-3xl">
134
+ <DataTable
135
+ :columns="badgeColumns"
136
+ :rows="users"
137
+ :page-size="8"
138
+ >
139
+ <template #cell-status="{ value }">
140
+ <Badge :variant="value === 'active' ? 'success' : value === 'pending' ? 'warning' : 'neutral'">
141
+ {{ value }}
142
+ </Badge>
143
+ </template>
144
+ </DataTable>
145
+ </div>
146
+ </Variant>
147
+ <Variant title="Loading">
148
+ <div class="max-w-3xl flex flex-col gap-2">
149
+ <button
150
+ class="self-start px-2 py-1 text-base bg-2 border border-line rounded-sm font-mono cursor-pointer hover:bg-3"
151
+ @click="simulateLoading"
152
+ >
153
+ simulate loading
154
+ </button>
155
+ <DataTable
156
+ :columns="badgeColumns"
157
+ :rows="loading ? [] : users"
158
+ :loading="loading"
159
+ :page-size="5"
160
+ />
161
+ </div>
162
+ </Variant>
163
+ <Variant title="Empty State">
164
+ <div class="max-w-3xl">
165
+ <DataTable
166
+ :columns="badgeColumns"
167
+ :rows="[]"
168
+ :page-size="5"
169
+ />
170
+ </div>
171
+ </Variant>
172
+ <Variant title="Compact (5 per page)">
173
+ <div class="max-w-3xl">
174
+ <DataTable
175
+ :columns="columns"
176
+ :rows="users"
177
+ :page-size="5"
178
+ filterable
179
+ filter-placeholder="find user..."
180
+ @update:row="handleRowUpdate"
181
+ />
182
+ </div>
183
+ </Variant>
184
+ </Story>
185
+ </template>
@@ -0,0 +1,49 @@
1
+ <script setup>
2
+ import { ref } from "vue"
3
+ import Dropdown from "../src/components/Dropdown.vue"
4
+
5
+ const selected = ref("grid")
6
+ </script>
7
+
8
+ <template>
9
+ <Story title="Dropdown">
10
+ <Variant title="With Icon and Label">
11
+ <Dropdown
12
+ v-model="selected"
13
+ icon="material-symbols:view-module"
14
+ label="View"
15
+ header="Layout"
16
+ :options="[
17
+ { value: 'grid', label: 'Grid', icon: 'material-symbols:grid-view' },
18
+ { value: 'list', label: 'List', icon: 'material-symbols:view-list' },
19
+ { value: 'board', label: 'Board', icon: 'material-symbols:view-kanban' }
20
+ ]"
21
+ />
22
+ <template #controls>
23
+ <div class="text-xs text-fg-2 p-2">value: {{ selected }}</div>
24
+ </template>
25
+ </Variant>
26
+ <Variant title="With Meta">
27
+ <Dropdown
28
+ v-model="selected"
29
+ label="Sort"
30
+ :options="[
31
+ { value: 'name', label: 'Name', meta: 'A-Z' },
32
+ { value: 'date', label: 'Date', meta: 'newest' },
33
+ { value: 'size', label: 'Size', meta: 'largest' }
34
+ ]"
35
+ />
36
+ </Variant>
37
+ <Variant title="With Action">
38
+ <Dropdown
39
+ v-model="selected"
40
+ label="File"
41
+ :options="[
42
+ { value: 'save', label: 'Save' },
43
+ { value: 'export', label: 'Export' },
44
+ { value: 'delete', label: 'Delete', action: () => alert('delete!') }
45
+ ]"
46
+ />
47
+ </Variant>
48
+ </Story>
49
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup>
2
+ import { ref } from "vue"
3
+ import GhostInput from "../src/components/GhostInput.vue"
4
+
5
+ const title = ref("Untitled Project")
6
+ const description = ref("")
7
+ </script>
8
+
9
+ <template>
10
+ <Story title="GhostInput">
11
+ <Variant title="Large (Title)">
12
+ <GhostInput v-model="title" size="lg" placeholder="Untitled..." />
13
+ </Variant>
14
+ <Variant title="Base (Description)">
15
+ <GhostInput v-model="description" placeholder="Add a description..." />
16
+ </Variant>
17
+ <Variant title="Stacked">
18
+ <div class="flex flex-col gap-1">
19
+ <GhostInput v-model="title" size="lg" placeholder="Untitled..." />
20
+ <GhostInput v-model="description" placeholder="Add a description..." />
21
+ </div>
22
+ </Variant>
23
+ </Story>
24
+ </template>
@@ -0,0 +1,23 @@
1
+ <script setup>
2
+ import { ref } from "vue"
3
+ import Input from "../src/components/Input.vue"
4
+
5
+ const value = ref("")
6
+ </script>
7
+
8
+ <template>
9
+ <Story title="Input">
10
+ <Variant title="Default">
11
+ <Input v-model="value" placeholder="type something..." />
12
+ <template #controls>
13
+ <div class="text-xs text-fg-2 p-2">value: "{{ value }}"</div>
14
+ </template>
15
+ </Variant>
16
+ <Variant title="With Value">
17
+ <Input model-value="hello world" />
18
+ </Variant>
19
+ <Variant title="Password">
20
+ <Input type="password" placeholder="password..." />
21
+ </Variant>
22
+ </Story>
23
+ </template>
@@ -0,0 +1,104 @@
1
+ <script setup>
2
+ import { reactive } from "vue"
3
+ import { logEvent } from "histoire/client"
4
+ import KeyValue from "../src/components/KeyValue.vue"
5
+ import Badge from "../src/components/Badge.vue"
6
+
7
+ const user = reactive({
8
+ name: "Alice Chen",
9
+ email: "alice@example.com",
10
+ role: "admin",
11
+ active: true,
12
+ score: 92,
13
+ created: "2024-01-15",
14
+ lastLogin: "2025-05-10 14:32"
15
+ })
16
+
17
+ const userFields = [
18
+ { key: "name", label: "Name", editable: true },
19
+ { key: "email", label: "Email", editable: true },
20
+ { key: "role", label: "Role", type: "enum", options: ["admin", "editor", "viewer"], editable: true },
21
+ { key: "active", label: "Active", type: "boolean", editable: true },
22
+ { key: "score", label: "Score", type: "number", editable: true },
23
+ { key: "created", label: "Created" },
24
+ { key: "lastLogin", label: "Last login" }
25
+ ]
26
+
27
+ const config = reactive({
28
+ host: "localhost",
29
+ port: 5432,
30
+ database: "app_production",
31
+ ssl: true,
32
+ poolSize: 20,
33
+ timeout: 30000
34
+ })
35
+
36
+ const configFields = [
37
+ { key: "host", label: "Host", editable: true },
38
+ { key: "port", label: "Port", type: "number", editable: true },
39
+ { key: "database", label: "Database", editable: true },
40
+ { key: "ssl", label: "SSL", type: "boolean", editable: true },
41
+ { key: "poolSize", label: "Pool size", type: "number", editable: true },
42
+ { key: "timeout", label: "Timeout (ms)", type: "number", editable: true }
43
+ ]
44
+
45
+ const server = reactive({
46
+ status: "active",
47
+ uptime: "14d 6h 23m",
48
+ cpu: "23%",
49
+ memory: "1.2 GB / 4 GB",
50
+ requests: "12,847",
51
+ errors: "3"
52
+ })
53
+
54
+ const serverFields = [
55
+ { key: "status", label: "Status" },
56
+ { key: "uptime", label: "Uptime" },
57
+ { key: "cpu", label: "CPU" },
58
+ { key: "memory", label: "Memory" },
59
+ { key: "requests", label: "Requests" },
60
+ { key: "errors", label: "Errors" }
61
+ ]
62
+
63
+ function handleUpdate(obj, { key, value }) {
64
+ obj[key] = value
65
+ logEvent("update", { key, value })
66
+ }
67
+ </script>
68
+
69
+ <template>
70
+ <Story title="KeyValue">
71
+ <Variant title="Editable User Profile">
72
+ <div class="max-w-sm border border-line rounded-sm overflow-hidden">
73
+ <KeyValue
74
+ :fields="userFields"
75
+ :data="user"
76
+ @update="e => handleUpdate(user, e)"
77
+ />
78
+ </div>
79
+ </Variant>
80
+ <Variant title="Database Config">
81
+ <div class="max-w-sm border border-line rounded-sm overflow-hidden">
82
+ <KeyValue
83
+ :fields="configFields"
84
+ :data="config"
85
+ label-width="100px"
86
+ @update="e => handleUpdate(config, e)"
87
+ />
88
+ </div>
89
+ </Variant>
90
+ <Variant title="Read-only with Custom Slot">
91
+ <div class="max-w-sm border border-line rounded-sm overflow-hidden">
92
+ <KeyValue
93
+ :fields="serverFields"
94
+ :data="server"
95
+ label-width="80px"
96
+ >
97
+ <template #value-status="{ value }">
98
+ <Badge :variant="value === 'active' ? 'success' : 'error'">{{ value }}</Badge>
99
+ </template>
100
+ </KeyValue>
101
+ </div>
102
+ </Variant>
103
+ </Story>
104
+ </template>
@@ -0,0 +1,44 @@
1
+ <script setup>
2
+ import { ref } from "vue"
3
+ import LabeledTextarea from "../src/components/LabeledTextarea.vue"
4
+ import LabeledTextareaGroup from "../src/components/LabeledTextareaGroup.vue"
5
+
6
+ const system = ref("high-level instructions for the model...")
7
+ const prompt = ref("Based on the retrieved context, extract:\n- summary: 2-3 sentence overview\n- entities: all named entities (people, places, organizations)\n- sentiment: overall sentiment (positive, neutral, or negative)\n\nBe thorough but concise.")
8
+ const empty = ref("")
9
+ </script>
10
+
11
+ <template>
12
+ <Story title="LabeledTextarea">
13
+ <Variant title="Standalone">
14
+ <LabeledTextarea v-model="system" label="System Instructions" />
15
+ </Variant>
16
+ <Variant title="Standalone (placeholder)">
17
+ <LabeledTextarea v-model="empty" label="Notes" placeholder="Write something..." />
18
+ </Variant>
19
+ <Variant title="Grouped">
20
+ <LabeledTextareaGroup>
21
+ <LabeledTextarea v-model="system" label="System Instructions" auto-grow />
22
+ <LabeledTextarea v-model="prompt" label="Per-Iteration Prompt" auto-grow />
23
+ </LabeledTextareaGroup>
24
+ </Variant>
25
+ <Variant title="With Icons">
26
+ <LabeledTextarea v-model="system" label="System Instructions" icon="material-symbols:settings" />
27
+ </Variant>
28
+ <Variant title="Grouped with Icons">
29
+ <LabeledTextareaGroup>
30
+ <LabeledTextarea v-model="system" label="System Instructions" icon="material-symbols:settings" auto-grow />
31
+ <LabeledTextarea v-model="prompt" label="Per-Iteration Prompt" icon="material-symbols:repeat" auto-grow />
32
+ </LabeledTextareaGroup>
33
+ </Variant>
34
+ <Variant title="Grouped with Footer">
35
+ <LabeledTextareaGroup>
36
+ <LabeledTextarea v-model="system" label="System Instructions" icon="material-symbols:settings" auto-grow />
37
+ <LabeledTextarea v-model="prompt" label="Per-Iteration Prompt" icon="material-symbols:repeat" auto-grow />
38
+ <template #footer>
39
+ <span class="text-xs text-success">bindings valid</span>
40
+ </template>
41
+ </LabeledTextareaGroup>
42
+ </Variant>
43
+ </Story>
44
+ </template>
@@ -0,0 +1,166 @@
1
+ <script setup>
2
+ import { ref } from "vue"
3
+ import Mention from "../src/components/Mention.vue"
4
+ import { useMention } from "../src/composables/useMention.js"
5
+
6
+ const text1 = ref("")
7
+ const text2 = ref("")
8
+ const text3 = ref("")
9
+ const lastAction = ref(null)
10
+
11
+ const users = [
12
+ { value: "alice", label: "Alice" },
13
+ { value: "bob", label: "Bob" },
14
+ { value: "carol", label: "Carol" },
15
+ { value: "dave", label: "Dave" },
16
+ { value: "eve", label: "Eve" },
17
+ { value: "frank", label: "Frank" },
18
+ { value: "grace", label: "Grace" },
19
+ { value: "heidi", label: "Heidi" },
20
+ { value: "ivan", label: "Ivan" },
21
+ { value: "judy", label: "Judy" },
22
+ { value: "karl", label: "Karl" },
23
+ { value: "liam", label: "Liam" },
24
+ { value: "mallory", label: "Mallory" },
25
+ { value: "nina", label: "Nina" },
26
+ { value: "oscar", label: "Oscar" },
27
+ { value: "peggy", label: "Peggy" },
28
+ { value: "quinn", label: "Quinn" },
29
+ { value: "rupert", label: "Rupert" },
30
+ { value: "sybil", label: "Sybil" },
31
+ { value: "trent", label: "Trent" }
32
+ ]
33
+
34
+ const commands = [
35
+ { value: "help", label: "help" },
36
+ { value: "clear", label: "clear" },
37
+ { value: "export", label: "export" },
38
+ { value: "import", label: "import" },
39
+ { value: "settings", label: "settings" }
40
+ ]
41
+
42
+ const actionCommands = [
43
+ { value: "bold", label: "bold", action: (item) => lastAction.value = item.label },
44
+ { value: "italic", label: "italic", action: (item) => lastAction.value = item.label },
45
+ { value: "code", label: "code", action: (item) => lastAction.value = item.label },
46
+ { value: "link", label: "link", action: (item) => lastAction.value = item.label },
47
+ { value: "image", label: "image", action: (item) => lastAction.value = item.label }
48
+ ]
49
+
50
+ const mention1 = useMention([
51
+ { key: "@", items: users }
52
+ ])
53
+
54
+ const mention2 = useMention([
55
+ {
56
+ key: "@",
57
+ onReveal: () => new Promise(r => setTimeout(() => r(users), 500))
58
+ },
59
+ { key: "/", items: commands }
60
+ ])
61
+
62
+ const mention3 = useMention([
63
+ { key: "@", items: users },
64
+ { key: "/", action: true, items: actionCommands }
65
+ ])
66
+ </script>
67
+
68
+ <template>
69
+ <Story title="Mention">
70
+ <Variant title="Static @mentions">
71
+ <div class="w-[400px]">
72
+ <textarea
73
+ v-model="text1"
74
+ class="w-full h-24 px-2 py-1 bg-0 border border-line rounded-sm font-mono text-base text-fg-0 outline-none focus:border-accent resize-none"
75
+ placeholder="type @ to mention a user..."
76
+ v-on="mention1.handlers"
77
+ />
78
+ <Mention
79
+ :open="mention1.open.value"
80
+ :items="mention1.filtered.value"
81
+ :active-index="mention1.activeIndex.value"
82
+ :loading="mention1.loading.value"
83
+ :anchor="mention1.virtualAnchor.value"
84
+ @select="mention1.confirm($event)"
85
+ @hover="mention1.setActiveIndex($event)"
86
+ />
87
+ </div>
88
+ <template #controls>
89
+ <div class="text-xs text-fg-2 p-2">
90
+ <div>value: {{ text1 || '(empty)' }}</div>
91
+ </div>
92
+ </template>
93
+ </Variant>
94
+
95
+ <Variant title="Multiple triggers (@ async + / static)">
96
+ <div class="w-[400px]">
97
+ <textarea
98
+ v-model="text2"
99
+ class="w-full h-24 px-2 py-1 bg-0 border border-line rounded-sm font-mono text-base text-fg-0 outline-none focus:border-accent resize-none"
100
+ placeholder="type @ for users (async), / for commands..."
101
+ v-on="mention2.handlers"
102
+ />
103
+ <Mention
104
+ :open="mention2.open.value"
105
+ :items="mention2.filtered.value"
106
+ :active-index="mention2.activeIndex.value"
107
+ :loading="mention2.loading.value"
108
+ :anchor="mention2.virtualAnchor.value"
109
+ @select="mention2.confirm($event)"
110
+ @hover="mention2.setActiveIndex($event)"
111
+ />
112
+ </div>
113
+ <template #controls>
114
+ <div class="text-xs text-fg-2 p-2">
115
+ <div>value: {{ text2 || '(empty)' }}</div>
116
+ <div>trigger: {{ mention2.activeTrigger.value?.key ?? 'none' }}</div>
117
+ </div>
118
+ </template>
119
+ </Variant>
120
+
121
+ <Variant title="Action commands (/ strips text)">
122
+ <div class="w-[400px]">
123
+ <textarea
124
+ v-model="text3"
125
+ class="w-full h-24 px-2 py-1 bg-0 border border-line rounded-sm font-mono text-base text-fg-0 outline-none focus:border-accent resize-none"
126
+ placeholder="type @ to mention, / to run a command..."
127
+ v-on="mention3.handlers"
128
+ />
129
+ <Mention
130
+ :open="mention3.open.value"
131
+ :items="mention3.filtered.value"
132
+ :active-index="mention3.activeIndex.value"
133
+ :loading="mention3.loading.value"
134
+ :anchor="mention3.virtualAnchor.value"
135
+ @select="mention3.confirm($event)"
136
+ @hover="mention3.setActiveIndex($event)"
137
+ />
138
+ </div>
139
+ <template #controls>
140
+ <div class="text-xs text-fg-2 p-2">
141
+ <div>value: {{ text3 || '(empty)' }}</div>
142
+ <div>last action: {{ lastAction ?? 'none' }}</div>
143
+ </div>
144
+ </template>
145
+ </Variant>
146
+
147
+ <Variant title="Input at bottom (flip test)">
148
+ <div class="w-[400px] flex flex-col justify-end" style="height: calc(100vh - 100px)">
149
+ <textarea
150
+ class="w-full h-24 px-2 py-1 bg-0 border border-line rounded-sm font-mono text-base text-fg-0 outline-none focus:border-accent resize-none"
151
+ placeholder="type @ here to see panel flip upward..."
152
+ v-on="mention1.handlers"
153
+ />
154
+ <Mention
155
+ :open="mention1.open.value"
156
+ :items="mention1.filtered.value"
157
+ :active-index="mention1.activeIndex.value"
158
+ :loading="mention1.loading.value"
159
+ :anchor="mention1.virtualAnchor.value"
160
+ @select="mention1.confirm($event)"
161
+ @hover="mention1.setActiveIndex($event)"
162
+ />
163
+ </div>
164
+ </Variant>
165
+ </Story>
166
+ </template>