@flamingo-stack/openframe-frontend-core 0.0.186 → 0.0.188
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/dist/{chunk-FMWHOUFE.js → chunk-6XYXWVYY.js} +2 -2
- package/dist/{chunk-ALW3D72O.cjs → chunk-HIMEIPED.cjs} +9 -9
- package/dist/{chunk-ALW3D72O.cjs.map → chunk-HIMEIPED.cjs.map} +1 -1
- package/dist/{chunk-RZ3HHPQH.js → chunk-J2C2TI5Z.js} +2174 -2119
- package/dist/chunk-J2C2TI5Z.js.map +1 -0
- package/dist/{chunk-TMD5LDX4.cjs → chunk-VJTFBYVG.cjs} +57 -2
- package/dist/{chunk-TMD5LDX4.cjs.map → chunk-VJTFBYVG.cjs.map} +1 -1
- package/dist/{chunk-SAWVZ5LA.cjs → chunk-W5AWCFKE.cjs} +1641 -1537
- package/dist/chunk-W5AWCFKE.cjs.map +1 -0
- package/dist/{chunk-LI2C4ZCU.js → chunk-YOMHP4V3.js} +17410 -17306
- package/dist/chunk-YOMHP4V3.js.map +1 -0
- package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
- package/dist/components/chat/block-card.d.ts +39 -0
- package/dist/components/chat/block-card.d.ts.map +1 -0
- package/dist/components/chat/chat-message-enhanced.d.ts +2 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/chat-ref.types.d.ts +6 -0
- package/dist/components/chat/chat-ref.types.d.ts.map +1 -1
- package/dist/components/chat/chat-video-entity-card.d.ts +30 -0
- package/dist/components/chat/chat-video-entity-card.d.ts.map +1 -0
- package/dist/components/chat/context-compaction-display.d.ts.map +1 -1
- package/dist/components/chat/index.d.ts +2 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
- package/dist/components/chat/types/api.types.d.ts +2 -6
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +20 -0
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/network.types.d.ts +2 -0
- package/dist/components/chat/types/network.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +2 -6
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -1
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +2 -6
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts +2 -6
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/chat/utils/tool-call-helpers.d.ts +21 -2
- package/dist/components/chat/utils/tool-call-helpers.d.ts.map +1 -1
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/icons-v2-generated/index.cjs +4 -2
- package/dist/components/icons-v2-generated/index.cjs.map +1 -1
- package/dist/components/icons-v2-generated/index.d.ts +1 -0
- package/dist/components/icons-v2-generated/index.d.ts.map +1 -1
- package/dist/components/icons-v2-generated/index.js +3 -1
- package/dist/components/icons-v2-generated/loaders/dots-loader-icon.d.ts +8 -0
- package/dist/components/icons-v2-generated/loaders/dots-loader-icon.d.ts.map +1 -0
- package/dist/components/icons-v2-generated/loaders/index.d.ts +2 -0
- package/dist/components/icons-v2-generated/loaders/index.d.ts.map +1 -0
- package/dist/components/index.cjs +6 -4
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +7 -5
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/ui/index.cjs +6 -4
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +0 -1
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +7 -5
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +6 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -5
- package/package.json +1 -1
- package/src/components/chat/approval-batch-message.tsx +4 -5
- package/src/components/chat/block-card.tsx +44 -0
- package/src/components/chat/chat-message-enhanced.tsx +168 -62
- package/src/components/chat/chat-message-list.tsx +57 -0
- package/src/components/chat/chat-ref.types.ts +6 -0
- package/src/components/chat/chat-video-entity-card.tsx +66 -0
- package/src/components/chat/context-compaction-display.tsx +2 -3
- package/src/components/chat/index.ts +2 -0
- package/src/components/chat/thinking-display.tsx +2 -2
- package/src/components/chat/tool-execution-display.tsx +14 -11
- package/src/components/chat/types/api.types.ts +2 -2
- package/src/components/chat/types/message.types.ts +21 -0
- package/src/components/chat/types/network.types.ts +2 -0
- package/src/components/chat/types/processing.types.ts +2 -6
- package/src/components/chat/utils/chunk-parser.ts +2 -0
- package/src/components/chat/utils/extract-incomplete-message-state.ts +4 -2
- package/src/components/chat/utils/message-segment-accumulator.ts +11 -2
- package/src/components/chat/utils/process-historical-messages.ts +2 -0
- package/src/components/chat/utils/tool-call-helpers.ts +97 -14
- package/src/components/icons-v2/loaders/dots-loader.svg +1 -0
- package/src/components/icons-v2-generated/index.ts +1 -0
- package/src/components/icons-v2-generated/loaders/dots-loader-icon.tsx +53 -0
- package/src/components/icons-v2-generated/loaders/index.ts +1 -0
- package/src/components/ui/index.ts +0 -2
- package/src/stories/DotsLoaderIcon.stories.tsx +103 -0
- package/dist/chunk-LI2C4ZCU.js.map +0 -1
- package/dist/chunk-RZ3HHPQH.js.map +0 -1
- package/dist/chunk-SAWVZ5LA.cjs.map +0 -1
- package/dist/components/ui/pulse-dots.d.ts +0 -7
- package/dist/components/ui/pulse-dots.d.ts.map +0 -1
- package/src/components/ui/pulse-dots.tsx +0 -56
- /package/dist/{chunk-FMWHOUFE.js.map → chunk-6XYXWVYY.js.map} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Contains types for message parsing, accumulation, and processing
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { MessageSegment, ProcessedMessage, ToolExecutionSegment, TokenUsageData, PendingToolCallData } from './message.types'
|
|
6
|
+
import type { MessageSegment, ProcessedMessage, ToolExecutionSegment, TokenUsageData, PendingToolCallData, ExecutingToolState } from './message.types'
|
|
7
7
|
import type { ChatApprovalStatus, AssistantType } from './chat.types'
|
|
8
8
|
import type { ChunkData, NatsMessageType } from './network.types'
|
|
9
9
|
|
|
@@ -39,11 +39,7 @@ export interface PendingApproval {
|
|
|
39
39
|
export interface AccumulatorState {
|
|
40
40
|
segments: MessageSegment[]
|
|
41
41
|
pendingApprovals: Map<string, PendingApproval>
|
|
42
|
-
executingTools: Map<string,
|
|
43
|
-
integratedToolType: string
|
|
44
|
-
toolFunction: string
|
|
45
|
-
parameters?: Record<string, any>
|
|
46
|
-
}>
|
|
42
|
+
executingTools: Map<string, ExecutingToolState>
|
|
47
43
|
escalatedApprovals?: Map<
|
|
48
44
|
string,
|
|
49
45
|
{ command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
|
|
@@ -74,6 +74,7 @@ export function parseChunkToAction(chunk: unknown): ParsedChunkAction | null {
|
|
|
74
74
|
type: 'EXECUTING_TOOL',
|
|
75
75
|
integratedToolType: data.integratedToolType || '',
|
|
76
76
|
toolFunction: data.toolFunction || '',
|
|
77
|
+
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
77
78
|
parameters: data.parameters,
|
|
78
79
|
toolExecutionRequestId: typeof data.toolExecutionRequestId === 'string' ? data.toolExecutionRequestId : undefined,
|
|
79
80
|
}
|
|
@@ -89,6 +90,7 @@ export function parseChunkToAction(chunk: unknown): ParsedChunkAction | null {
|
|
|
89
90
|
type: 'EXECUTED_TOOL',
|
|
90
91
|
integratedToolType: data.integratedToolType || '',
|
|
91
92
|
toolFunction: data.toolFunction || '',
|
|
93
|
+
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
92
94
|
parameters: data.parameters,
|
|
93
95
|
result: data.result,
|
|
94
96
|
success: data.success,
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
ProcessedMessage,
|
|
8
8
|
MessageSegment,
|
|
9
9
|
PendingApproval,
|
|
10
|
+
ExecutingToolState,
|
|
10
11
|
} from '../types'
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -18,7 +19,7 @@ export function extractIncompleteMessageState(
|
|
|
18
19
|
): {
|
|
19
20
|
existingSegments?: MessageSegment[]
|
|
20
21
|
pendingApprovals?: Map<string, PendingApproval>
|
|
21
|
-
executingTools?: Map<string,
|
|
22
|
+
executingTools?: Map<string, ExecutingToolState>
|
|
22
23
|
} | undefined {
|
|
23
24
|
if (!lastMessage || lastMessage.role !== 'assistant' || typeof lastMessage.content === 'string') {
|
|
24
25
|
return undefined
|
|
@@ -26,7 +27,7 @@ export function extractIncompleteMessageState(
|
|
|
26
27
|
|
|
27
28
|
const segments = lastMessage.content as MessageSegment[]
|
|
28
29
|
const pendingApprovals = new Map<string, PendingApproval>()
|
|
29
|
-
const executingTools = new Map<string,
|
|
30
|
+
const executingTools = new Map<string, ExecutingToolState>()
|
|
30
31
|
let hasIncompleteState = false
|
|
31
32
|
|
|
32
33
|
segments.forEach(segment => {
|
|
@@ -39,6 +40,7 @@ export function extractIncompleteMessageState(
|
|
|
39
40
|
executingTools.set(toolKey, {
|
|
40
41
|
integratedToolType: segment.data.integratedToolType,
|
|
41
42
|
toolFunction: segment.data.toolFunction,
|
|
43
|
+
toolTitle: segment.data.toolTitle,
|
|
42
44
|
parameters: segment.data.parameters,
|
|
43
45
|
})
|
|
44
46
|
hasIncompleteState = true
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
PendingToolCallData,
|
|
21
21
|
AccumulatorState,
|
|
22
22
|
ChatApprovalStatus,
|
|
23
|
+
ExecutingToolState,
|
|
23
24
|
} from '../types'
|
|
24
25
|
|
|
25
26
|
export interface AccumulatorCallbacks {
|
|
@@ -34,7 +35,7 @@ export interface AccumulatorCallbacks {
|
|
|
34
35
|
export class MessageSegmentAccumulator {
|
|
35
36
|
private segments: MessageSegment[] = []
|
|
36
37
|
private pendingApprovals: Map<string, PendingApproval> = new Map()
|
|
37
|
-
private executingTools: Map<string,
|
|
38
|
+
private executingTools: Map<string, ExecutingToolState> = new Map()
|
|
38
39
|
private callbacks: AccumulatorCallbacks = {}
|
|
39
40
|
|
|
40
41
|
constructor(callbacks?: AccumulatorCallbacks) {
|
|
@@ -57,7 +58,7 @@ export class MessageSegmentAccumulator {
|
|
|
57
58
|
initializeWithState(state: {
|
|
58
59
|
existingSegments?: MessageSegment[]
|
|
59
60
|
pendingApprovals?: Map<string, PendingApproval>
|
|
60
|
-
executingTools?: Map<string,
|
|
61
|
+
executingTools?: Map<string, ExecutingToolState>
|
|
61
62
|
}): void {
|
|
62
63
|
if (state.existingSegments) {
|
|
63
64
|
this.segments = [...state.existingSegments]
|
|
@@ -164,6 +165,7 @@ export class MessageSegmentAccumulator {
|
|
|
164
165
|
this.executingTools.set(toolKey, {
|
|
165
166
|
integratedToolType: toolData.integratedToolType,
|
|
166
167
|
toolFunction: toolData.toolFunction,
|
|
168
|
+
toolTitle: toolData.toolTitle,
|
|
167
169
|
parameters: toolData.parameters,
|
|
168
170
|
})
|
|
169
171
|
this.segments.push(segment)
|
|
@@ -179,10 +181,17 @@ export class MessageSegmentAccumulator {
|
|
|
179
181
|
)
|
|
180
182
|
|
|
181
183
|
const executingTool = this.executingTools.get(toolKey)
|
|
184
|
+
// The backend omits `toolTitle` on EXECUTED_TOOL; restore it from the
|
|
185
|
+
// paired EXECUTING segment (or its tracked state) so the completed
|
|
186
|
+
// segment keeps the human-readable title instead of falling back to the
|
|
187
|
+
// raw `toolFunction`.
|
|
188
|
+
const existingExecuting =
|
|
189
|
+
existingIndex !== -1 ? (this.segments[existingIndex] as ToolExecutionSegment) : undefined
|
|
182
190
|
const mergedSegment: ToolExecutionSegment = {
|
|
183
191
|
type: 'tool_execution',
|
|
184
192
|
data: {
|
|
185
193
|
...toolData,
|
|
194
|
+
toolTitle: toolData.toolTitle ?? existingExecuting?.data.toolTitle ?? executingTool?.toolTitle,
|
|
186
195
|
parameters: toolData.parameters || executingTool?.parameters,
|
|
187
196
|
}
|
|
188
197
|
}
|
|
@@ -231,6 +231,7 @@ function processMessageData(
|
|
|
231
231
|
type: 'EXECUTING_TOOL',
|
|
232
232
|
integratedToolType: data.integratedToolType || '',
|
|
233
233
|
toolFunction: data.toolFunction || '',
|
|
234
|
+
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
234
235
|
parameters: data.parameters,
|
|
235
236
|
toolExecutionRequestId: data.toolExecutionRequestId,
|
|
236
237
|
},
|
|
@@ -246,6 +247,7 @@ function processMessageData(
|
|
|
246
247
|
type: 'EXECUTED_TOOL',
|
|
247
248
|
integratedToolType: data.integratedToolType || '',
|
|
248
249
|
toolFunction: data.toolFunction || '',
|
|
250
|
+
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
249
251
|
parameters: data.parameters,
|
|
250
252
|
result: data.result,
|
|
251
253
|
success: data.success,
|
|
@@ -8,19 +8,38 @@ import type { PendingToolCallData } from '../types'
|
|
|
8
8
|
export const COMMAND_BODY_ARG_KEYS = ['command', 'query', 'script', 'scriptContent', 'code'] as const
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* Order: command/query/script/scriptContent/code arg →
|
|
11
|
+
* Resolve the human-readable title/preview line for a tool call.
|
|
12
|
+
* Order: command/query/script/scriptContent/code arg → title → name → fallback.
|
|
13
|
+
*
|
|
14
|
+
* Shape-agnostic so both the approval card (`PendingToolCallData`:
|
|
15
|
+
* `toolCallArguments`/`toolName`) and the execution card (`ToolExecutionData`:
|
|
16
|
+
* `parameters`/`toolFunction`) can share one source of truth.
|
|
13
17
|
*/
|
|
14
|
-
export function
|
|
15
|
-
|
|
18
|
+
export function getToolCallTitle(opts: {
|
|
19
|
+
args?: Record<string, unknown> | null
|
|
20
|
+
title?: string | null
|
|
21
|
+
name?: string | null
|
|
22
|
+
}): string {
|
|
23
|
+
const { args, title, name } = opts
|
|
16
24
|
if (args && typeof args === 'object') {
|
|
17
|
-
const a = args as Record<string, unknown>
|
|
18
25
|
for (const key of COMMAND_BODY_ARG_KEYS) {
|
|
19
|
-
const candidate =
|
|
26
|
+
const candidate = args[key]
|
|
20
27
|
if (typeof candidate === 'string' && candidate.trim()) return candidate
|
|
21
28
|
}
|
|
22
29
|
}
|
|
23
|
-
return
|
|
30
|
+
return title || name || 'Tool call'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Extract a human-readable command string from a batch tool call.
|
|
35
|
+
* Thin adapter over {@link getToolCallTitle}.
|
|
36
|
+
*/
|
|
37
|
+
export function getCommandText(call: PendingToolCallData): string {
|
|
38
|
+
return getToolCallTitle({
|
|
39
|
+
args: call.toolCallArguments,
|
|
40
|
+
title: call.toolTitle,
|
|
41
|
+
name: call.toolName,
|
|
42
|
+
})
|
|
24
43
|
}
|
|
25
44
|
|
|
26
45
|
export type FormattedArgValue =
|
|
@@ -29,6 +48,74 @@ export type FormattedArgValue =
|
|
|
29
48
|
|
|
30
49
|
const INLINE_STRING_MAX = 80
|
|
31
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Pretty-print like `JSON.stringify(value, null, 2)`, but render multi-line
|
|
53
|
+
* string values across real lines instead of escaped `\n` sequences.
|
|
54
|
+
*
|
|
55
|
+
* Trade-off: the output is optimised for human reading, not for being
|
|
56
|
+
* re-parsed as JSON (multi-line string bodies lose their surrounding quotes
|
|
57
|
+
* and escaping). Single-line strings keep normal JSON quoting/escaping.
|
|
58
|
+
*
|
|
59
|
+
* `indent` is the nesting level of this value's content lines / closing
|
|
60
|
+
* bracket (matching the 2-space step of `JSON.stringify(_, null, 2)`).
|
|
61
|
+
*/
|
|
62
|
+
function expandJson(value: unknown, indent: number): string {
|
|
63
|
+
const pad = (n: number) => ' '.repeat(n)
|
|
64
|
+
|
|
65
|
+
if (value === null) return 'null'
|
|
66
|
+
const t = typeof value
|
|
67
|
+
if (t === 'number' || t === 'boolean' || t === 'bigint') return String(value)
|
|
68
|
+
|
|
69
|
+
if (t === 'string') {
|
|
70
|
+
const s = (value as string).replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
71
|
+
if (!s.includes('\n')) return JSON.stringify(s)
|
|
72
|
+
return s
|
|
73
|
+
.replace(/^\n+/, '')
|
|
74
|
+
.replace(/\n+$/, '')
|
|
75
|
+
.split('\n')
|
|
76
|
+
.map((line) => pad(indent) + line)
|
|
77
|
+
.join('\n')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
if (value.length === 0) return '[]'
|
|
82
|
+
const items = value.map((v) =>
|
|
83
|
+
typeof v === 'string' && /[\r\n]/.test(v)
|
|
84
|
+
? expandJson(v, indent + 1)
|
|
85
|
+
: pad(indent + 1) + expandJson(v, indent + 1),
|
|
86
|
+
)
|
|
87
|
+
return '[\n' + items.join(',\n') + '\n' + pad(indent) + ']'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (t === 'object') {
|
|
91
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
92
|
+
if (entries.length === 0) return '{}'
|
|
93
|
+
const items = entries.map(([k, v]) => {
|
|
94
|
+
const key = JSON.stringify(k)
|
|
95
|
+
if (typeof v === 'string' && /[\r\n]/.test(v)) {
|
|
96
|
+
return pad(indent + 1) + key + ':\n' + expandJson(v, indent + 2)
|
|
97
|
+
}
|
|
98
|
+
return pad(indent + 1) + key + ': ' + expandJson(v, indent + 1)
|
|
99
|
+
})
|
|
100
|
+
return '{\n' + items.join(',\n') + '\n' + pad(indent) + '}'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return JSON.stringify(value)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Entry point for {@link expandJson}: readable pretty-print where multi-line
|
|
108
|
+
* string fields (script bodies, command output, …) are shown with real line
|
|
109
|
+
* breaks instead of literal `\n`.
|
|
110
|
+
*/
|
|
111
|
+
export function expandedJsonStringify(value: unknown): string {
|
|
112
|
+
try {
|
|
113
|
+
return expandJson(value, 0)
|
|
114
|
+
} catch {
|
|
115
|
+
return String(value)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
32
119
|
/**
|
|
33
120
|
* Decide how to render a single `toolCallArguments` value:
|
|
34
121
|
* - objects/arrays → pretty-printed JSON block
|
|
@@ -50,7 +137,7 @@ export function formatToolArgValue(value: unknown): FormattedArgValue {
|
|
|
50
137
|
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
51
138
|
) {
|
|
52
139
|
try {
|
|
53
|
-
return { kind: 'block', text:
|
|
140
|
+
return { kind: 'block', text: expandedJsonStringify(JSON.parse(trimmed)), language: 'json' }
|
|
54
141
|
} catch {
|
|
55
142
|
// not valid JSON — fall through
|
|
56
143
|
}
|
|
@@ -65,11 +152,7 @@ export function formatToolArgValue(value: unknown): FormattedArgValue {
|
|
|
65
152
|
}
|
|
66
153
|
|
|
67
154
|
function safeStringify(value: unknown): string {
|
|
68
|
-
|
|
69
|
-
return JSON.stringify(value, null, 2)
|
|
70
|
-
} catch {
|
|
71
|
-
return String(value)
|
|
72
|
-
}
|
|
155
|
+
return expandedJsonStringify(value)
|
|
73
156
|
}
|
|
74
157
|
|
|
75
158
|
/**
|
|
@@ -92,7 +175,7 @@ export function formatToolResult(value: string | undefined | null): FormattedArg
|
|
|
92
175
|
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
93
176
|
) {
|
|
94
177
|
try {
|
|
95
|
-
return { kind: 'block', text:
|
|
178
|
+
return { kind: 'block', text: expandedJsonStringify(JSON.parse(trimmed)), language: 'json' }
|
|
96
179
|
} catch {
|
|
97
180
|
// fall through
|
|
98
181
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="#888" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="12" r="3"><animate attributeName="r" dur="0.75s" values="3;.2;3" repeatCount="indefinite" begin="0s"/></circle><circle cx="12" cy="12" r="3"><animate attributeName="r" dur="0.75s" values="3;.2;3" repeatCount="indefinite" begin="0.15s"/></circle><circle cx="20" cy="12" r="3"><animate attributeName="r" dur="0.75s" values="3;.2;3" repeatCount="indefinite" begin="0.3s"/></circle></svg>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { SVGProps } from "react";
|
|
2
|
+
export interface DotsLoaderIconProps
|
|
3
|
+
extends Omit<SVGProps<SVGSVGElement>, "width" | "height"> {
|
|
4
|
+
className?: string;
|
|
5
|
+
size?: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
}
|
|
8
|
+
export function DotsLoaderIcon({
|
|
9
|
+
className = "",
|
|
10
|
+
size = 24,
|
|
11
|
+
color = "currentColor",
|
|
12
|
+
...props
|
|
13
|
+
}: DotsLoaderIconProps) {
|
|
14
|
+
return (
|
|
15
|
+
<svg
|
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
fill={color}
|
|
18
|
+
viewBox="0 0 24 24"
|
|
19
|
+
width={size}
|
|
20
|
+
height={size}
|
|
21
|
+
className={className}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
<circle cx={4} cy={12} r={3}>
|
|
25
|
+
<animate
|
|
26
|
+
attributeName="r"
|
|
27
|
+
dur="0.75s"
|
|
28
|
+
values="3;.2;3"
|
|
29
|
+
repeatCount="indefinite"
|
|
30
|
+
begin="0s"
|
|
31
|
+
/>
|
|
32
|
+
</circle>
|
|
33
|
+
<circle cx={12} cy={12} r={3}>
|
|
34
|
+
<animate
|
|
35
|
+
attributeName="r"
|
|
36
|
+
dur="0.75s"
|
|
37
|
+
values="3;.2;3"
|
|
38
|
+
repeatCount="indefinite"
|
|
39
|
+
begin="0.15s"
|
|
40
|
+
/>
|
|
41
|
+
</circle>
|
|
42
|
+
<circle cx={20} cy={12} r={3}>
|
|
43
|
+
<animate
|
|
44
|
+
attributeName="r"
|
|
45
|
+
dur="0.75s"
|
|
46
|
+
values="3;.2;3"
|
|
47
|
+
repeatCount="indefinite"
|
|
48
|
+
begin="0.3s"
|
|
49
|
+
/>
|
|
50
|
+
</circle>
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DotsLoaderIcon } from './dots-loader-icon';
|
|
@@ -44,8 +44,6 @@ export * from './menubar'
|
|
|
44
44
|
export * from './navigation-menu'
|
|
45
45
|
export * from './tab-content'
|
|
46
46
|
export * from './tab-navigation'
|
|
47
|
-
// Animation components
|
|
48
|
-
export * from './pulse-dots'
|
|
49
47
|
// Feedback components
|
|
50
48
|
export * from './alert'
|
|
51
49
|
export * from './badge'
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
|
|
2
|
+
import { DotsLoaderIcon } from '../components/icons-v2-generated/loaders/dots-loader-icon';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'UI/DotsLoaderIcon',
|
|
6
|
+
component: DotsLoaderIcon,
|
|
7
|
+
argTypes: {
|
|
8
|
+
size: {
|
|
9
|
+
control: { type: 'number', min: 12, max: 128, step: 4 },
|
|
10
|
+
},
|
|
11
|
+
color: {
|
|
12
|
+
control: 'text',
|
|
13
|
+
description: 'CSS color. Defaults to `currentColor` — pass an ODS token like `var(--ods-accent)`.',
|
|
14
|
+
},
|
|
15
|
+
className: { control: 'text' },
|
|
16
|
+
},
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: 'centered',
|
|
19
|
+
},
|
|
20
|
+
} satisfies Meta<typeof DotsLoaderIcon>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default — 24px, inherits text color via `currentColor`.
|
|
27
|
+
* The three dots animate in an infinite loop (SMIL chain).
|
|
28
|
+
*/
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
size: 24,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Large size for full-page / blocking loading states.
|
|
37
|
+
*/
|
|
38
|
+
export const Large: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
size: 64,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Inherits the surrounding text color through `currentColor`.
|
|
46
|
+
*/
|
|
47
|
+
export const InheritsTextColor: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
size: 40,
|
|
50
|
+
},
|
|
51
|
+
render: (args) => (
|
|
52
|
+
<div className="text-ods-accent">
|
|
53
|
+
<DotsLoaderIcon {...args} />
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Explicit ODS color tokens — never hardcode hex.
|
|
60
|
+
*/
|
|
61
|
+
export const OdsColors: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
size: 40,
|
|
64
|
+
},
|
|
65
|
+
render: (args) => (
|
|
66
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '2rem' }}>
|
|
67
|
+
<DotsLoaderIcon {...args} color="var(--ods-text-primary)" />
|
|
68
|
+
<DotsLoaderIcon {...args} color="var(--ods-text-secondary)" />
|
|
69
|
+
<DotsLoaderIcon {...args} color="var(--ods-accent)" />
|
|
70
|
+
<DotsLoaderIcon {...args} color="var(--ods-error)" />
|
|
71
|
+
</div>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Size scale.
|
|
77
|
+
*/
|
|
78
|
+
export const Sizes: Story = {
|
|
79
|
+
args: {},
|
|
80
|
+
render: () => (
|
|
81
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '2rem' }}>
|
|
82
|
+
<DotsLoaderIcon size={16} />
|
|
83
|
+
<DotsLoaderIcon size={24} />
|
|
84
|
+
<DotsLoaderIcon size={40} />
|
|
85
|
+
<DotsLoaderIcon size={64} />
|
|
86
|
+
<DotsLoaderIcon size={96} />
|
|
87
|
+
</div>
|
|
88
|
+
),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* In context — centered inside a card, as used for inline loading.
|
|
93
|
+
*/
|
|
94
|
+
export const InCard: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
size: 32,
|
|
97
|
+
},
|
|
98
|
+
render: (args) => (
|
|
99
|
+
<div className="flex items-center justify-center bg-ods-card border border-ods-border rounded-lg w-64 h-40 text-ods-text-secondary">
|
|
100
|
+
<DotsLoaderIcon {...args} />
|
|
101
|
+
</div>
|
|
102
|
+
),
|
|
103
|
+
};
|