@tanstack/query-devtools 5.21.5 → 5.24.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/build/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createSignal, render, lazy, setupStyleSheet, createComponent, mergeProps } from './chunk/XKKA3NOX.js';
1
+ import { createSignal, render, lazy, setupStyleSheet, createComponent, mergeProps } from './chunk/7LTSZCE6.js';
2
2
 
3
3
  // src/index.tsx
4
4
  var TanstackQueryDevtools = class {
@@ -66,7 +66,7 @@ var TanstackQueryDevtools = class {
66
66
  if (this.#Component) {
67
67
  Devtools = this.#Component;
68
68
  } else {
69
- Devtools = lazy(() => import('./Devtools/MY47IRH5.js'));
69
+ Devtools = lazy(() => import('./Devtools/335X72D7.js'));
70
70
  this.#Component = Devtools;
71
71
  }
72
72
  setupStyleSheet(this.#styleNonce);
package/build/index.jsx CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  lazy,
4
4
  render,
5
5
  setupStyleSheet
6
- } from "./chunk/AZNCIVVX.jsx";
6
+ } from "./chunk/LJVATCV2.jsx";
7
7
 
8
8
  // src/index.tsx
9
9
  var TanstackQueryDevtools = class {
@@ -70,7 +70,7 @@ var TanstackQueryDevtools = class {
70
70
  if (this.#Component) {
71
71
  Devtools = this.#Component;
72
72
  } else {
73
- Devtools = lazy(() => import("./Devtools/OQR5HZUS.jsx"));
73
+ Devtools = lazy(() => import("./Devtools/36BZFNWK.jsx"));
74
74
  this.#Component = Devtools;
75
75
  }
76
76
  setupStyleSheet(this.#styleNonce);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/query-devtools",
3
- "version": "5.21.5",
3
+ "version": "5.24.0",
4
4
  "description": "Developer tools to interact with and visualize the TanStack Query cache",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@
50
50
  "superjson": "^1.13.3",
51
51
  "tsup-preset-solid": "^2.2.0",
52
52
  "vite-plugin-solid": "^2.9.1",
53
- "@tanstack/query-core": "5.21.4"
53
+ "@tanstack/query-core": "5.22.2"
54
54
  },
55
55
  "scripts": {
56
56
  "clean": "rimraf ./build && rimraf ./coverage",
package/src/Devtools.tsx CHANGED
@@ -2,12 +2,14 @@ import {
2
2
  For,
3
3
  Show,
4
4
  batch,
5
+ createContext,
5
6
  createEffect,
6
7
  createMemo,
7
8
  createSignal,
8
9
  on,
9
10
  onCleanup,
10
11
  onMount,
12
+ useContext,
11
13
  } from 'solid-js'
12
14
  import { rankItem } from '@tanstack/match-sorter-utils'
13
15
  import { css } from 'goober'
@@ -17,6 +19,7 @@ import { Key } from '@solid-primitives/keyed'
17
19
  import { createLocalStorage } from '@solid-primitives/storage'
18
20
  import { createResizeObserver } from '@solid-primitives/resize-observer'
19
21
  import { DropdownMenu, RadioGroup } from '@kobalte/core'
22
+ import { Portal, clearDelegatedEvents, delegateEvents } from 'solid-js/web'
20
23
  import { tokens } from './theme'
21
24
  import {
22
25
  convertRemToPixels,
@@ -42,6 +45,7 @@ import {
42
45
  Moon,
43
46
  Offline,
44
47
  PauseCircle,
48
+ PiPIcon,
45
49
  Search,
46
50
  Settings,
47
51
  Sun,
@@ -109,6 +113,175 @@ const [panelWidth, setPanelWidth] = createSignal(0)
109
113
 
110
114
  export type DevtoolsComponentType = Component<QueryDevtoolsProps>
111
115
 
116
+ interface PiPProviderProps {
117
+ children: JSX.Element
118
+ localStore: StorageObject<string>
119
+ setLocalStore: StorageSetter<string, unknown>
120
+ }
121
+
122
+ type PiPContextType = {
123
+ pipWindow: Window | null
124
+ requestPipWindow: (width: number, height: number) => Promise<void>
125
+ closePipWindow: () => void
126
+ }
127
+
128
+ const PiPContext = createContext<Accessor<PiPContextType> | undefined>(
129
+ undefined,
130
+ )
131
+
132
+ const PiPProvider = (props: PiPProviderProps) => {
133
+ // Expose pipWindow that is currently active
134
+ const [pipWindow, setPipWindow] = createSignal<Window | null>(null)
135
+
136
+ // Close pipWindow programmatically
137
+ const closePipWindow = () => {
138
+ const w = pipWindow()
139
+ if (w != null) {
140
+ w.close()
141
+ setPipWindow(null)
142
+ }
143
+ }
144
+
145
+ // Open new pipWindow
146
+ const requestPipWindow = async (width: number, height: number) => {
147
+ // We don't want to allow multiple requests.
148
+ if (pipWindow() != null) {
149
+ return
150
+ }
151
+
152
+ const pip = window.open(
153
+ '',
154
+ 'TSQD-Devtools-Panel',
155
+ `width=${width},height=${height},popup`,
156
+ )
157
+
158
+ if (!pip) {
159
+ throw new Error(
160
+ 'Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.',
161
+ )
162
+ }
163
+
164
+ // Remove existing styles
165
+ pip.document.head.innerHTML = ''
166
+ // Remove existing body
167
+ pip.document.body.innerHTML = ''
168
+ // Clear Delegated Events
169
+ clearDelegatedEvents(pip.document)
170
+
171
+ pip.document.title = 'TanStack Query Devtools'
172
+ pip.document.body.style.margin = '0'
173
+
174
+ // Detect when window is closed by user
175
+ pip.addEventListener('pagehide', () => {
176
+ props.setLocalStore('pip_open', 'false')
177
+ setPipWindow(null)
178
+ })
179
+
180
+ // It is important to copy all parent window styles. Otherwise, there would be no CSS available at all
181
+ // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window
182
+ ;[...document.styleSheets].forEach((styleSheet) => {
183
+ try {
184
+ const cssRules = [...styleSheet.cssRules]
185
+ .map((rule) => rule.cssText)
186
+ .join('')
187
+ const style = document.createElement('style')
188
+ const style_node = styleSheet.ownerNode
189
+ let style_id = ''
190
+
191
+ if (style_node && 'id' in style_node) {
192
+ style_id = style_node.id
193
+ }
194
+
195
+ if (style_id) {
196
+ style.setAttribute('id', style_id)
197
+ }
198
+ style.textContent = cssRules
199
+ pip.document.head.appendChild(style)
200
+ } catch (e) {
201
+ const link = document.createElement('link')
202
+ if (styleSheet.href == null) {
203
+ return
204
+ }
205
+
206
+ link.rel = 'stylesheet'
207
+ link.type = styleSheet.type
208
+ link.media = styleSheet.media.toString()
209
+ link.href = styleSheet.href
210
+ pip.document.head.appendChild(link)
211
+ }
212
+ })
213
+ delegateEvents(
214
+ [
215
+ 'focusin',
216
+ 'focusout',
217
+ 'pointermove',
218
+ 'keydown',
219
+ 'pointerdown',
220
+ 'pointerup',
221
+ 'click',
222
+ 'mousedown',
223
+ 'input',
224
+ ],
225
+ pip.document,
226
+ )
227
+ props.setLocalStore('pip_open', 'true')
228
+ setPipWindow(pip)
229
+ }
230
+
231
+ createEffect(() => {
232
+ const pip_open = (props.localStore.pip_open ?? 'false') as 'true' | 'false'
233
+ if (pip_open === 'true') {
234
+ requestPipWindow(
235
+ Number(window.innerWidth),
236
+ Number(props.localStore.height || DEFAULT_HEIGHT),
237
+ )
238
+ }
239
+ })
240
+
241
+ createEffect(() => {
242
+ // Setup mutation observer for goober styles with id `_goober
243
+ const gooberStyles = document.getElementById('_goober')
244
+ const w = pipWindow()
245
+ if (gooberStyles && w) {
246
+ const observer = new MutationObserver(() => {
247
+ const pip_style = w.document.getElementById('_goober')
248
+ if (pip_style) {
249
+ pip_style.textContent = gooberStyles.textContent
250
+ }
251
+ })
252
+ observer.observe(gooberStyles, {
253
+ childList: true, // observe direct children
254
+ subtree: true, // and lower descendants too
255
+ characterDataOldValue: true, // pass old data to callback
256
+ })
257
+ onCleanup(() => {
258
+ observer.disconnect()
259
+ })
260
+ }
261
+ })
262
+
263
+ const value = createMemo(() => ({
264
+ pipWindow: pipWindow(),
265
+ requestPipWindow,
266
+ closePipWindow,
267
+ }))
268
+
269
+ return (
270
+ <PiPContext.Provider value={value}>{props.children}</PiPContext.Provider>
271
+ )
272
+ }
273
+
274
+ const usePiPWindow = () => {
275
+ const context = createMemo(() => {
276
+ const ctx = useContext(PiPContext)
277
+ if (!ctx) {
278
+ throw new Error('usePiPWindow must be used within a PiPProvider')
279
+ }
280
+ return ctx()
281
+ })
282
+ return context
283
+ }
284
+
112
285
  const DevtoolsComponent: DevtoolsComponentType = (props) => {
113
286
  const [localStore, setLocalStore] = createLocalStorage({
114
287
  prefix: 'TanstackQueryDevtools',
@@ -127,9 +300,11 @@ const DevtoolsComponent: DevtoolsComponentType = (props) => {
127
300
 
128
301
  return (
129
302
  <QueryDevtoolsContext.Provider value={props}>
130
- <ThemeContext.Provider value={theme}>
131
- <Devtools localStore={localStore} setLocalStore={setLocalStore} />
132
- </ThemeContext.Provider>
303
+ <PiPProvider localStore={localStore} setLocalStore={setLocalStore}>
304
+ <ThemeContext.Provider value={theme}>
305
+ <Devtools localStore={localStore} setLocalStore={setLocalStore} />
306
+ </ThemeContext.Provider>
307
+ </PiPProvider>
133
308
  </QueryDevtoolsContext.Provider>
134
309
  )
135
310
  }
@@ -142,6 +317,8 @@ const Devtools: Component<DevtoolsPanelProps> = (props) => {
142
317
  return theme() === 'dark' ? darkStyles : lightStyles
143
318
  })
144
319
 
320
+ const pip = usePiPWindow()
321
+
145
322
  const buttonPosition = createMemo(() => {
146
323
  return useQueryDevtoolsContext().buttonPosition || BUTTON_POSITION
147
324
  })
@@ -195,79 +372,162 @@ const Devtools: Component<DevtoolsPanelProps> = (props) => {
195
372
  })
196
373
  })
197
374
 
375
+ const pip_open = createMemo(
376
+ () => (props.localStore.pip_open ?? 'false') as 'true' | 'false',
377
+ )
378
+
198
379
  return (
199
- <div
200
- // styles for animating the panel in and out
201
- class={cx(
202
- css`
203
- & .tsqd-panel-transition-exit-active,
204
- & .tsqd-panel-transition-enter-active {
205
- transition:
206
- opacity 0.3s,
207
- transform 0.3s;
208
- }
380
+ <>
381
+ <Show when={pip().pipWindow && pip_open() == 'true'}>
382
+ <Portal mount={pip().pipWindow?.document.body}>
383
+ <PiPPanel>
384
+ <ContentView
385
+ localStore={props.localStore}
386
+ setLocalStore={props.setLocalStore}
387
+ />
388
+ </PiPPanel>
389
+ </Portal>
390
+ </Show>
391
+ <div
392
+ // styles for animating the panel in and out
393
+ class={cx(
394
+ css`
395
+ & .tsqd-panel-transition-exit-active,
396
+ & .tsqd-panel-transition-enter-active {
397
+ transition:
398
+ opacity 0.3s,
399
+ transform 0.3s;
400
+ }
209
401
 
210
- & .tsqd-panel-transition-exit-to,
211
- & .tsqd-panel-transition-enter {
212
- ${position() === 'top' || position() === 'bottom'
213
- ? `transform: translateY(var(--tsqd-panel-height));`
214
- : `transform: translateX(var(--tsqd-panel-width));`}
215
- }
402
+ & .tsqd-panel-transition-exit-to,
403
+ & .tsqd-panel-transition-enter {
404
+ ${position() === 'top' || position() === 'bottom'
405
+ ? `transform: translateY(var(--tsqd-panel-height));`
406
+ : `transform: translateX(var(--tsqd-panel-width));`}
407
+ }
216
408
 
217
- & .tsqd-button-transition-exit-active,
218
- & .tsqd-button-transition-enter-active {
219
- transition:
220
- opacity 0.3s,
221
- transform 0.3s;
222
- opacity: 1;
223
- }
409
+ & .tsqd-button-transition-exit-active,
410
+ & .tsqd-button-transition-enter-active {
411
+ transition:
412
+ opacity 0.3s,
413
+ transform 0.3s;
414
+ opacity: 1;
415
+ }
224
416
 
225
- & .tsqd-button-transition-exit-to,
226
- & .tsqd-button-transition-enter {
227
- transform: ${buttonPosition() === 'relative'
228
- ? `none;`
229
- : buttonPosition() === 'top-left'
230
- ? `translateX(-72px);`
231
- : buttonPosition() === 'top-right'
232
- ? `translateX(72px);`
233
- : `translateY(72px);`};
234
- opacity: 0;
235
- }
236
- `,
237
- 'tsqd-transitions-container',
417
+ & .tsqd-button-transition-exit-to,
418
+ & .tsqd-button-transition-enter {
419
+ transform: ${buttonPosition() === 'relative'
420
+ ? `none;`
421
+ : buttonPosition() === 'top-left'
422
+ ? `translateX(-72px);`
423
+ : buttonPosition() === 'top-right'
424
+ ? `translateX(72px);`
425
+ : `translateY(72px);`};
426
+ opacity: 0;
427
+ }
428
+ `,
429
+ 'tsqd-transitions-container',
430
+ )}
431
+ ref={transitionsContainerRef}
432
+ >
433
+ <TransitionGroup name="tsqd-panel-transition">
434
+ <Show when={isOpen() && !pip().pipWindow && pip_open() == 'false'}>
435
+ <DevtoolsPanel
436
+ localStore={props.localStore}
437
+ setLocalStore={props.setLocalStore}
438
+ />
439
+ </Show>
440
+ </TransitionGroup>
441
+ <TransitionGroup name="tsqd-button-transition">
442
+ <Show when={!isOpen()}>
443
+ <div
444
+ class={cx(
445
+ styles().devtoolsBtn,
446
+ styles()[`devtoolsBtn-position-${buttonPosition()}`],
447
+ 'tsqd-open-btn-container',
448
+ )}
449
+ >
450
+ <div aria-hidden="true">
451
+ <TanstackLogo />
452
+ </div>
453
+ <button
454
+ aria-label="Open Tanstack query devtools"
455
+ onClick={() => props.setLocalStore('open', 'true')}
456
+ class="tsqd-open-btn"
457
+ >
458
+ <TanstackLogo />
459
+ </button>
460
+ </div>
461
+ </Show>
462
+ </TransitionGroup>
463
+ </div>
464
+ </>
465
+ )
466
+ }
467
+
468
+ const PiPPanel: Component<{
469
+ children: JSX.Element
470
+ }> = (props) => {
471
+ const pip = usePiPWindow()
472
+ const theme = useTheme()
473
+ const styles = createMemo(() => {
474
+ return theme() === 'dark' ? darkStyles : lightStyles
475
+ })
476
+
477
+ const getPanelDynamicStyles = () => {
478
+ const { colors } = tokens
479
+ const t = (light: string, dark: string) =>
480
+ theme() === 'dark' ? dark : light
481
+ if (panelWidth() < secondBreakpoint) {
482
+ return css`
483
+ flex-direction: column;
484
+ background-color: ${t(colors.gray[300], colors.gray[600])};
485
+ `
486
+ }
487
+ return css`
488
+ flex-direction: row;
489
+ background-color: ${t(colors.gray[200], colors.darkGray[900])};
490
+ `
491
+ }
492
+
493
+ createEffect(() => {
494
+ const win = pip().pipWindow
495
+ const resizeCB = () => {
496
+ if (!win) return
497
+ setPanelWidth(win.innerWidth)
498
+ }
499
+ if (win) {
500
+ win.addEventListener('resize', resizeCB)
501
+ resizeCB()
502
+ }
503
+
504
+ onCleanup(() => {
505
+ if (win) {
506
+ win.removeEventListener('resize', resizeCB)
507
+ }
508
+ })
509
+ })
510
+
511
+ return (
512
+ <div
513
+ style={{
514
+ '--tsqd-font-size': '16px',
515
+ 'max-height': '100vh',
516
+ height: '100vh',
517
+ width: '100vw',
518
+ }}
519
+ class={cx(
520
+ styles().panel,
521
+ getPanelDynamicStyles(),
522
+ {
523
+ [css`
524
+ min-width: min-content;
525
+ `]: panelWidth() < thirdBreakpoint,
526
+ },
527
+ 'tsqd-main-panel',
238
528
  )}
239
- ref={transitionsContainerRef}
240
529
  >
241
- <TransitionGroup name="tsqd-panel-transition">
242
- <Show when={isOpen()}>
243
- <DevtoolsPanel
244
- localStore={props.localStore}
245
- setLocalStore={props.setLocalStore}
246
- />
247
- </Show>
248
- </TransitionGroup>
249
- <TransitionGroup name="tsqd-button-transition">
250
- <Show when={!isOpen()}>
251
- <div
252
- class={cx(
253
- styles().devtoolsBtn,
254
- styles()[`devtoolsBtn-position-${buttonPosition()}`],
255
- 'tsqd-open-btn-container',
256
- )}
257
- >
258
- <div aria-hidden="true">
259
- <TanstackLogo />
260
- </div>
261
- <button
262
- aria-label="Open Tanstack query devtools"
263
- onClick={() => props.setLocalStore('open', 'true')}
264
- class="tsqd-open-btn"
265
- >
266
- <TanstackLogo />
267
- </button>
268
- </div>
269
- </Show>
270
- </TransitionGroup>
530
+ {props.children}
271
531
  </div>
272
532
  )
273
533
  }
@@ -480,6 +740,8 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
480
740
  return theme() === 'dark' ? darkStyles : lightStyles
481
741
  })
482
742
 
743
+ const pip = usePiPWindow()
744
+
483
745
  const [selectedView, setSelectedView] = createSignal<'queries' | 'mutations'>(
484
746
  'queries',
485
747
  )
@@ -618,7 +880,11 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
618
880
  <div class={styles().logoAndToggleContainer}>
619
881
  <button
620
882
  class={cx(styles().logo, 'tsqd-text-logo-container')}
621
- onClick={() => props.setLocalStore('open', 'false')}
883
+ onClick={() => {
884
+ if (!pip().pipWindow) {
885
+ props.setLocalStore('open', 'false')
886
+ }
887
+ }}
622
888
  aria-label="Close Tanstack query devtools"
623
889
  >
624
890
  <span
@@ -693,7 +959,7 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
693
959
  props.setLocalStore('mutationFilter', e.currentTarget.value)
694
960
  }
695
961
  }}
