@solo-io-public/ui-components 0.0.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 +201 -0
- package/README.md +167 -0
- package/dist/Button.style-CCplEN2o.js +474 -0
- package/dist/Button.style-CCplEN2o.js.map +1 -0
- package/dist/Button.style-CI6Xbod7.cjs +10 -0
- package/dist/Button.style-CI6Xbod7.cjs.map +1 -0
- package/dist/_internal/Svg.d.ts +15 -0
- package/dist/_internal/colors.d.ts +17 -0
- package/dist/_internal/gradients.d.ts +11 -0
- package/dist/_internal/hexAddAlpha.d.ts +3 -0
- package/dist/_internal/palette.d.ts +30 -0
- package/dist/_internal/sizing.d.ts +14 -0
- package/dist/_internal/unstyledButton.d.ts +8 -0
- package/dist/_internal/utils.d.ts +7 -0
- package/dist/components/Alert/Alert.d.ts +17 -0
- package/dist/components/Alert/index.d.ts +1 -0
- package/dist/components/Button/Button.d.ts +32 -0
- package/dist/components/Button/Button.style.d.ts +48 -0
- package/dist/components/Button/index.d.ts +1 -0
- package/dist/components/CloseButton/CloseButton.d.ts +14 -0
- package/dist/components/CloseButton/index.d.ts +1 -0
- package/dist/components/Layout/FlexLayout.d.ts +83 -0
- package/dist/components/Layout/Spacer.d.ts +80 -0
- package/dist/components/Layout/index.d.ts +2 -0
- package/dist/components/MonacoEditor/EditorSettingsContext.d.ts +15 -0
- package/dist/components/MonacoEditor/MonacoEditorWithSettings.d.ts +20 -0
- package/dist/components/MonacoEditor/index.d.ts +2 -0
- package/dist/components/Text/Text.d.ts +71 -0
- package/dist/components/Text/index.d.ts +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/providers/SoloContextProvider.d.ts +62 -0
- package/dist/providers/SoloModeContext.d.ts +5 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/solo-components.cjs +56 -0
- package/dist/solo-components.cjs.map +1 -0
- package/dist/solo-components.js +664 -0
- package/dist/solo-components.js.map +1 -0
- package/dist/styles.cjs +2 -0
- package/dist/styles.cjs.map +1 -0
- package/dist/styles.d.ts +1 -0
- package/dist/styles.js +5 -0
- package/dist/styles.js.map +1 -0
- package/package.json +109 -0
- package/src/_internal/Svg.tsx +58 -0
- package/src/_internal/colors.ts +26 -0
- package/src/_internal/gradients.ts +36 -0
- package/src/_internal/hexAddAlpha.ts +9 -0
- package/src/_internal/palette.ts +46 -0
- package/src/_internal/sizing.ts +15 -0
- package/src/_internal/unstyledButton.ts +17 -0
- package/src/_internal/utils.ts +11 -0
- package/src/components/Alert/Alert.stories.tsx +44 -0
- package/src/components/Alert/Alert.tsx +168 -0
- package/src/components/Alert/index.ts +1 -0
- package/src/components/Button/Button.stories.tsx +62 -0
- package/src/components/Button/Button.style.ts +158 -0
- package/src/components/Button/Button.test.tsx +47 -0
- package/src/components/Button/Button.tsx +84 -0
- package/src/components/Button/index.ts +11 -0
- package/src/components/CloseButton/CloseButton.tsx +76 -0
- package/src/components/CloseButton/index.ts +1 -0
- package/src/components/Layout/FlexLayout.tsx +101 -0
- package/src/components/Layout/Spacer.tsx +131 -0
- package/src/components/Layout/index.ts +2 -0
- package/src/components/MonacoEditor/EditorSettingsContext.test.tsx +55 -0
- package/src/components/MonacoEditor/EditorSettingsContext.tsx +63 -0
- package/src/components/MonacoEditor/MonacoEditor.stories.tsx +197 -0
- package/src/components/MonacoEditor/MonacoEditorWithSettings.tsx +376 -0
- package/src/components/MonacoEditor/index.ts +9 -0
- package/src/components/Text/Text.stories.tsx +63 -0
- package/src/components/Text/Text.tsx +65 -0
- package/src/components/Text/index.ts +1 -0
- package/src/index.ts +7 -0
- package/src/providers/SoloContextProvider.tsx +213 -0
- package/src/providers/SoloModeContext.tsx +13 -0
- package/src/providers/index.ts +10 -0
- package/src/styles.ts +5 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import type { SerializedStyles } from '@emotion/react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { Editor, type OnMount } from '@monaco-editor/react';
|
|
4
|
+
import { Dropdown, Tooltip, type MenuProps } from 'antd';
|
|
5
|
+
import { Copy, Settings } from 'lucide-react';
|
|
6
|
+
import type * as monacoEditor from 'monaco-editor';
|
|
7
|
+
import { initVimMode, type VimMode, VimMode as VimModeClass } from 'monaco-vim';
|
|
8
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
9
|
+
import toast from 'react-hot-toast';
|
|
10
|
+
import { useEditorSettings } from './EditorSettingsContext';
|
|
11
|
+
import { useSoloMode, type SoloMode } from '../../providers/SoloModeContext';
|
|
12
|
+
|
|
13
|
+
// region Styles
|
|
14
|
+
|
|
15
|
+
// The component reads its palette from CSS custom properties so consumers can
|
|
16
|
+
// theme it, but ships dark-theme fallbacks so it looks right with no theming.
|
|
17
|
+
const EditorContainer = styled.div<{ height: string; mode: SoloMode; styleOverrides?: SerializedStyles }>`
|
|
18
|
+
/* Include the 1px border in the 100% width/height so the container never spills
|
|
19
|
+
past a tight parent (e.g. an antd Drawer body), which would show scrollbars. */
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
position: relative;
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: ${props => props.height};
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
background: transparent;
|
|
27
|
+
border-radius: 6px;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
/* Light gets a clearer border + a soft card shadow so the editor reads as an
|
|
30
|
+
elevated surface on a white page; dark keeps a subtle themed frame. */
|
|
31
|
+
${props =>
|
|
32
|
+
props.mode === 'light'
|
|
33
|
+
? `
|
|
34
|
+
border: 1px solid #cdd1d8;
|
|
35
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 6px 16px rgba(16, 24, 40, 0.06);
|
|
36
|
+
`
|
|
37
|
+
: `border: 1px solid var(--color-border-base, #3f3f46);`}
|
|
38
|
+
${props => props.styleOverrides}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const EditorWrapper = styled.div`
|
|
42
|
+
flex: 1;
|
|
43
|
+
position: relative;
|
|
44
|
+
min-height: 0;
|
|
45
|
+
background: transparent;
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
// Top-right control bar holding the (read-only) copy button and the settings gear.
|
|
49
|
+
// Shifts left by `offsetRight` so it clears the editor's vertical scrollbar.
|
|
50
|
+
const ControlsBar = styled.div<{ offsetRight: number }>`
|
|
51
|
+
position: absolute;
|
|
52
|
+
top: 8px;
|
|
53
|
+
right: ${props => props.offsetRight}px;
|
|
54
|
+
z-index: 10;
|
|
55
|
+
display: flex;
|
|
56
|
+
gap: 6px;
|
|
57
|
+
transition: right 0.15s ease;
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const IconActionButton = styled.button`
|
|
61
|
+
background: var(--color-bg-elevated, #1e1e22);
|
|
62
|
+
border: 1px solid var(--color-border-base, #3f3f46);
|
|
63
|
+
border-radius: 3px;
|
|
64
|
+
padding: 6px;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: center;
|
|
69
|
+
transition: all 0.08s ease;
|
|
70
|
+
|
|
71
|
+
&:hover {
|
|
72
|
+
background: var(--color-bg-hover, #282035);
|
|
73
|
+
border-color: var(--color-primary, #8134e2);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Active is a clearly distinct step from hover (deeper fill + press shift). */
|
|
77
|
+
&:active {
|
|
78
|
+
background: var(--color-bg-active, #352b48);
|
|
79
|
+
border-color: var(--color-primary, #8134e2);
|
|
80
|
+
transform: translateY(1px);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&:focus-visible {
|
|
84
|
+
outline: 2px solid var(--color-primary, #8134e2);
|
|
85
|
+
outline-offset: 2px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
svg {
|
|
89
|
+
width: 16px;
|
|
90
|
+
height: 16px;
|
|
91
|
+
color: var(--color-text-primary, #fafafa);
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
const VimStatusBar = styled.div`
|
|
96
|
+
padding: 4px 8px;
|
|
97
|
+
background: var(--color-bg-elevated, #1e1e22);
|
|
98
|
+
border-top: 1px solid var(--color-border-base, #3f3f46);
|
|
99
|
+
font-family: monospace;
|
|
100
|
+
font-size: 12px;
|
|
101
|
+
min-height: 24px;
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
// region Component
|
|
105
|
+
|
|
106
|
+
export interface MonacoEditorWithSettingsProps {
|
|
107
|
+
value: string;
|
|
108
|
+
onChange?: (value: string | undefined) => void;
|
|
109
|
+
language: string;
|
|
110
|
+
height?: string;
|
|
111
|
+
/** Defaults to the active `SoloContextProvider` mode when omitted. */
|
|
112
|
+
theme?: 'light' | 'dark';
|
|
113
|
+
options?: monacoEditor.editor.IStandaloneEditorConstructionOptions;
|
|
114
|
+
onMount?: OnMount;
|
|
115
|
+
readOnly?: boolean;
|
|
116
|
+
downloadFileName?: string;
|
|
117
|
+
onSave?: () => void;
|
|
118
|
+
onQuit?: () => void;
|
|
119
|
+
/** Emotion styles appended to the outer container. */
|
|
120
|
+
styleOverrides?: SerializedStyles;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function MonacoEditorWithSettings({
|
|
124
|
+
value,
|
|
125
|
+
onChange,
|
|
126
|
+
language,
|
|
127
|
+
height = '100%',
|
|
128
|
+
theme,
|
|
129
|
+
options = {},
|
|
130
|
+
onMount,
|
|
131
|
+
readOnly = false,
|
|
132
|
+
downloadFileName,
|
|
133
|
+
onSave,
|
|
134
|
+
onQuit,
|
|
135
|
+
styleOverrides,
|
|
136
|
+
}: MonacoEditorWithSettingsProps) {
|
|
137
|
+
const { vimEnabled, toggleVimMode, wordWrapEnabled, toggleWordWrap } = useEditorSettings();
|
|
138
|
+
// Vim is suppressed in read-only mode, regardless of the user's saved setting.
|
|
139
|
+
const vimActive = vimEnabled && !readOnly;
|
|
140
|
+
// Fall back to the provider's light/dark mode when no explicit theme is given.
|
|
141
|
+
const soloMode = useSoloMode();
|
|
142
|
+
const resolvedTheme = theme ?? soloMode;
|
|
143
|
+
const editorRef = useRef<monacoEditor.editor.IStandaloneCodeEditor | null>(null);
|
|
144
|
+
const vimModeRef = useRef<VimMode | null>(null);
|
|
145
|
+
const statusNodeRef = useRef<HTMLDivElement | null>(null);
|
|
146
|
+
const onSaveRef = useRef(onSave);
|
|
147
|
+
const onQuitRef = useRef(onQuit);
|
|
148
|
+
const disposablesRef = useRef<monacoEditor.IDisposable[]>([]);
|
|
149
|
+
// Width of the vertical scrollbar when it's visible; the controls shift left by this.
|
|
150
|
+
const [scrollbarWidth, setScrollbarWidth] = useState(0);
|
|
151
|
+
|
|
152
|
+
// Keep onSave and onQuit refs up to date
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
onSaveRef.current = onSave;
|
|
155
|
+
}, [onSave]);
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
onQuitRef.current = onQuit;
|
|
159
|
+
}, [onQuit]);
|
|
160
|
+
|
|
161
|
+
// Enable/disable vim mode for this editor instance
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!editorRef.current) return;
|
|
164
|
+
|
|
165
|
+
if (vimActive) {
|
|
166
|
+
// Enable vim mode
|
|
167
|
+
if (!vimModeRef.current && statusNodeRef.current) {
|
|
168
|
+
vimModeRef.current = initVimMode(editorRef.current, statusNodeRef.current);
|
|
169
|
+
|
|
170
|
+
// Set up vim save and quit commands using VimMode.Vim
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
try {
|
|
173
|
+
const Vim = (VimModeClass as any).Vim;
|
|
174
|
+
if (Vim && Vim.defineEx) {
|
|
175
|
+
if (onSaveRef.current) {
|
|
176
|
+
Vim.defineEx('write', 'w', () => {
|
|
177
|
+
onSaveRef.current?.();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (onQuitRef.current) {
|
|
181
|
+
Vim.defineEx('quit', 'q', () => {
|
|
182
|
+
onQuitRef.current?.();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (onSaveRef.current && onQuitRef.current) {
|
|
186
|
+
Vim.defineEx('wq', 'wq', () => {
|
|
187
|
+
onSaveRef.current?.();
|
|
188
|
+
onQuitRef.current?.();
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
console.error('Failed to setup vim commands:', e);
|
|
194
|
+
}
|
|
195
|
+
}, 100);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
// Disable vim mode
|
|
199
|
+
if (vimModeRef.current) {
|
|
200
|
+
vimModeRef.current.dispose();
|
|
201
|
+
vimModeRef.current = null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}, [vimActive]);
|
|
205
|
+
|
|
206
|
+
const handleEditorMount: OnMount = useCallback(
|
|
207
|
+
(editor, monaco) => {
|
|
208
|
+
editorRef.current = editor;
|
|
209
|
+
|
|
210
|
+
// Track the vertical scrollbar so the top-right controls can clear it.
|
|
211
|
+
const syncScrollbar = () => {
|
|
212
|
+
const layout = editor.getLayoutInfo();
|
|
213
|
+
const hasVerticalScrollbar = editor.getScrollHeight() > layout.height;
|
|
214
|
+
setScrollbarWidth(hasVerticalScrollbar ? layout.verticalScrollbarWidth : 0);
|
|
215
|
+
};
|
|
216
|
+
syncScrollbar();
|
|
217
|
+
disposablesRef.current.push(
|
|
218
|
+
editor.onDidContentSizeChange(syncScrollbar),
|
|
219
|
+
editor.onDidLayoutChange(syncScrollbar)
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Add keyboard shortcut for save (Cmd+S / Ctrl+S)
|
|
223
|
+
if (onSaveRef.current) {
|
|
224
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
225
|
+
onSaveRef.current?.();
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Initialize vim mode if enabled (never in read-only mode)
|
|
230
|
+
if (vimActive && statusNodeRef.current && !vimModeRef.current) {
|
|
231
|
+
vimModeRef.current = initVimMode(editor, statusNodeRef.current);
|
|
232
|
+
|
|
233
|
+
// Set up vim save and quit commands using VimMode.Vim
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
try {
|
|
236
|
+
const Vim = (VimModeClass as any).Vim;
|
|
237
|
+
if (Vim && Vim.defineEx) {
|
|
238
|
+
if (onSaveRef.current) {
|
|
239
|
+
Vim.defineEx('write', 'w', () => {
|
|
240
|
+
onSaveRef.current?.();
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (onQuitRef.current) {
|
|
244
|
+
Vim.defineEx('quit', 'q', () => {
|
|
245
|
+
onQuitRef.current?.();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (onSaveRef.current && onQuitRef.current) {
|
|
249
|
+
Vim.defineEx('wq', 'wq', () => {
|
|
250
|
+
onSaveRef.current?.();
|
|
251
|
+
onQuitRef.current?.();
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.error('Failed to setup vim commands:', e);
|
|
257
|
+
}
|
|
258
|
+
}, 100);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Call custom onMount if provided
|
|
262
|
+
if (onMount) {
|
|
263
|
+
onMount(editor, monaco);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
[onMount, vimActive]
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Cleanup on unmount
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
return () => {
|
|
272
|
+
vimModeRef.current?.dispose();
|
|
273
|
+
disposablesRef.current.forEach(d => d.dispose());
|
|
274
|
+
};
|
|
275
|
+
}, []);
|
|
276
|
+
|
|
277
|
+
const handleCopy = useCallback(async () => {
|
|
278
|
+
// Read the live editor content, not the (possibly stale) `value` prop.
|
|
279
|
+
const text = editorRef.current?.getValue() ?? value;
|
|
280
|
+
try {
|
|
281
|
+
await navigator.clipboard.writeText(text);
|
|
282
|
+
toast.success('Copied to clipboard');
|
|
283
|
+
} catch {
|
|
284
|
+
toast.error('Failed to copy to clipboard');
|
|
285
|
+
}
|
|
286
|
+
}, [value]);
|
|
287
|
+
|
|
288
|
+
const handleDownload = useCallback(() => {
|
|
289
|
+
const text = editorRef.current?.getValue() ?? value;
|
|
290
|
+
try {
|
|
291
|
+
const blob = new Blob([text], { type: 'text/plain' });
|
|
292
|
+
const url = URL.createObjectURL(blob);
|
|
293
|
+
const a = document.createElement('a');
|
|
294
|
+
a.href = url;
|
|
295
|
+
|
|
296
|
+
// Determine file extension from language
|
|
297
|
+
let extension = 'txt';
|
|
298
|
+
if (language === 'json') extension = 'json';
|
|
299
|
+
else if (language === 'yaml' || language === 'yml') extension = 'yaml';
|
|
300
|
+
else if (language === 'javascript' || language === 'typescript')
|
|
301
|
+
extension = language === 'typescript' ? 'ts' : 'js';
|
|
302
|
+
else if (language === 'html') extension = 'html';
|
|
303
|
+
else if (language === 'css') extension = 'css';
|
|
304
|
+
|
|
305
|
+
a.download = downloadFileName || `content.${extension}`;
|
|
306
|
+
document.body.appendChild(a);
|
|
307
|
+
a.click();
|
|
308
|
+
document.body.removeChild(a);
|
|
309
|
+
URL.revokeObjectURL(url);
|
|
310
|
+
toast.success('Downloaded successfully');
|
|
311
|
+
} catch {
|
|
312
|
+
toast.error('Failed to download file');
|
|
313
|
+
}
|
|
314
|
+
}, [value, language, downloadFileName]);
|
|
315
|
+
|
|
316
|
+
const menuItems: MenuProps['items'] = [
|
|
317
|
+
// Copy lives in its own always-visible button (left of the gear), not the menu.
|
|
318
|
+
{
|
|
319
|
+
key: 'download',
|
|
320
|
+
label: 'Download',
|
|
321
|
+
onClick: handleDownload,
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
type: 'divider',
|
|
325
|
+
},
|
|
326
|
+
// Vim mode is meaningless when read-only, so omit the toggle there.
|
|
327
|
+
!readOnly && {
|
|
328
|
+
key: 'vim',
|
|
329
|
+
label: vimEnabled ? 'Disable Vim Mode' : 'Enable Vim Mode',
|
|
330
|
+
onClick: toggleVimMode,
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
key: 'wordwrap',
|
|
334
|
+
label: wordWrapEnabled ? 'Disable Word Wrap' : 'Enable Word Wrap',
|
|
335
|
+
onClick: toggleWordWrap,
|
|
336
|
+
},
|
|
337
|
+
].filter(Boolean) as MenuProps['items'];
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<EditorContainer height={height} mode={resolvedTheme} styleOverrides={styleOverrides}>
|
|
341
|
+
<EditorWrapper>
|
|
342
|
+
<ControlsBar offsetRight={8 + scrollbarWidth}>
|
|
343
|
+
<Tooltip title='Copy to clipboard'>
|
|
344
|
+
<IconActionButton type='button' aria-label='Copy to clipboard' onClick={handleCopy}>
|
|
345
|
+
<Copy />
|
|
346
|
+
</IconActionButton>
|
|
347
|
+
</Tooltip>
|
|
348
|
+
<Dropdown menu={{ items: menuItems }} placement='bottomRight' trigger={['click']}>
|
|
349
|
+
<Tooltip title='More options'>
|
|
350
|
+
<IconActionButton type='button' aria-label='Editor settings'>
|
|
351
|
+
<Settings />
|
|
352
|
+
</IconActionButton>
|
|
353
|
+
</Tooltip>
|
|
354
|
+
</Dropdown>
|
|
355
|
+
</ControlsBar>
|
|
356
|
+
<Editor
|
|
357
|
+
height='100%'
|
|
358
|
+
language={language}
|
|
359
|
+
value={value}
|
|
360
|
+
onChange={onChange}
|
|
361
|
+
theme={resolvedTheme === 'dark' ? 'vs-dark' : 'vs'}
|
|
362
|
+
onMount={handleEditorMount}
|
|
363
|
+
options={{
|
|
364
|
+
// Keep Monaco fitted to its container as it resizes (e.g. a drawer
|
|
365
|
+
// opening) so it never ends up shorter than its box.
|
|
366
|
+
automaticLayout: true,
|
|
367
|
+
...options,
|
|
368
|
+
readOnly,
|
|
369
|
+
wordWrap: wordWrapEnabled ? 'on' : 'off',
|
|
370
|
+
}}
|
|
371
|
+
/>
|
|
372
|
+
</EditorWrapper>
|
|
373
|
+
{vimActive && <VimStatusBar ref={statusNodeRef as any} className='monaco-vim-status' />}
|
|
374
|
+
</EditorContainer>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Text } from './Text';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Text> = {
|
|
5
|
+
title: 'Common / Text',
|
|
6
|
+
component: Text,
|
|
7
|
+
args: {
|
|
8
|
+
children: 'The quick brown fox jumps over the lazy dog',
|
|
9
|
+
size: '16px'
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof Text>;
|
|
15
|
+
|
|
16
|
+
// Default color follows the theme, so it flips with the light/dark toolbar.
|
|
17
|
+
export const Default: Story = {};
|
|
18
|
+
|
|
19
|
+
export const Sizes: Story = {
|
|
20
|
+
render: () => (
|
|
21
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
22
|
+
{['12px', '14px', '16px', '20px', '24px', '30px'].map(s => (
|
|
23
|
+
<Text key={s} size={s}>
|
|
24
|
+
{s} — The quick brown fox
|
|
25
|
+
</Text>
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Weights: Story = {
|
|
32
|
+
render: () => (
|
|
33
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
34
|
+
{[300, 400, 500, 600, 700].map(w => (
|
|
35
|
+
<Text key={w} weight={w} size='18px'>
|
|
36
|
+
{w} — The quick brown fox
|
|
37
|
+
</Text>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const Colors: Story = {
|
|
44
|
+
render: () => (
|
|
45
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
46
|
+
<Text size='18px'>Default — follows the theme's text color</Text>
|
|
47
|
+
<Text size='18px' color='var(--color-text-secondary, #a1a1aa)'>
|
|
48
|
+
Secondary — themed via CSS variable
|
|
49
|
+
</Text>
|
|
50
|
+
<Text size='18px' color='var(--color-primary, #6844ff)'>
|
|
51
|
+
Primary accent
|
|
52
|
+
</Text>
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const Truncate: Story = {
|
|
58
|
+
render: () => (
|
|
59
|
+
<Text truncate maxWidth='260px' size='16px'>
|
|
60
|
+
This is a very long single line of text that should be truncated with an ellipsis.
|
|
61
|
+
</Text>
|
|
62
|
+
)
|
|
63
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import type { CSSProperties } from 'react';
|
|
3
|
+
import { cssProp, dontForwardProps } from '../../_internal/utils';
|
|
4
|
+
import { Spacer, type SpacerProps } from '../Layout/Spacer';
|
|
5
|
+
|
|
6
|
+
// region Helpers
|
|
7
|
+
|
|
8
|
+
const truncated = `
|
|
9
|
+
white-space: nowrap;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
text-overflow: ellipsis;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
// region Component
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Typography primitive built on `Spacer` (so margin/padding/sizing props work
|
|
18
|
+
* too). Defaults to the theme's `--color-text-primary`, so text color follows
|
|
19
|
+
* the active light/dark mode unless `color` is set explicitly.
|
|
20
|
+
*/
|
|
21
|
+
export type TextProps = SpacerProps & {
|
|
22
|
+
size?: CSSProperties['fontSize'];
|
|
23
|
+
weight?: CSSProperties['fontWeight'];
|
|
24
|
+
lineHeight?: CSSProperties['lineHeight'];
|
|
25
|
+
color?: CSSProperties['color'];
|
|
26
|
+
textAlign?: CSSProperties['textAlign'];
|
|
27
|
+
whiteSpace?: CSSProperties['whiteSpace'];
|
|
28
|
+
truncate?: boolean;
|
|
29
|
+
wordBreak?: CSSProperties['wordBreak'];
|
|
30
|
+
textTransform?: CSSProperties['textTransform'];
|
|
31
|
+
fontStyle?: CSSProperties['fontStyle'];
|
|
32
|
+
inline?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Text = styled(
|
|
36
|
+
Spacer,
|
|
37
|
+
dontForwardProps(
|
|
38
|
+
'size',
|
|
39
|
+
'weight',
|
|
40
|
+
'lineHeight',
|
|
41
|
+
'color',
|
|
42
|
+
'textAlign',
|
|
43
|
+
'whiteSpace',
|
|
44
|
+
'truncate',
|
|
45
|
+
'wordBreak',
|
|
46
|
+
'textTransform',
|
|
47
|
+
'fontStyle',
|
|
48
|
+
'inline'
|
|
49
|
+
)
|
|
50
|
+
)<TextProps>(
|
|
51
|
+
({ size, weight, lineHeight, color, textAlign, whiteSpace, truncate, wordBreak, textTransform, fontStyle, inline, display }) => `
|
|
52
|
+
font-family: var(--solo-font-family, inherit);
|
|
53
|
+
color: ${color ?? 'var(--color-text-primary, #fafafa)'};
|
|
54
|
+
${cssProp('font-size', size)}
|
|
55
|
+
${cssProp('font-weight', weight)}
|
|
56
|
+
${cssProp('line-height', lineHeight)}
|
|
57
|
+
${cssProp('text-align', textAlign)}
|
|
58
|
+
${cssProp('white-space', whiteSpace)}
|
|
59
|
+
${cssProp('word-break', wordBreak)}
|
|
60
|
+
${cssProp('text-transform', textTransform)}
|
|
61
|
+
${cssProp('font-style', fontStyle)}
|
|
62
|
+
${cssProp('display', display ?? (inline ? 'inline' : 'block'))}
|
|
63
|
+
${truncate ? truncated : ''}
|
|
64
|
+
`
|
|
65
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Text, type TextProps } from './Text';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./components/Alert";
|
|
2
|
+
export * from "./components/Button";
|
|
3
|
+
export * from "./components/CloseButton";
|
|
4
|
+
export * from "./components/Layout";
|
|
5
|
+
export * from "./components/MonacoEditor";
|
|
6
|
+
export * from "./components/Text";
|
|
7
|
+
export * from "./providers";
|