@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.
Files changed (77) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +167 -0
  3. package/dist/Button.style-CCplEN2o.js +474 -0
  4. package/dist/Button.style-CCplEN2o.js.map +1 -0
  5. package/dist/Button.style-CI6Xbod7.cjs +10 -0
  6. package/dist/Button.style-CI6Xbod7.cjs.map +1 -0
  7. package/dist/_internal/Svg.d.ts +15 -0
  8. package/dist/_internal/colors.d.ts +17 -0
  9. package/dist/_internal/gradients.d.ts +11 -0
  10. package/dist/_internal/hexAddAlpha.d.ts +3 -0
  11. package/dist/_internal/palette.d.ts +30 -0
  12. package/dist/_internal/sizing.d.ts +14 -0
  13. package/dist/_internal/unstyledButton.d.ts +8 -0
  14. package/dist/_internal/utils.d.ts +7 -0
  15. package/dist/components/Alert/Alert.d.ts +17 -0
  16. package/dist/components/Alert/index.d.ts +1 -0
  17. package/dist/components/Button/Button.d.ts +32 -0
  18. package/dist/components/Button/Button.style.d.ts +48 -0
  19. package/dist/components/Button/index.d.ts +1 -0
  20. package/dist/components/CloseButton/CloseButton.d.ts +14 -0
  21. package/dist/components/CloseButton/index.d.ts +1 -0
  22. package/dist/components/Layout/FlexLayout.d.ts +83 -0
  23. package/dist/components/Layout/Spacer.d.ts +80 -0
  24. package/dist/components/Layout/index.d.ts +2 -0
  25. package/dist/components/MonacoEditor/EditorSettingsContext.d.ts +15 -0
  26. package/dist/components/MonacoEditor/MonacoEditorWithSettings.d.ts +20 -0
  27. package/dist/components/MonacoEditor/index.d.ts +2 -0
  28. package/dist/components/Text/Text.d.ts +71 -0
  29. package/dist/components/Text/index.d.ts +1 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/providers/SoloContextProvider.d.ts +62 -0
  32. package/dist/providers/SoloModeContext.d.ts +5 -0
  33. package/dist/providers/index.d.ts +2 -0
  34. package/dist/solo-components.cjs +56 -0
  35. package/dist/solo-components.cjs.map +1 -0
  36. package/dist/solo-components.js +664 -0
  37. package/dist/solo-components.js.map +1 -0
  38. package/dist/styles.cjs +2 -0
  39. package/dist/styles.cjs.map +1 -0
  40. package/dist/styles.d.ts +1 -0
  41. package/dist/styles.js +5 -0
  42. package/dist/styles.js.map +1 -0
  43. package/package.json +109 -0
  44. package/src/_internal/Svg.tsx +58 -0
  45. package/src/_internal/colors.ts +26 -0
  46. package/src/_internal/gradients.ts +36 -0
  47. package/src/_internal/hexAddAlpha.ts +9 -0
  48. package/src/_internal/palette.ts +46 -0
  49. package/src/_internal/sizing.ts +15 -0
  50. package/src/_internal/unstyledButton.ts +17 -0
  51. package/src/_internal/utils.ts +11 -0
  52. package/src/components/Alert/Alert.stories.tsx +44 -0
  53. package/src/components/Alert/Alert.tsx +168 -0
  54. package/src/components/Alert/index.ts +1 -0
  55. package/src/components/Button/Button.stories.tsx +62 -0
  56. package/src/components/Button/Button.style.ts +158 -0
  57. package/src/components/Button/Button.test.tsx +47 -0
  58. package/src/components/Button/Button.tsx +84 -0
  59. package/src/components/Button/index.ts +11 -0
  60. package/src/components/CloseButton/CloseButton.tsx +76 -0
  61. package/src/components/CloseButton/index.ts +1 -0
  62. package/src/components/Layout/FlexLayout.tsx +101 -0
  63. package/src/components/Layout/Spacer.tsx +131 -0
  64. package/src/components/Layout/index.ts +2 -0
  65. package/src/components/MonacoEditor/EditorSettingsContext.test.tsx +55 -0
  66. package/src/components/MonacoEditor/EditorSettingsContext.tsx +63 -0
  67. package/src/components/MonacoEditor/MonacoEditor.stories.tsx +197 -0
  68. package/src/components/MonacoEditor/MonacoEditorWithSettings.tsx +376 -0
  69. package/src/components/MonacoEditor/index.ts +9 -0
  70. package/src/components/Text/Text.stories.tsx +63 -0
  71. package/src/components/Text/Text.tsx +65 -0
  72. package/src/components/Text/index.ts +1 -0
  73. package/src/index.ts +7 -0
  74. package/src/providers/SoloContextProvider.tsx +213 -0
  75. package/src/providers/SoloModeContext.tsx +13 -0
  76. package/src/providers/index.ts +10 -0
  77. package/src/styles.ts +5 -0
