@tanstack/devtools 0.0.0 → 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 (125) hide show
  1. package/README.md +41 -0
  2. package/dist/cjs/components/checkbox.cjs +55 -0
  3. package/dist/cjs/components/checkbox.cjs.map +1 -0
  4. package/dist/cjs/components/checkbox.d.cts +8 -0
  5. package/dist/cjs/components/content-panel.cjs +5 -1
  6. package/dist/cjs/components/content-panel.cjs.map +1 -1
  7. package/dist/cjs/components/input.cjs +57 -0
  8. package/dist/cjs/components/input.cjs.map +1 -0
  9. package/dist/cjs/components/input.d.cts +10 -0
  10. package/dist/cjs/components/logo.cjs.map +1 -1
  11. package/dist/cjs/components/main-panel.cjs +4 -1
  12. package/dist/cjs/components/main-panel.cjs.map +1 -1
  13. package/dist/cjs/components/select.cjs +59 -0
  14. package/dist/cjs/components/select.cjs.map +1 -0
  15. package/dist/cjs/components/select.d.cts +13 -0
  16. package/dist/cjs/components/tab-content.cjs +2 -4
  17. package/dist/cjs/components/tab-content.cjs.map +1 -1
  18. package/dist/cjs/components/tabs.cjs.map +1 -1
  19. package/dist/cjs/components/trigger.cjs +1 -1
  20. package/dist/cjs/components/trigger.cjs.map +1 -1
  21. package/dist/cjs/constants.cjs +7 -0
  22. package/dist/cjs/constants.cjs.map +1 -0
  23. package/dist/cjs/constants.d.cts +2 -0
  24. package/dist/cjs/context/devtools-context.cjs +16 -1
  25. package/dist/cjs/context/devtools-context.cjs.map +1 -1
  26. package/dist/cjs/context/devtools-context.d.cts +5 -5
  27. package/dist/cjs/context/devtools-store.cjs.map +1 -1
  28. package/dist/cjs/context/devtools-store.d.cts +3 -3
  29. package/dist/cjs/context/use-devtools-context.cjs +2 -2
  30. package/dist/cjs/context/use-devtools-context.cjs.map +1 -1
  31. package/dist/cjs/context/use-devtools-context.d.cts +4 -5
  32. package/dist/cjs/core.cjs +29 -43
  33. package/dist/cjs/core.cjs.map +1 -1
  34. package/dist/cjs/core.d.cts +7 -8
  35. package/dist/cjs/devtools.cjs +40 -29
  36. package/dist/cjs/devtools.cjs.map +1 -1
  37. package/dist/cjs/hooks/use-disable-tabbing.cjs.map +1 -1
  38. package/dist/cjs/index.cjs +4 -1
  39. package/dist/cjs/index.cjs.map +1 -1
  40. package/dist/cjs/index.d.cts +4 -3
  41. package/dist/cjs/styles/tokens.cjs +7 -2
  42. package/dist/cjs/styles/tokens.cjs.map +1 -1
  43. package/dist/cjs/styles/use-styles.cjs +251 -11
  44. package/dist/cjs/styles/use-styles.cjs.map +1 -1
  45. package/dist/cjs/styles/use-styles.d.cts +29 -5
  46. package/dist/cjs/tabs/index.cjs.map +1 -1
  47. package/dist/cjs/tabs/plugins-tab.cjs +8 -12
  48. package/dist/cjs/tabs/plugins-tab.cjs.map +1 -1
  49. package/dist/cjs/tabs/settings-tab.cjs +172 -2
  50. package/dist/cjs/tabs/settings-tab.cjs.map +1 -1
  51. package/dist/cjs/utils/sanitize.cjs +2 -0
  52. package/dist/cjs/utils/sanitize.cjs.map +1 -1
  53. package/dist/cjs/utils/sanitize.d.cts +1 -0
  54. package/dist/cjs/utils/storage.cjs.map +1 -1
  55. package/dist/esm/components/checkbox.d.ts +8 -0
  56. package/dist/esm/components/checkbox.js +55 -0
  57. package/dist/esm/components/checkbox.js.map +1 -0
  58. package/dist/esm/components/content-panel.js +5 -1
  59. package/dist/esm/components/content-panel.js.map +1 -1
  60. package/dist/esm/components/input.d.ts +10 -0
  61. package/dist/esm/components/input.js +57 -0
  62. package/dist/esm/components/input.js.map +1 -0
  63. package/dist/esm/components/logo.js.map +1 -1
  64. package/dist/esm/components/main-panel.js +5 -2
  65. package/dist/esm/components/main-panel.js.map +1 -1
  66. package/dist/esm/components/select.d.ts +13 -0
  67. package/dist/esm/components/select.js +59 -0
  68. package/dist/esm/components/select.js.map +1 -0
  69. package/dist/esm/components/tab-content.js +2 -4
  70. package/dist/esm/components/tab-content.js.map +1 -1
  71. package/dist/esm/components/tabs.js.map +1 -1
  72. package/dist/esm/components/trigger.js +1 -1
  73. package/dist/esm/components/trigger.js.map +1 -1
  74. package/dist/esm/constants.d.ts +2 -0
  75. package/dist/esm/constants.js +7 -0
  76. package/dist/esm/constants.js.map +1 -0
  77. package/dist/esm/context/devtools-context.d.ts +5 -5
  78. package/dist/esm/context/devtools-context.js +16 -1
  79. package/dist/esm/context/devtools-context.js.map +1 -1
  80. package/dist/esm/context/devtools-store.d.ts +3 -3
  81. package/dist/esm/context/devtools-store.js.map +1 -1
  82. package/dist/esm/context/use-devtools-context.d.ts +4 -5
  83. package/dist/esm/context/use-devtools-context.js +2 -2
  84. package/dist/esm/context/use-devtools-context.js.map +1 -1
  85. package/dist/esm/core.d.ts +7 -8
  86. package/dist/esm/core.js +29 -43
  87. package/dist/esm/core.js.map +1 -1
  88. package/dist/esm/devtools.js +42 -31
  89. package/dist/esm/devtools.js.map +1 -1
  90. package/dist/esm/hooks/use-disable-tabbing.js.map +1 -1
  91. package/dist/esm/index.d.ts +4 -3
  92. package/dist/esm/index.js +5 -2
  93. package/dist/esm/index.js.map +1 -1
  94. package/dist/esm/styles/tokens.js +7 -2
  95. package/dist/esm/styles/tokens.js.map +1 -1
  96. package/dist/esm/styles/use-styles.d.ts +29 -5
  97. package/dist/esm/styles/use-styles.js +251 -11
  98. package/dist/esm/styles/use-styles.js.map +1 -1
  99. package/dist/esm/tabs/index.js.map +1 -1
  100. package/dist/esm/tabs/plugins-tab.js +9 -13
  101. package/dist/esm/tabs/plugins-tab.js.map +1 -1
  102. package/dist/esm/tabs/settings-tab.js +173 -3
  103. package/dist/esm/tabs/settings-tab.js.map +1 -1
  104. package/dist/esm/utils/sanitize.d.ts +1 -0
  105. package/dist/esm/utils/sanitize.js +3 -1
  106. package/dist/esm/utils/sanitize.js.map +1 -1
  107. package/dist/esm/utils/storage.js.map +1 -1
  108. package/package.json +7 -9
  109. package/src/components/checkbox.tsx +43 -0
  110. package/src/components/content-panel.tsx +3 -1
  111. package/src/components/input.tsx +42 -0
  112. package/src/components/main-panel.tsx +3 -2
  113. package/src/components/select.tsx +50 -0
  114. package/src/components/trigger.tsx +1 -1
  115. package/src/constants.ts +2 -0
  116. package/src/context/devtools-context.tsx +28 -9
  117. package/src/context/devtools-store.ts +3 -3
  118. package/src/context/use-devtools-context.ts +2 -3
  119. package/src/core.tsx +18 -20
  120. package/src/devtools.tsx +34 -18
  121. package/src/index.ts +7 -3
  122. package/src/styles/use-styles.ts +257 -13
  123. package/src/tabs/plugins-tab.tsx +11 -5
  124. package/src/tabs/settings-tab.tsx +217 -1
  125. package/src/utils/sanitize.ts +3 -0