696
- class="tsqd-query-filter-textfield"
962
+ class={cx('tsqd-query-filter-textfield')}
697
963
  name="tsqd-query-filter-input"
698
964
  value={
699
965
  selectedView() === 'queries'
@@ -833,6 +1099,25 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
833
1099
  >
834
1100
  {offline() ? <Offline /> : <Wifi />}
835
1101
  </button>
1102
+ <Show when={!pip().pipWindow}>
1103
+ <button
1104
+ onClick={() => {
1105
+ pip().requestPipWindow(
1106
+ Number(window.innerWidth),
1107
+ Number(props.localStore.height ?? 500),
1108
+ )
1109
+ }}
1110
+ class={cx(
1111
+ styles().actionsBtn,
1112
+ 'tsqd-actions-btn',
1113
+ 'tsqd-action-open-pip',
1114
+ )}
1115
+ aria-label="Open in picture-in-picture mode"
1116
+ title={`Open in picture-in-picture mode`}
1117
+ >
1118
+ <PiPIcon />
1119
+ </button>
1120
+ </Show>
836
1121
 
837
1122
  <DropdownMenu.Root gutter={4}>
838
1123
  <DropdownMenu.Trigger
@@ -846,6 +1131,11 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
846
1131
  </DropdownMenu.Trigger>
847
1132
  <DropdownMenu.Portal
848
1133
  ref={(el) => setComputedVariables(el as HTMLDivElement)}
1134
+ mount={
1135
+ pip().pipWindow
1136
+ ? pip().pipWindow!.document.body
1137
+ : document.body
1138
+ }
849
1139
  >
850
1140
  <DropdownMenu.Content
851
1141
  class={cx(styles().settingsMenu, 'tsqd-settings-menu')}
@@ -871,6 +1161,11 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
871
1161
  </DropdownMenu.SubTrigger>
872
1162
  <DropdownMenu.Portal
873
1163
  ref={(el) => setComputedVariables(el as HTMLDivElement)}
1164
+ mount={
1165
+ pip().pipWindow
1166
+ ? pip().pipWindow!.document.body
1167
+ : document.body
1168
+ }
874
1169
  >
875
1170
  <DropdownMenu.SubContent
876
1171
  class={cx(
@@ -950,6 +1245,11 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
950
1245
  </DropdownMenu.SubTrigger>
951
1246
  <DropdownMenu.Portal
952
1247
  ref={(el) => setComputedVariables(el as HTMLDivElement)}
1248
+ mount={
1249
+ pip().pipWindow
1250
+ ? pip().pipWindow!.document.body
1251
+ : document.body
1252
+ }
953
1253
  >
954
1254
  <DropdownMenu.SubContent
955
1255
  class={cx(
@@ -2602,8 +2902,8 @@ const stylesFactory = (theme: 'light' | 'dark') => {
2602
2902
  gap: ${tokens.size[2]};
2603
2903
  & > button {
2604
2904
  cursor: pointer;
2605
- padding: ${tokens.size[0.5]} ${tokens.size[2]};
2606
- padding-right: ${tokens.size[1.5]};
2905
+ padding: ${tokens.size[0.5]} ${tokens.size[1.5]} ${tokens.size[0.5]}
2906
+ ${tokens.size[2]};
2607
2907
  border-radius: ${tokens.border.radius.sm};
2608
2908
  background-color: ${t(colors.gray[100], colors.darkGray[400])};
2609
2909
  border: 1px solid ${t(colors.gray[300], colors.darkGray[200])};
@@ -268,6 +268,26 @@ export function Settings() {
268
268
  )
269
269
  }
270
270
 
271
+ export function PiPIcon() {
272
+ return (
273
+ <svg
274
+ width="24"
275
+ height="24"
276
+ viewBox="0 0 24 24"
277
+ fill="none"
278
+ xmlns="http://www.w3.org/2000/svg"
279
+ >
280
+ <path
281
+ d="M16 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V7.8C21 6.11984 21 5.27976 20.673 4.63803C20.3854 4.07354 19.9265 3.6146 19.362 3.32698C18.7202 3 17.8802 3 16.2 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V8M11.5 12.5L17 7M17 7H12M17 7V12M6.2 21H8.8C9.9201 21 10.4802 21 10.908 20.782C11.2843 20.5903 11.5903 20.2843 11.782 19.908C12 19.4802 12 18.9201 12 17.8V15.2C12 14.0799 12 13.5198 11.782 13.092C11.5903 12.7157 11.2843 12.4097 10.908 12.218C10.4802 12 9.92011 12 8.8 12H6.2C5.0799 12 4.51984 12 4.09202 12.218C3.71569 12.4097 3.40973 12.7157 3.21799 13.092C3 13.5198 3 14.0799 3 15.2V17.8C3 18.9201 3 19.4802 3.21799 19.908C3.40973 20.2843 3.71569 20.5903 4.09202 20.782C4.51984 21 5.07989 21 6.2 21Z"
282
+ stroke="currentColor"
283
+ stroke-width="2"
284
+ stroke-linecap="round"
285
+ stroke-linejoin="round"
286
+ />
287
+ </svg>
288
+ )
289
+ }
290
+
271
291
  export function Copier() {
272
292
  return (
273
293
  <svg