@@ -0,0 +1,101 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import type { CSSProperties } from 'react';
4
+ import { cssProp, dontForwardProps } from '../../_internal/utils';
5
+ import { Spacer, type SpacerProps } from './Spacer';
6
+
7
+ // region Component
8
+
9
+ export type FlexLayoutProps = SpacerProps & {
10
+ horizontal?: boolean;
11
+ vertical?: boolean;
12
+ gap?: CSSProperties['gap'] | number;
13
+ display?: CSSProperties['display'];
14
+ flexWrap?: CSSProperties['flexWrap'];
15
+ justifyContent?: CSSProperties['justifyContent'];
16
+ alignItems?: CSSProperties['alignItems'];
17
+ alignContent?: CSSProperties['alignContent'];
18
+ flexDirection?: CSSProperties['flexDirection'];
19
+ overflow?: CSSProperties['overflow'];
20
+ column?: boolean;
21
+ row?: boolean;
22
+ nowrap?: boolean;
23
+ expandChildren?: boolean;
24
+ };
25
+
26
+ /**
27
+ * Quick horizontal, vertical, or centered (horizontal & vertical) flex layout.
28
+ * A numeric `gap` is treated as a 0.25em scale step (e.g. `gap={3}` → `0.75em`).
29
+ */
30
+ export const FlexLayout = styled(
31
+ Spacer,
32
+ dontForwardProps(
33
+ 'horizontal',
34
+ 'vertical',
35
+ 'gap',
36
+ 'display',
37
+ 'flexWrap',
38
+ 'justifyContent',
39
+ 'alignItems',
40
+ 'alignContent',
41
+ 'flexDirection',
42
+ 'overflow',
43
+ 'column',
44
+ 'row',
45
+ 'nowrap',
46
+ 'expandChildren'
47
+ )
48
+ )<FlexLayoutProps>(({
49
+ horizontal,
50
+ vertical,
51
+ gap,
52
+ display,
53
+ flexWrap,
54
+ nowrap,
55
+ justifyContent,
56
+ alignItems,
57
+ alignContent,
58
+ flexDirection,
59
+ column,
60
+ row,
61
+ maxWidth,
62
+ minWidth,
63
+ maxHeight,
64
+ expandChildren,
65
+ overflow
66
+ }) => {
67
+ if (typeof gap === 'number') gap = gap * 0.25 + 'em';
68
+ return css`
69
+ display: flex;
70
+ ${cssProp('justify-content', justifyContent ? justifyContent : horizontal ? 'center' : undefined)}
71
+ ${cssProp('align-items', alignItems ? alignItems : vertical ? 'center' : undefined)}
72
+ ${cssProp('align-content', alignContent ? alignContent : vertical ? 'center' : undefined)}
73
+ ${cssProp('flex-direction', flexDirection ? flexDirection : column ? 'column' : row ? 'row' : undefined)}
74
+ ${cssProp('gap', gap)}
75
+ ${cssProp('display', display)}
76
+ ${cssProp('flex-wrap', flexWrap ? flexWrap : nowrap ? 'nowrap' : 'wrap')}
77
+ ${cssProp('overflow', overflow)}
78
+ max-width: ${maxWidth ?? '100%'};
79
+ ${minWidth
80
+ ? css`
81
+ min-width: ${minWidth};
82
+ `
83
+ : ''}
84
+ max-height: ${maxHeight ?? '100%'};
85
+ ${expandChildren &&
86
+ css`
87
+ > * {
88
+ flex-grow: 1;
89
+ width: auto;
90
+ }
91
+ `}
92
+ `;
93
+ });
94
+
95
+ /** Adds a `flex-grow: 1` filler into a flex layout. */
96
+ export const FlexLayoutSpacer = styled.div<{ flexBasis?: CSSProperties['flexBasis'] }>(
97
+ ({ flexBasis }) => css`
98
+ flex-grow: 1;
99
+ ${cssProp('flex-basis', flexBasis)}
100
+ `
101
+ );
@@ -0,0 +1,131 @@
1
+ import type { SerializedStyles } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import type { ComponentProps, CSSProperties } from 'react';
4
+ import { cssProp, dontForwardProps } from '../../_internal/utils';
5
+
6
+ // region Helpers
7
+
8
+ type SizeType = number | string;
9
+
10
+ // region Component
11
+
12
+ /**
13
+ * Margin/padding/sizing primitive. Use a string for exact values
14
+ * (e.g. `mx='5px'`). Directional props (`mr`, `mb`, …) override the axis
15
+ * shorthands (`mx`, `my`).
16
+ */
17
+ export type SpacerProps = {
18
+ mx?: string;
19
+ my?: string;
20
+ mr?: string;
21
+ ml?: string;
22
+ mt?: string;
23
+ mb?: string;
24
+ margin?: string;
25
+ px?: string;
26
+ py?: string;
27
+ pr?: string;
28
+ pl?: string;
29
+ pt?: string;
30
+ pb?: string;
31
+ padding?: string;
32
+ display?: CSSProperties['display'];
33
+ position?: CSSProperties['position'];
34
+ minHeight?: SizeType;
35
+ minWidth?: CSSProperties['minWidth'];
36
+ height?: SizeType;
37
+ width?: SizeType;
38
+ maxHeight?: CSSProperties['maxHeight'];
39
+ maxWidth?: CSSProperties['maxWidth'];
40
+ flexGrow?: CSSProperties['flexGrow'];
41
+ flexBasis?: CSSProperties['flexBasis'];
42
+ flexShrink?: CSSProperties['flexShrink'];
43
+ overflowY?: CSSProperties['overflowY'];
44
+ overflowX?: CSSProperties['overflowX'];
45
+ textOverflow?: CSSProperties['textOverflow'];
46
+ overflow?: CSSProperties['overflow'];
47
+ zIndex?: CSSProperties['zIndex'];
48
+ stylingOverrides?: SerializedStyles;
49
+ fadeIn?: boolean;
50
+ } & ComponentProps<'div'>;
51
+
52
+ export const Spacer = styled(
53
+ 'div',
54
+ dontForwardProps(
55
+ 'mx',
56
+ 'my',
57
+ 'mr',
58
+ 'ml',
59
+ 'mt',
60
+ 'mb',
61
+ 'margin',
62
+ 'px',
63
+ 'py',
64
+ 'pr',
65
+ 'pl',
66
+ 'pt',
67
+ 'pb',
68
+ 'padding',
69
+ 'display',
70
+ 'position',
71
+ 'minHeight',
72
+ 'minWidth',
73
+ 'height',
74
+ 'width',
75
+ 'maxHeight',
76
+ 'maxWidth',
77
+ 'flexGrow',
78
+ 'flexBasis',
79
+ 'flexShrink',
80
+ 'overflowY',
81
+ 'overflowX',
82
+ 'textOverflow',
83
+ 'overflow',
84
+ 'zIndex',
85
+ 'stylingOverrides',
86
+ 'fadeIn'
87
+ )
88
+ )<SpacerProps>(props => {
89
+ const { mx, my, mr, ml, mt, mb, margin, px, py, pl, pr, pt, pb, padding } = props;
90
+ const marginRight = mr ?? mx;
91
+ const marginLeft = ml ?? mx;
92
+ const marginTop = mt ?? my;
93
+ const marginBottom = mb ?? my;
94
+ const paddingRight = pr ?? px;
95
+ const paddingLeft = pl ?? px;
96
+ const paddingTop = pt ?? py;
97
+ const paddingBottom = pb ?? py;
98
+ return `
99
+ ${cssProp('display', props.display)}
100
+ ${cssProp('position', props.position)}
101
+ ${cssProp('margin', margin)}
102
+ ${cssProp('margin-right', marginRight)}
103
+ ${cssProp('margin-left', marginLeft)}
104
+ ${cssProp('margin-top', marginTop)}
105
+ ${cssProp('margin-bottom', marginBottom)}
106
+ ${cssProp('padding', padding)}
107
+ ${cssProp('padding-right', paddingRight)}
108
+ ${cssProp('padding-left', paddingLeft)}
109
+ ${cssProp('padding-top', paddingTop)}
110
+ ${cssProp('padding-bottom', paddingBottom)}
111
+ ${cssProp('min-height', props.minHeight)}
112
+ ${cssProp('min-width', props.minWidth)}
113
+ ${cssProp('height', props.height)}
114
+ ${cssProp('width', props.width)}
115
+ ${cssProp('max-height', props.maxHeight)}
116
+ ${cssProp('max-width', props.maxWidth)}
117
+ ${cssProp('flex-grow', props.flexGrow)}
118
+ ${cssProp('flex-shrink', props.flexShrink)}
119
+ ${cssProp('flex-basis', props.flexBasis)}
120
+ ${cssProp('overflow-y', props.overflowY)}
121
+ ${cssProp('overflow-x', props.overflowX)}
122
+ ${cssProp('overflow', props.overflow)}
123
+ ${cssProp('text-overflow', props.textOverflow)}
124
+ ${cssProp('z-index', props.zIndex)}
125
+
126
+ animation: ${props.fadeIn ? `fadeIn 200ms cubic-bezier(0.22, 1, 0.36, 1) forwards` : 'none'};
127
+ ${props.fadeIn ? `@media (prefers-reduced-motion: reduce) { animation: none; opacity: 1; }` : ''}
128
+
129
+ ${props.stylingOverrides?.styles ? props.stylingOverrides.styles : ''}
130
+ `;
131
+ });
@@ -0,0 +1,2 @@
1
+ export { Spacer, type SpacerProps } from './Spacer';
2
+ export { FlexLayout, FlexLayoutSpacer, type FlexLayoutProps } from './FlexLayout';
@@ -0,0 +1,55 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { EditorSettingsProvider, useEditorSettings } from './EditorSettingsContext';
5
+
6
+ // Exercises the context only — Monaco itself can't render under happy-dom.
7
+ const Probe = () => {
8
+ const { vimEnabled, toggleVimMode, wordWrapEnabled, toggleWordWrap } = useEditorSettings();
9
+ return (
10
+ <div>
11
+ <span data-testid='vim'>{String(vimEnabled)}</span>
12
+ <span data-testid='wrap'>{String(wordWrapEnabled)}</span>
13
+ <button onClick={toggleVimMode}>toggle vim</button>
14
+ <button onClick={toggleWordWrap}>toggle wrap</button>
15
+ </div>
16
+ );
17
+ };
18
+
19
+ const renderProbe = () =>
20
+ render(
21
+ <EditorSettingsProvider>
22
+ <Probe />
23
+ </EditorSettingsProvider>
24
+ );
25
+
26
+ describe('EditorSettingsContext', () => {
27
+ beforeEach(() => localStorage.clear());
28
+
29
+ it('defaults both settings to disabled', () => {
30
+ renderProbe();
31
+ expect(screen.getByTestId('vim')).toHaveTextContent('false');
32
+ expect(screen.getByTestId('wrap')).toHaveTextContent('false');
33
+ });
34
+
35
+ it('toggles vim mode and persists it to localStorage', async () => {
36
+ const user = userEvent.setup();
37
+ renderProbe();
38
+ await user.click(screen.getByRole('button', { name: 'toggle vim' }));
39
+ expect(screen.getByTestId('vim')).toHaveTextContent('true');
40
+ expect(localStorage.getItem('monaco-editor-vim-mode')).toBe('true');
41
+ });
42
+
43
+ it('hydrates initial state from localStorage', () => {
44
+ localStorage.setItem('monaco-editor-word-wrap', 'true');
45
+ renderProbe();
46
+ expect(screen.getByTestId('wrap')).toHaveTextContent('true');
47
+ });
48
+
49
+ it('throws when the hook is used outside the provider', () => {
50
+ // Suppress the expected React error log for the thrown render.
51
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
52
+ expect(() => render(<Probe />)).toThrow(/within EditorSettingsProvider/);
53
+ spy.mockRestore();
54
+ });
55
+ });
@@ -0,0 +1,63 @@
1
+ import { createContext, useContext, useState, type ReactNode } from 'react';
2
+
3
+ // region Helpers
4
+
5
+ const VIM_MODE_STORAGE_KEY = 'monaco-editor-vim-mode';
6
+ const WORD_WRAP_STORAGE_KEY = 'monaco-editor-word-wrap';
7
+
8
+ export interface EditorSettings {
9
+ vimEnabled: boolean;
10
+ toggleVimMode: () => void;
11
+ wordWrapEnabled: boolean;
12
+ toggleWordWrap: () => void;
13
+ }
14
+
15
+ const EditorSettingsContext = createContext<EditorSettings | undefined>(undefined);
16
+
17
+ // region Component
18
+
19
+ /**
20
+ * Provides per-user editor preferences (vim mode, word wrap), persisted to
21
+ * `localStorage`. Wrap any tree that renders `<MonacoEditorWithSettings />`.
22
+ */
23
+ export function EditorSettingsProvider({ children }: { children: ReactNode }) {
24
+ const [vimEnabled, setVimEnabled] = useState(() => {
25
+ const saved = localStorage.getItem(VIM_MODE_STORAGE_KEY);
26
+ return saved === 'true';
27
+ });
28
+
29
+ const [wordWrapEnabled, setWordWrapEnabled] = useState(() => {
30
+ const saved = localStorage.getItem(WORD_WRAP_STORAGE_KEY);
31
+ return saved === 'true';
32
+ });
33
+
34
+ const toggleVimMode = () => {
35
+ setVimEnabled(prev => {
36
+ const newValue = !prev;
37
+ localStorage.setItem(VIM_MODE_STORAGE_KEY, String(newValue));
38
+ return newValue;
39
+ });
40
+ };
41
+
42
+ const toggleWordWrap = () => {
43
+ setWordWrapEnabled(prev => {
44
+ const newValue = !prev;
45
+ localStorage.setItem(WORD_WRAP_STORAGE_KEY, String(newValue));
46
+ return newValue;
47
+ });
48
+ };
49
+
50
+ return (
51
+ <EditorSettingsContext.Provider value={{ vimEnabled, toggleVimMode, wordWrapEnabled, toggleWordWrap }}>
52
+ {children}
53
+ </EditorSettingsContext.Provider>
54
+ );
55
+ }
56
+
57
+ export function useEditorSettings() {
58
+ const context = useContext(EditorSettingsContext);
59
+ if (context === undefined) {
60
+ throw new Error('useEditorSettings must be used within EditorSettingsProvider');
61
+ }
62
+ return context;
63
+ }
@@ -0,0 +1,197 @@
1
+ import { css } from "@emotion/react";
2
+ import type { Decorator, Meta, StoryObj } from "@storybook/react";
3
+ import { Drawer } from "antd";
4
+ import { useState } from "react";
5
+ import { Button } from "../Button";
6
+ import { CloseButton } from "../CloseButton";
7
+ import { MonacoEditorWithSettings } from "./MonacoEditorWithSettings";
8
+
9
+ const meta: Meta<typeof MonacoEditorWithSettings> = {
10
+ title: "Common / MonacoEditor",
11
+ component: MonacoEditorWithSettings,
12
+ args: {
13
+ language: "json",
14
+ height: "100%",
15
+ },
16
+ argTypes: {
17
+ language: {
18
+ control: "select",
19
+ options: ["json", "yaml", "javascript", "typescript", "html", "css"],
20
+ },
21
+ readOnly: { control: "boolean" },
22
+ },
23
+ };
24
+
25
+ // Sizes the inline editor stories; the Drawer story renders just its button.
26
+ const editorBox: Decorator = (Story) => (
27
+ <div style={{ height: 360 }}>
28
+ <Story />
29
+ </div>
30
+ );
31
+ export default meta;
32
+
33
+ type Story = StoryObj<typeof MonacoEditorWithSettings>;
34
+
35
+ export const Json: Story = {
36
+ name: "JSON",
37
+ decorators: [editorBox],
38
+ args: {
39
+ language: "json",
40
+ value: `{
41
+ "name": "@solo-io-public/ui-components",
42
+ "private": false,
43
+ "keep": "the button"
44
+ }`,
45
+ },
46
+ };
47
+
48
+ export const Yaml: Story = {
49
+ name: "YAML",
50
+ decorators: [editorBox],
51
+ args: {
52
+ language: "yaml",
53
+ value: `name: example
54
+ items:
55
+ - one
56
+ - two
57
+ nested:
58
+ enabled: true`,
59
+ },
60
+ };
61
+
62
+ export const ReadOnly: Story = {
63
+ decorators: [editorBox],
64
+ args: {
65
+ language: "typescript",
66
+ readOnly: true,
67
+ value: `export const add = (a: number, b: number): number => a + b;`,
68
+ },
69
+ };
70
+
71
+ const K8S_YAML = `apiVersion: apps/v1
72
+ kind: Deployment
73
+ metadata:
74
+ name: payments-api
75
+ namespace: payments
76
+ labels:
77
+ app.kubernetes.io/name: payments-api
78
+ app.kubernetes.io/instance: payments-api
79
+ app.kubernetes.io/version: "1.8.3"
80
+ app.kubernetes.io/component: api
81
+ app.kubernetes.io/part-of: payments-platform
82
+ app.kubernetes.io/managed-by: helm
83
+ annotations:
84
+ kubernetes.io/change-cause: "Deploy v1.8.3"
85
+ prometheus.io/scrape: "true"
86
+ prometheus.io/port: "9090"
87
+ spec:
88
+ replicas: 3
89
+ revisionHistoryLimit: 5
90
+ strategy:
91
+ type: RollingUpdate
92
+ rollingUpdate:
93
+ maxSurge: 1
94
+ maxUnavailable: 0
95
+ selector:
96
+ matchLabels:
97
+ app.kubernetes.io/name: payments-api
98
+ template:
99
+ metadata:
100
+ labels:
101
+ app.kubernetes.io/name: payments-api
102
+ app.kubernetes.io/instance: payments-api
103
+ spec:
104
+ serviceAccountName: payments-api
105
+ securityContext:
106
+ runAsNonRoot: true
107
+ runAsUser: 10001
108
+ fsGroup: 10001
109
+ containers:
110
+ - name: api
111
+ image: registry.example.com/payments/payments-api:1.8.3
112
+ imagePullPolicy: IfNotPresent
113
+ ports:
114
+ - name: http
115
+ containerPort: 8080
116
+ - name: metrics
117
+ containerPort: 9090
118
+ env:
119
+ - name: LOG_LEVEL
120
+ value: info
121
+ - name: DATABASE_URL
122
+ valueFrom:
123
+ secretKeyRef:
124
+ name: payments-db
125
+ key: url
126
+ - name: REDIS_HOST
127
+ value: redis.payments.svc.cluster.local
128
+ resources:
129
+ requests:
130
+ cpu: 250m
131
+ memory: 256Mi
132
+ limits:
133
+ cpu: "1"
134
+ memory: 512Mi
135
+ readinessProbe:
136
+ httpGet:
137
+ path: /healthz/ready
138
+ port: http
139
+ initialDelaySeconds: 5
140
+ periodSeconds: 10
141
+ livenessProbe:
142
+ httpGet:
143
+ path: /healthz/live
144
+ port: http
145
+ initialDelaySeconds: 15
146
+ periodSeconds: 20
147
+ volumeMounts:
148
+ - name: config
149
+ mountPath: /etc/payments
150
+ readOnly: true
151
+ volumes:
152
+ - name: config
153
+ configMap:
154
+ name: payments-api-config
155
+ `;
156
+
157
+ // Demonstrates the common "view a resource" pattern: a button opens a Drawer
158
+ // with the editor in read-only mode. The manifest is long enough to scroll,
159
+ // which also shows the controls shifting clear of the vertical scrollbar.
160
+ const ViewYamlDrawer = () => {
161
+ const [open, setOpen] = useState(false);
162
+ return (
163
+ <>
164
+ <Button color="dark-purple" onClick={() => setOpen(true)}>
165
+ View YAML
166
+ </Button>
167
+ <Drawer
168
+ title="payments-api.deployment.yaml"
169
+ placement="right"
170
+ width={680}
171
+ open={open}
172
+ onClose={() => setOpen(false)}
173
+ closable={false}
174
+ extra={<CloseButton aria-label="Close" onClick={() => setOpen(false)} />}
175
+ styles={{ body: { padding: 0 } }}
176
+ >
177
+ <MonacoEditorWithSettings
178
+ value={K8S_YAML}
179
+ language="yaml"
180
+ readOnly
181
+ height="100%"
182
+ // Full-bleed in the drawer: drop the rounded corners + side/top border
183
+ // so it sits flush under the drawer title (the drawer supplies the frame).
184
+ styleOverrides={css`
185
+ border-radius: 0;
186
+ border-width: 0 0 1px 0;
187
+ `}
188
+ />
189
+ </Drawer>
190
+ </>
191
+ );
192
+ };
193
+
194
+ export const ViewInDrawer: Story = {
195
+ name: "View YAML in Drawer",
196
+ render: () => <ViewYamlDrawer />,
197
+ };