@@ -1,7 +1,7 @@
1
1
  import * as goober from 'goober'
2
2
  import { createSignal } from 'solid-js'
3
3
  import { tokens } from './tokens'
4
- import type { DevtoolsSettings } from '../context/devtools-context'
4
+ import type { TanStackDevtoolsConfig } from '../context/devtools-context'
5
5
  import type { Accessor } from 'solid-js'
6
6
 
7
7
  const stylesFactory = () => {
@@ -10,10 +10,14 @@ const stylesFactory = () => {
10
10
  const css = goober.css
11
11
 
12
12
  return {
13
- devtoolsPanelContainer: css`
13
+ devtoolsPanelContainer: (
14
+ panelLocation: TanStackDevtoolsConfig['panelLocation'],
15
+ ) => css`
14
16
  direction: ltr;
15
17
  position: fixed;
16
- bottom: 0;
18
+ overflow-y: hidden;
19
+ overflow-x: hidden;
20
+ ${panelLocation}: 0;
17
21
  right: 0;
18
22
  z-index: 99999;
19
23
  width: 100%;
@@ -75,13 +79,14 @@ const stylesFactory = () => {
75
79
  color: ${colors.gray[300]};
76
80
  width: w-screen;
77
81
  flex-direction: row;
78
- overflow: auto;
82
+ overflow-x: hidden;
83
+ overflow-y: hidden;
79
84
  height: 100%;
80
85
  `,
81
- dragHandle: css`
86
+ dragHandle: (panelLocation: TanStackDevtoolsConfig['panelLocation']) => css`
82
87
  position: absolute;
83
88
  left: 0;
84
- top: 0;
89
+ ${panelLocation === 'bottom' ? 'top' : 'bottom'}: 0;
85
90
  width: 100%;
86
91
  height: 4px;
87
92
  cursor: row-resize;
@@ -105,13 +110,23 @@ const stylesFactory = () => {
105
110
  font-size: ${font.size.xs};
106
111
  cursor: pointer;
107
112
  transition: all 0.25s ease-out;
113
+ &:hide-until-hover {
114
+ opacity: 0;
115
+ pointer-events: none;
116
+ visibility: hidden;
117
+ }
118
+ &:hide-until-hover:hover {
119
+ opacity: 1;
120
+ pointer-events: auto;
121
+ visibility: visible;
122
+ }
108
123
  &:focus-visible {
109
124
  outline-offset: 2px;
110
125
  border-radius: ${border.radius.full};
111
126
  outline: 2px solid ${colors.blue[800]};
112
127
  }
113
128
  `,
114
- mainCloseBtnPosition: (position: DevtoolsSettings['position']) => {
129
+ mainCloseBtnPosition: (position: TanStackDevtoolsConfig['position']) => {
115
130
  const base = css`
116
131
  ${position === 'top-left' ? `top: ${size[2]}; left: ${size[2]};` : ''}
117
132
  ${position === 'top-right' ? `top: ${size[2]}; right: ${size[2]};` : ''}
@@ -130,13 +145,23 @@ const stylesFactory = () => {
130
145
  `
131
146
  return base
132
147
  },
133
- mainCloseBtnAnimation: (isOpen: boolean) => {
148
+ mainCloseBtnAnimation: (isOpen: boolean, hideUntilHover: boolean) => {
134
149
  if (!isOpen) {
135
- return css`
136
- opacity: 1;
137
- pointer-events: auto;
138
- visibility: visible;
139
- `
150
+ return hideUntilHover
151
+ ? css`
152
+ opacity: 0;
153
+
154
+ &:hover {
155
+ opacity: 1;
156
+ pointer-events: auto;
157
+ visibility: visible;
158
+ }
159
+ `
160
+ : css`
161
+ opacity: 1;
162
+ pointer-events: auto;
163
+ visibility: visible;
164
+ `
140
165
  }
141
166
  return css`
142
167
  opacity: 0;
@@ -243,6 +268,225 @@ const stylesFactory = () => {
243
268
  height: 100%;
244
269
  overflow-y: auto;
245
270
  `,
271
+ selectWrapper: css`
272
+ width: 100%;
273
+ max-width: 300px;
274
+ display: flex;
275
+ flex-direction: column;
276
+ gap: 0.375rem;
277
+ `,
278
+ selectContainer: css`
279
+ width: 100%;
280
+ `,
281
+ selectLabel: css`
282
+ font-size: 0.875rem;
283
+ font-weight: 500;
284
+ color: ${colors.gray[100]};
285
+ `,
286
+ selectDescription: css`
287
+ font-size: 0.8rem;
288
+ color: ${colors.gray[400]};
289
+ margin: 0;
290
+ line-height: 1.3;
291
+ `,
292
+ select: css`
293
+ appearance: none;
294
+ width: 100%;
295
+ padding: 0.75rem 3rem 0.75rem 0.75rem;
296
+ border-radius: 0.5rem;
297
+ background-color: ${colors.darkGray[800]};
298
+ color: ${colors.gray[100]};
299
+ border: 1px solid ${colors.gray[700]};
300
+ font-size: 0.875rem;
301
+ transition: all 0.2s ease;
302
+ cursor: pointer;
303
+
304
+ /* Custom arrow */
305
+ background-image: url("data:image/svg+xml;utf8,<svg fill='%236b7280' height='20' viewBox='0 0 24 24' width='20' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/></svg>");
306
+ background-repeat: no-repeat;
307
+ background-position: right 0.75rem center;
308
+ background-size: 1.25rem;
309
+
310
+ &:hover {
311
+ border-color: ${colors.gray[600]};
312
+ }
313
+
314
+ &:focus {
315
+ outline: none;
316
+ border-color: ${colors.purple[400]};
317
+ box-shadow: 0 0 0 3px ${colors.purple[400]}${alpha[20]};
318
+ }
319
+ `,
320
+ inputWrapper: css`
321
+ width: 100%;
322
+ max-width: 300px;
323
+ display: flex;
324
+ flex-direction: column;
325
+ gap: 0.375rem;
326
+ `,
327
+ inputContainer: css`
328
+ width: 100%;
329
+ `,
330
+ inputLabel: css`
331
+ font-size: 0.875rem;
332
+ font-weight: 500;
333
+ color: ${colors.gray[100]};
334
+ `,
335
+ inputDescription: css`
336
+ font-size: 0.8rem;
337
+ color: ${colors.gray[400]};
338
+ margin: 0;
339
+ line-height: 1.3;
340
+ `,
341
+ input: css`
342
+ appearance: none;
343
+ width: 100%;
344
+ padding: 0.75rem;
345
+ border-radius: 0.5rem;
346
+ background-color: ${colors.darkGray[800]};
347
+ color: ${colors.gray[100]};
348
+ border: 1px solid ${colors.gray[700]};
349
+ font-size: 0.875rem;
350
+ font-family: ${fontFamily.mono};
351
+ transition: all 0.2s ease;
352
+
353
+ &::placeholder {
354
+ color: ${colors.gray[500]};
355
+ }
356
+
357
+ &:hover {
358
+ border-color: ${colors.gray[600]};
359
+ }
360
+
361
+ &:focus {
362
+ outline: none;
363
+ border-color: ${colors.purple[400]};
364
+ box-shadow: 0 0 0 3px ${colors.purple[400]}${alpha[20]};
365
+ }
366
+ `,
367
+ checkboxWrapper: css`
368
+ display: flex;
369
+ align-items: flex-start;
370
+ gap: 0.75rem;
371
+ cursor: pointer;
372
+ user-select: none;
373
+ padding: 0.5rem;
374
+ border-radius: 0.5rem;
375
+ transition: background-color 0.2s ease;
376
+
377
+ &:hover {
378
+ background-color: ${colors.darkGray[800]};
379
+ }
380
+ `,
381
+ checkboxContainer: css`
382
+ width: 100%;
383
+ `,
384
+ checkboxLabelContainer: css`
385
+ display: flex;
386
+ flex-direction: column;
387
+ gap: 0.25rem;
388
+ flex: 1;
389
+ `,
390
+ checkbox: css`
391
+ appearance: none;
392
+ width: 1.25rem;
393
+ height: 1.25rem;
394
+ border: 2px solid ${colors.gray[700]};
395
+ border-radius: 0.375rem;
396
+ background-color: ${colors.darkGray[800]};
397
+ display: grid;
398
+ place-items: center;
399
+ transition: all 0.2s ease;
400
+ flex-shrink: 0;
401
+ margin-top: 0.125rem;
402
+
403
+ &:hover {
404
+ border-color: ${colors.purple[400]};
405
+ }
406
+
407
+ &:checked {
408
+ background-color: ${colors.purple[500]};
409
+ border-color: ${colors.purple[500]};
410
+ }
411
+
412
+ &:checked::after {
413
+ content: '';
414
+ width: 0.4rem;
415
+ height: 0.6rem;
416
+ border: solid white;
417
+ border-width: 0 2px 2px 0;
418
+ transform: rotate(45deg);
419
+ margin-top: -3px;
420
+ }
421
+ `,
422
+ checkboxLabel: css`
423
+ color: ${colors.gray[100]};
424
+ font-size: 0.875rem;
425
+ font-weight: 500;
426
+ line-height: 1.4;
427
+ `,
428
+ checkboxDescription: css`
429
+ color: ${colors.gray[400]};
430
+ font-size: 0.8rem;
431
+ line-height: 1.3;
432
+ `,
433
+ settingsContainer: css`
434
+ padding: 1.5rem;
435
+ height: 100%;
436
+ overflow-y: auto;
437
+ background-color: ${colors.darkGray[700]};
438
+ `,
439
+ settingsSection: css`
440
+ margin-bottom: 2rem;
441
+ padding: 1.5rem;
442
+ background-color: ${colors.darkGray[800]};
443
+ border: 1px solid ${colors.gray[700]};
444
+ border-radius: 0.75rem;
445
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
446
+ `,
447
+ sectionTitle: css`
448
+ font-size: 1.125rem;
449
+ font-weight: 600;
450
+ color: ${colors.gray[100]};
451
+ margin: 0 0 1rem 0;
452
+ padding-bottom: 0.5rem;
453
+ border-bottom: 1px solid ${colors.gray[700]};
454
+ display: flex;
455
+ align-items: center;
456
+ gap: 0.5rem;
457
+ `,
458
+ sectionIcon: css`
459
+ color: ${colors.purple[400]};
460
+ `,
461
+ sectionDescription: css`
462
+ color: ${colors.gray[400]};
463
+ font-size: 0.875rem;
464
+ margin: 0 0 1.5rem 0;
465
+ line-height: 1.5;
466
+ `,
467
+ settingsGroup: css`
468
+ display: flex;
469
+ flex-direction: column;
470
+ gap: 1rem;
471
+ `,
472
+ conditionalSetting: css`
473
+ margin-left: 1.5rem;
474
+ padding-left: 1rem;
475
+ border-left: 2px solid ${colors.purple[400]};
476
+ background-color: ${colors.darkGray[800]};
477
+ padding: 1rem;
478
+ border-radius: 0.5rem;
479
+ margin-top: 0.5rem;
480
+ `,
481
+ settingRow: css`
482
+ display: grid;
483
+ grid-template-columns: 1fr 1fr;
484
+ gap: 1rem;
485
+
486
+ @media (max-width: 768px) {
487
+ grid-template-columns: 1fr;
488
+ }
489
+ `,
246
490
  }
247
491
  }
248
492
 
@@ -2,13 +2,15 @@ import { For, createEffect } from 'solid-js'
2
2
  import clsx from 'clsx'
3
3
  import { usePlugins } from '../context/use-devtools-context'
4
4
  import { useStyles } from '../styles/use-styles'
5
+ import { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from '../constants'
5
6
 
6
7
  export const PluginsTab = () => {
7
8
  const { plugins, activePlugin, setActivePlugin } = usePlugins()
8
9
  let activePluginRef: HTMLDivElement | undefined
10
+
9
11
  createEffect(() => {
10
12
  const currentActivePlugin = plugins()?.find(
11
- (plugin) => plugin.id === activePlugin()?.id,
13
+ (plugin) => plugin.id === activePlugin(),
12
14
  )
13
15
  if (activePluginRef && currentActivePlugin) {
14
16
  currentActivePlugin.render(activePluginRef)
@@ -30,18 +32,22 @@ export const PluginsTab = () => {
30
32
  })
31
33
  return (
32
34
  <div
33
- onClick={() => setActivePlugin(plugin)}
35
+ onClick={() => setActivePlugin(plugin.id!)}
34
36
  class={clsx(styles().pluginName, {
35
- active: activePlugin()?.id === plugin.id,
37
+ active: activePlugin() === plugin.id,
36
38
  })}
37
39
  >
38
- <h3 ref={pluginHeading} />
40
+ <h3 id={PLUGIN_TITLE_CONTAINER_ID} ref={pluginHeading} />
39
41
  </div>
40
42
  )
41
43
  }}
42
44
  </For>
43
45
  </div>
44
- <div ref={activePluginRef} class={styles().pluginsTabContent}></div>
46
+ <div
47
+ id={PLUGIN_CONTAINER_ID}
48
+ ref={activePluginRef}
49
+ class={styles().pluginsTabContent}
50
+ ></div>
45
51
  </div>
46
52
  )
47
53
  }
@@ -1,3 +1,219 @@
1
+ import { Show } from 'solid-js'
2
+ import { Input } from '../components/input'
3
+ import { Select } from '../components/select'
4
+ import { useDevtoolsSettings } from '../context/use-devtools-context'
5
+ import { uppercaseFirstLetter } from '../utils/sanitize'
6
+ import { Checkbox } from '../components/checkbox'
7
+ import { useStyles } from '../styles/use-styles'
8
+
1
9
  export const SettingsTab = () => {
2
- return <div>Settings</div>
10
+ const { setSettings, settings } = useDevtoolsSettings()
11
+ const styles = useStyles()
12
+
13
+ return (
14
+ <div class={styles().settingsContainer}>
15
+ {/* General Settings */}
16
+ <div class={styles().settingsSection}>
17
+ <h3 class={styles().sectionTitle}>
18
+ <svg
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ width="20"
21
+ height="20"
22
+ viewBox="0 0 24 24"
23
+ fill="none"
24
+ stroke="currentColor"
25
+ stroke-width="2"
26
+ stroke-linecap="round"
27
+ stroke-linejoin="round"
28
+ class={styles().sectionIcon}
29
+ >
30
+ <path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
31
+ <circle cx="12" cy="12" r="3" />
32
+ </svg>
33
+ General
34
+ </h3>
35
+ <p class={styles().sectionDescription}>
36
+ Configure general behavior of the devtools panel.
37
+ </p>
38
+ <div class={styles().settingsGroup}>
39
+ <Checkbox
40
+ label="Default open"
41
+ description="Automatically open the devtools panel when the page loads"
42
+ onChange={() =>
43
+ setSettings({ defaultOpen: !settings().defaultOpen })
44
+ }
45
+ checked={settings().defaultOpen}
46
+ />
47
+ <Checkbox
48
+ label="Hide trigger until hovered"
49
+ description="Keep the devtools trigger button hidden until you hover over its area"
50
+ onChange={() =>
51
+ setSettings({ hideUntilHover: !settings().hideUntilHover })
52
+ }
53
+ checked={settings().hideUntilHover}
54
+ />
55
+ </div>
56
+ </div>
57
+
58
+ {/* URL Flag Settings */}
59
+ <div class={styles().settingsSection}>
60
+ <h3 class={styles().sectionTitle}>
61
+ <svg
62
+ class={styles().sectionIcon}
63
+ xmlns="http://www.w3.org/2000/svg"
64
+ width="20"
65
+ height="20"
66
+ viewBox="0 0 24 24"
67
+ fill="none"
68
+ stroke="currentColor"
69
+ stroke-width="2"
70
+ stroke-linecap="round"
71
+ stroke-linejoin="round"
72
+ >
73
+ <path d="M9 17H7A5 5 0 0 1 7 7h2" />
74
+ <path d="M15 7h2a5 5 0 1 1 0 10h-2" />
75
+ <line x1="8" x2="16" y1="12" y2="12" />
76
+ </svg>
77
+ URL Configuration
78
+ </h3>
79
+ <p class={styles().sectionDescription}>
80
+ Control when devtools are available based on URL parameters.
81
+ </p>
82
+ <div class={styles().settingsGroup}>
83
+ <Checkbox
84
+ label="Require URL Flag"
85
+ description="Only show devtools when a specific URL parameter is present"
86
+ checked={settings().requireUrlFlag}
87
+ onChange={(checked) =>
88
+ setSettings({
89
+ requireUrlFlag: checked,
90
+ })
91
+ }
92
+ />
93
+ <Show when={settings().requireUrlFlag}>
94
+ <div class={styles().conditionalSetting}>
95
+ <Input
96
+ label="URL flag"
97
+ description="Enter the URL parameter name (e.g., 'debug' for ?debug=true)"
98
+ placeholder="debug"
99
+ value={settings().urlFlag}
100
+ onChange={(e) =>
101
+ setSettings({
102
+ urlFlag: e,
103
+ })
104
+ }
105
+ />
106
+ </div>
107
+ </Show>
108
+ </div>
109
+ </div>
110
+
111
+ {/* Keyboard Settings */}
112
+ <div class={styles().settingsSection}>
113
+ <h3 class={styles().sectionTitle}>
114
+ <svg
115
+ class={styles().sectionIcon}
116
+ xmlns="http://www.w3.org/2000/svg"
117
+ width="20"
118
+ height="20"
119
+ viewBox="0 0 24 24"
120
+ fill="none"
121
+ stroke="currentColor"
122
+ stroke-width="2"
123
+ stroke-linecap="round"
124
+ stroke-linejoin="round"
125
+ >
126
+ <path d="M10 8h.01" />
127
+ <path d="M12 12h.01" />
128
+ <path d="M14 8h.01" />
129
+ <path d="M16 12h.01" />
130
+ <path d="M18 8h.01" />
131
+ <path d="M6 8h.01" />
132
+ <path d="M7 16h10" />
133
+ <path d="M8 12h.01" />
134
+ <rect width="20" height="16" x="2" y="4" rx="2" />
135
+ </svg>
136
+ Keyboard
137
+ </h3>
138
+ <p class={styles().sectionDescription}>
139
+ Customize keyboard shortcuts for quick access.
140
+ </p>
141
+ <div class={styles().settingsGroup}>
142
+ <Input
143
+ label="Hotkey to open/close devtools"
144
+ description="Use '+' to combine keys (e.g., 'Ctrl+Shift+D' or 'Alt+D')"
145
+ placeholder="Ctrl+Shift+D"
146
+ value={settings().openHotkey.join('+')}
147
+ onChange={(e) =>
148
+ setSettings({
149
+ openHotkey: e
150
+ .split('+')
151
+ .map((key) => uppercaseFirstLetter(key))
152
+ .filter(Boolean),
153
+ })
154
+ }
155
+ />
156
+ </div>
157
+ </div>
158
+
159
+ {/* Position Settings */}
160
+ <div class={styles().settingsSection}>
161
+ <h3 class={styles().sectionTitle}>
162
+ <svg
163
+ class={styles().sectionIcon}
164
+ xmlns="http://www.w3.org/2000/svg"
165
+ width="20"
166
+ height="20"
167
+ viewBox="0 0 24 24"
168
+ fill="none"
169
+ stroke="currentColor"
170
+ stroke-width="2"
171
+ stroke-linecap="round"
172
+ stroke-linejoin="round"
173
+ >
174
+ <path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" />
175
+ <circle cx="12" cy="10" r="3" />
176
+ </svg>
177
+ Position
178
+ </h3>
179
+ <p class={styles().sectionDescription}>
180
+ Adjust the position of the trigger button and devtools panel.
181
+ </p>
182
+ <div class={styles().settingsGroup}>
183
+ <div class={styles().settingRow}>
184
+ <Select
185
+ label="Trigger Position"
186
+ options={[
187
+ { label: 'Bottom Right', value: 'bottom-right' },
188
+ { label: 'Bottom Left', value: 'bottom-left' },
189
+ { label: 'Top Right', value: 'top-right' },
190
+ { label: 'Top Left', value: 'top-left' },
191
+ { label: 'Middle Right', value: 'middle-right' },
192
+ { label: 'Middle Left', value: 'middle-left' },
193
+ ]}
194
+ value={settings().position}
195
+ onChange={(value) =>
196
+ setSettings({
197
+ position: value,
198
+ })
199
+ }
200
+ />
201
+ <Select
202
+ label="Panel Position"
203
+ value={settings().panelLocation}
204
+ options={[
205
+ { label: 'Top', value: 'top' },
206
+ { label: 'Bottom', value: 'bottom' },
207
+ ]}
208
+ onChange={(value) =>
209
+ setSettings({
210
+ panelLocation: value,
211
+ })
212
+ }
213
+ />
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ )
3
219
  }
@@ -6,3 +6,6 @@ export const tryParseJson = <T>(json: string | null): T | undefined => {
6
6
  return undefined
7
7
  }
8
8
  }
9
+
10
+ export const uppercaseFirstLetter = (value: string) =>
11
+ value.charAt(0).toUpperCase() + value.slice(1)