dr-widget 0.1.3__py3-none-any.whl

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 (127) hide show
  1. dr_widget/__init__.py +5 -0
  2. dr_widget/py.typed +0 -0
  3. dr_widget/widgets/__init__.py +5 -0
  4. dr_widget/widgets/config_file_manager/.gitignore +24 -0
  5. dr_widget/widgets/config_file_manager/.vscode/extensions.json +3 -0
  6. dr_widget/widgets/config_file_manager/README.md +89 -0
  7. dr_widget/widgets/config_file_manager/__init__.py +283 -0
  8. dr_widget/widgets/config_file_manager/components.json +16 -0
  9. dr_widget/widgets/config_file_manager/index.html +12 -0
  10. dr_widget/widgets/config_file_manager/jsrepo.json +18 -0
  11. dr_widget/widgets/config_file_manager/package.json +49 -0
  12. dr_widget/widgets/config_file_manager/postcss.config.js +6 -0
  13. dr_widget/widgets/config_file_manager/public/fonts/Inter-roman.var.woff2 +0 -0
  14. dr_widget/widgets/config_file_manager/public/vite.svg +1 -0
  15. dr_widget/widgets/config_file_manager/src/App.svelte +62 -0
  16. dr_widget/widgets/config_file_manager/src/ConfigFileManager.svelte +605 -0
  17. dr_widget/widgets/config_file_manager/src/app.css +134 -0
  18. dr_widget/widgets/config_file_manager/src/index.js +5 -0
  19. dr_widget/widgets/config_file_manager/src/lib/@test_state.json +20 -0
  20. dr_widget/widgets/config_file_manager/src/lib/Counter.svelte +10 -0
  21. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/BrowseConfigsPanel.svelte +137 -0
  22. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/ComplexJsonViewer.svelte +94 -0
  23. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/ConfigViewerPanel.svelte +282 -0
  24. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/LoadedConfigPreview.svelte +74 -0
  25. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SaveConfigPanel.svelte +449 -0
  26. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SelectedFileRow.svelte +38 -0
  27. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SelectedFilesList.svelte +30 -0
  28. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SimpleJsonViewer.svelte +405 -0
  29. dr_widget/widgets/config_file_manager/src/lib/components/ui/badge/badge.svelte +50 -0
  30. dr_widget/widgets/config_file_manager/src/lib/components/ui/badge/index.ts +2 -0
  31. dr_widget/widgets/config_file_manager/src/lib/components/ui/button/button.svelte +128 -0
  32. dr_widget/widgets/config_file_manager/src/lib/components/ui/button/index.ts +27 -0
  33. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-action.svelte +20 -0
  34. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-content.svelte +15 -0
  35. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-description.svelte +20 -0
  36. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-footer.svelte +20 -0
  37. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-header.svelte +23 -0
  38. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-title.svelte +20 -0
  39. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card.svelte +23 -0
  40. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/index.ts +25 -0
  41. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
  42. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-content.svelte +47 -0
  43. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-description.svelte +21 -0
  44. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-footer.svelte +24 -0
  45. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-header.svelte +24 -0
  46. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-overlay.svelte +24 -0
  47. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-title.svelte +21 -0
  48. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
  49. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/index.ts +41 -0
  50. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-close.svelte +11 -0
  51. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-content.svelte +41 -0
  52. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-description.svelte +21 -0
  53. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-footer.svelte +24 -0
  54. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-header.svelte +24 -0
  55. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-nested.svelte +16 -0
  56. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-overlay.svelte +24 -0
  57. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-title.svelte +21 -0
  58. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-trigger.svelte +11 -0
  59. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer.svelte +16 -0
  60. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/index.ts +45 -0
  61. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-content.svelte +23 -0
  62. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-description.svelte +23 -0
  63. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-header.svelte +20 -0
  64. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-media.svelte +41 -0
  65. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-title.svelte +20 -0
  66. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty.svelte +23 -0
  67. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/index.ts +22 -0
  68. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-content.svelte +20 -0
  69. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-description.svelte +25 -0
  70. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-error.svelte +58 -0
  71. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-group.svelte +23 -0
  72. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-label.svelte +26 -0
  73. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-legend.svelte +29 -0
  74. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-separator.svelte +38 -0
  75. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-set.svelte +24 -0
  76. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-title.svelte +23 -0
  77. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field.svelte +53 -0
  78. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/index.ts +33 -0
  79. dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/file-drop-zone.svelte +178 -0
  80. dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/index.ts +29 -0
  81. dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/types.ts +51 -0
  82. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/index.ts +34 -0
  83. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-actions.svelte +20 -0
  84. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-content.svelte +20 -0
  85. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-description.svelte +24 -0
  86. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-footer.svelte +20 -0
  87. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-group.svelte +21 -0
  88. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-header.svelte +20 -0
  89. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-media.svelte +42 -0
  90. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-separator.svelte +19 -0
  91. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-title.svelte +20 -0
  92. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item.svelte +60 -0
  93. dr_widget/widgets/config_file_manager/src/lib/components/ui/label/index.ts +7 -0
  94. dr_widget/widgets/config_file_manager/src/lib/components/ui/label/label.svelte +20 -0
  95. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/index.ts +13 -0
  96. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-content.svelte +29 -0
  97. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-description.svelte +20 -0
  98. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-footer.svelte +29 -0
  99. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-header.svelte +29 -0
  100. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-title.svelte +20 -0
  101. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-trigger.svelte +24 -0
  102. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal.svelte +24 -0
  103. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal.svelte.ts +32 -0
  104. dr_widget/widgets/config_file_manager/src/lib/components/ui/separator/index.ts +7 -0
  105. dr_widget/widgets/config_file_manager/src/lib/components/ui/separator/separator.svelte +21 -0
  106. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/index.ts +16 -0
  107. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  108. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-list.svelte +20 -0
  109. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-trigger.svelte +20 -0
  110. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs.svelte +19 -0
  111. dr_widget/widgets/config_file_manager/src/lib/hooks/use-file-bindings.ts +189 -0
  112. dr_widget/widgets/config_file_manager/src/lib/react/JsonTreeCanvas.tsx +207 -0
  113. dr_widget/widgets/config_file_manager/src/lib/utils/config-format.ts +113 -0
  114. dr_widget/widgets/config_file_manager/src/lib/utils/utils.ts +21 -0
  115. dr_widget/widgets/config_file_manager/src/lib/utils.ts +17 -0
  116. dr_widget/widgets/config_file_manager/src/main.js +7 -0
  117. dr_widget/widgets/config_file_manager/static/fonts/Inter-roman.var.woff2 +0 -0
  118. dr_widget/widgets/config_file_manager/static/index.js +9719 -0
  119. dr_widget/widgets/config_file_manager/static/style.css +1 -0
  120. dr_widget/widgets/config_file_manager/static/vite.svg +1 -0
  121. dr_widget/widgets/config_file_manager/svelte.config.js +8 -0
  122. dr_widget/widgets/config_file_manager/tailwind.config.js +12 -0
  123. dr_widget/widgets/config_file_manager/tsconfig.json +28 -0
  124. dr_widget/widgets/config_file_manager/vite.config.js +36 -0
  125. dr_widget-0.1.3.dist-info/METADATA +62 -0
  126. dr_widget-0.1.3.dist-info/RECORD +127 -0
  127. dr_widget-0.1.3.dist-info/WHEEL +4 -0
@@ -0,0 +1,41 @@
1
+ <script lang="ts" module>
2
+ import { tv, type VariantProps } from "tailwind-variants";
3
+
4
+ export const emptyMediaVariants = tv({
5
+ base: "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
6
+ variants: {
7
+ variant: {
8
+ default: "bg-transparent",
9
+ icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
10
+ },
11
+ },
12
+ defaultVariants: {
13
+ variant: "default",
14
+ },
15
+ });
16
+
17
+ export type EmptyMediaVariant = VariantProps<typeof emptyMediaVariants>["variant"];
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import { cn, type WithElementRef } from "$lib/utils.js";
22
+ import type { HTMLAttributes } from "svelte/elements";
23
+
24
+ let {
25
+ ref = $bindable(null),
26
+ class: className,
27
+ children,
28
+ variant = "default",
29
+ ...restProps
30
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { variant?: EmptyMediaVariant } = $props();
31
+ </script>
32
+
33
+ <div
34
+ bind:this={ref}
35
+ data-slot="empty-icon"
36
+ data-variant={variant}
37
+ class={cn(emptyMediaVariants({ variant }), className)}
38
+ {...restProps}
39
+ >
40
+ {@render children?.()}
41
+ </div>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="empty-title"
16
+ class={cn("text-lg font-medium tracking-tight", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </div>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="empty"
16
+ class={cn(
17
+ "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,22 @@
1
+ import Root from "./empty.svelte";
2
+ import Header from "./empty-header.svelte";
3
+ import Media from "./empty-media.svelte";
4
+ import Title from "./empty-title.svelte";
5
+ import Description from "./empty-description.svelte";
6
+ import Content from "./empty-content.svelte";
7
+
8
+ export {
9
+ Root,
10
+ Header,
11
+ Media,
12
+ Title,
13
+ Description,
14
+ Content,
15
+ //
16
+ Root as Empty,
17
+ Header as EmptyHeader,
18
+ Media as EmptyMedia,
19
+ Title as EmptyTitle,
20
+ Description as EmptyDescription,
21
+ Content as EmptyContent,
22
+ };
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="field-content"
16
+ class={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </div>
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
11
+ </script>
12
+
13
+ <p
14
+ bind:this={ref}
15
+ data-slot="field-description"
16
+ class={cn(
17
+ "text-muted-foreground text-sm font-normal leading-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
18
+ "nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5",
19
+ "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
20
+ className
21
+ )}
22
+ {...restProps}
23
+ >
24
+ {@render children?.()}
25
+ </p>
@@ -0,0 +1,58 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import type { Snippet } from "svelte";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ children,
10
+ errors,
11
+ ...restProps
12
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
13
+ children?: Snippet;
14
+ errors?: { message?: string }[];
15
+ } = $props();
16
+
17
+ const hasContent = $derived.by(() => {
18
+ // has slotted error
19
+ if (children) return true;
20
+
21
+ // no errors
22
+ if (!errors) return false;
23
+
24
+ // has an error but no message
25
+ if (errors.length === 1 && !errors[0]?.message) {
26
+ return false;
27
+ }
28
+
29
+ return true;
30
+ });
31
+
32
+ const isMultipleErrors = $derived(errors && errors.length > 1);
33
+ const singleErrorMessage = $derived(errors && errors.length === 1 && errors[0]?.message);
34
+ </script>
35
+
36
+ {#if hasContent}
37
+ <div
38
+ bind:this={ref}
39
+ role="alert"
40
+ data-slot="field-error"
41
+ class={cn("text-destructive text-sm font-normal", className)}
42
+ {...restProps}
43
+ >
44
+ {#if children}
45
+ {@render children()}
46
+ {:else if singleErrorMessage}
47
+ {singleErrorMessage}
48
+ {:else if isMultipleErrors}
49
+ <ul class="ml-4 flex list-disc flex-col gap-1">
50
+ {#each errors ?? [] as error, index (index)}
51
+ {#if error?.message}
52
+ <li>{error.message}</li>
53
+ {/if}
54
+ {/each}
55
+ </ul>
56
+ {/if}
57
+ </div>
58
+ {/if}
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="field-group"
16
+ class={cn(
17
+ "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import { Label } from "$lib/components/ui/label/index.js";
3
+ import { cn } from "$lib/utils.js";
4
+ import type { ComponentProps } from "svelte";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ children,
10
+ ...restProps
11
+ }: ComponentProps<typeof Label> = $props();
12
+ </script>
13
+
14
+ <Label
15
+ bind:ref
16
+ data-slot="field-label"
17
+ class={cn(
18
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
19
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
20
+ "has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
21
+ className
22
+ )}
23
+ {...restProps}
24
+ >
25
+ {@render children?.()}
26
+ </Label>
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ variant = "legend",
9
+ children,
10
+ ...restProps
11
+ }: WithElementRef<HTMLAttributes<HTMLLegendElement>> & {
12
+ variant?: "legend" | "label";
13
+ } = $props();
14
+ </script>
15
+
16
+ <legend
17
+ bind:this={ref}
18
+ data-slot="field-legend"
19
+ data-variant={variant}
20
+ class={cn(
21
+ "mb-3 font-medium",
22
+ "data-[variant=legend]:text-base",
23
+ "data-[variant=label]:text-sm",
24
+ className
25
+ )}
26
+ {...restProps}
27
+ >
28
+ {@render children?.()}
29
+ </legend>
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { Separator } from "$lib/components/ui/separator/index.js";
3
+ import { cn, type WithElementRef } from "$lib/utils.js";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+ import type { Snippet } from "svelte";
6
+
7
+ let {
8
+ ref = $bindable(null),
9
+ class: className,
10
+ children,
11
+ ...restProps
12
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
13
+ children?: Snippet;
14
+ } = $props();
15
+
16
+ const hasContent = $derived(!!children);
17
+ </script>
18
+
19
+ <div
20
+ bind:this={ref}
21
+ data-slot="field-separator"
22
+ data-content={hasContent}
23
+ class={cn(
24
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
25
+ className
26
+ )}
27
+ {...restProps}
28
+ >
29
+ <Separator class="absolute inset-0 top-1/2" />
30
+ {#if children}
31
+ <span
32
+ class="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
33
+ data-slot="field-separator-content"
34
+ >
35
+ {@render children()}
36
+ </span>
37
+ {/if}
38
+ </div>
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLFieldsetAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLFieldsetAttributes> = $props();
11
+ </script>
12
+
13
+ <fieldset
14
+ bind:this={ref}
15
+ data-slot="field-set"
16
+ class={cn(
17
+ "flex flex-col gap-6",
18
+ "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
19
+ className
20
+ )}
21
+ {...restProps}
22
+ >
23
+ {@render children?.()}
24
+ </fieldset>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="field-title"
16
+ class={cn(
17
+ "flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,53 @@
1
+ <script lang="ts" module>
2
+ import { tv, type VariantProps } from "tailwind-variants";
3
+
4
+ export const fieldVariants = tv({
5
+ base: "group/field data-[invalid=true]:text-destructive flex w-full gap-3",
6
+ variants: {
7
+ orientation: {
8
+ vertical: "flex-col [&>*]:w-full [&>.sr-only]:w-auto",
9
+ horizontal: [
10
+ "flex-row items-center",
11
+ "[&>[data-slot=field-label]]:flex-auto",
12
+ "has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start",
13
+ ],
14
+ responsive: [
15
+ "@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto",
16
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
17
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
18
+ ],
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ orientation: "vertical",
23
+ },
24
+ });
25
+
26
+ export type FieldOrientation = VariantProps<typeof fieldVariants>["orientation"];
27
+ </script>
28
+
29
+ <script lang="ts">
30
+ import { cn, type WithElementRef } from "$lib/utils.js";
31
+ import type { HTMLAttributes } from "svelte/elements";
32
+
33
+ let {
34
+ ref = $bindable(null),
35
+ class: className,
36
+ orientation = "vertical",
37
+ children,
38
+ ...restProps
39
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
40
+ orientation?: FieldOrientation;
41
+ } = $props();
42
+ </script>
43
+
44
+ <div
45
+ bind:this={ref}
46
+ role="group"
47
+ data-slot="field"
48
+ data-orientation={orientation}
49
+ class={cn(fieldVariants({ orientation }), className)}
50
+ {...restProps}
51
+ >
52
+ {@render children?.()}
53
+ </div>
@@ -0,0 +1,33 @@
1
+ import Field from "./field.svelte";
2
+ import Set from "./field-set.svelte";
3
+ import Legend from "./field-legend.svelte";
4
+ import Group from "./field-group.svelte";
5
+ import Content from "./field-content.svelte";
6
+ import Label from "./field-label.svelte";
7
+ import Title from "./field-title.svelte";
8
+ import Description from "./field-description.svelte";
9
+ import Separator from "./field-separator.svelte";
10
+ import Error from "./field-error.svelte";
11
+
12
+ export {
13
+ Field,
14
+ Set,
15
+ Legend,
16
+ Group,
17
+ Content,
18
+ Label,
19
+ Title,
20
+ Description,
21
+ Separator,
22
+ Error,
23
+ //
24
+ Set as FieldSet,
25
+ Legend as FieldLegend,
26
+ Group as FieldGroup,
27
+ Content as FieldContent,
28
+ Label as FieldLabel,
29
+ Title as FieldTitle,
30
+ Description as FieldDescription,
31
+ Separator as FieldSeparator,
32
+ Error as FieldError,
33
+ };
@@ -0,0 +1,178 @@
1
+ <!--
2
+ Installed from @ieedan/shadcn-svelte-extras
3
+ -->
4
+
5
+ <script lang="ts">
6
+ import { cn } from '../../../utils/utils';
7
+ import UploadIcon from '@lucide/svelte/icons/upload';
8
+ import { displaySize } from '.';
9
+ import { useId } from 'bits-ui';
10
+ import type { FileDropZoneProps, FileRejectedReason } from './types';
11
+
12
+ let {
13
+ id = useId(),
14
+ children,
15
+ maxFiles,
16
+ maxFileSize,
17
+ fileCount,
18
+ disabled = false,
19
+ onUpload,
20
+ onFileRejected,
21
+ accept,
22
+ class: className,
23
+ ...rest
24
+ }: FileDropZoneProps = $props();
25
+
26
+ if (maxFiles !== undefined && fileCount === undefined) {
27
+ console.warn(
28
+ 'Make sure to provide FileDropZone with `fileCount` when using the `maxFiles` prompt'
29
+ );
30
+ }
31
+
32
+ let uploading = $state(false);
33
+
34
+ const drop = async (
35
+ e: DragEvent & {
36
+ currentTarget: EventTarget & HTMLLabelElement;
37
+ }
38
+ ) => {
39
+ if (disabled || !canUploadFiles) return;
40
+
41
+ e.preventDefault();
42
+
43
+ const droppedFiles = Array.from(e.dataTransfer?.files ?? []);
44
+
45
+ await upload(droppedFiles);
46
+ };
47
+
48
+ const change = async (
49
+ e: Event & {
50
+ currentTarget: EventTarget & HTMLInputElement;
51
+ }
52
+ ) => {
53
+ if (disabled) return;
54
+
55
+ const selectedFiles = e.currentTarget.files;
56
+
57
+ if (!selectedFiles) return;
58
+
59
+ await upload(Array.from(selectedFiles));
60
+
61
+ // this if a file fails and we upload the same file again we still get feedback
62
+ if (e.target as HTMLInputElement) {
63
+ (e.target as HTMLInputElement).value = '';
64
+ }
65
+ };
66
+
67
+ const shouldAcceptFile = (file: File, fileNumber: number): FileRejectedReason | undefined => {
68
+ if (maxFileSize !== undefined && file.size > maxFileSize) return 'Maximum file size exceeded';
69
+
70
+ if (maxFiles !== undefined && fileNumber > maxFiles) return 'Maximum files uploaded';
71
+
72
+ if (!accept) return undefined;
73
+
74
+ const acceptedTypes = accept.split(',').map((a) => a.trim().toLowerCase());
75
+ const fileType = file.type.toLowerCase();
76
+ const fileName = file.name.toLowerCase();
77
+
78
+ const isAcceptable = acceptedTypes.some((pattern) => {
79
+ // check extension like .mp4
80
+ if (fileType.startsWith('.')) {
81
+ return fileName.endsWith(pattern);
82
+ }
83
+
84
+ // if pattern has wild card like video/*
85
+ if (pattern.endsWith('/*')) {
86
+ const baseType = pattern.slice(0, pattern.indexOf('/*'));
87
+ return fileType.startsWith(baseType + '/');
88
+ }
89
+
90
+ // otherwise it must be a specific type like video/mp4
91
+ return fileType === pattern;
92
+ });
93
+
94
+ if (!isAcceptable) return 'File type not allowed';
95
+
96
+ return undefined;
97
+ };
98
+
99
+ const upload = async (uploadFiles: File[]) => {
100
+ uploading = true;
101
+
102
+ const validFiles: File[] = [];
103
+
104
+ for (let i = 0; i < uploadFiles.length; i++) {
105
+ const file = uploadFiles[i];
106
+
107
+ const rejectedReason = shouldAcceptFile(file, (fileCount ?? 0) + i + 1);
108
+
109
+ if (rejectedReason) {
110
+ onFileRejected?.({ file, reason: rejectedReason });
111
+ continue;
112
+ }
113
+
114
+ validFiles.push(file);
115
+ }
116
+
117
+ await onUpload(validFiles);
118
+
119
+ uploading = false;
120
+ };
121
+
122
+ const canUploadFiles = $derived(
123
+ !disabled &&
124
+ !uploading &&
125
+ !(maxFiles !== undefined && fileCount !== undefined && fileCount >= maxFiles)
126
+ );
127
+ </script>
128
+
129
+ <label
130
+ ondragover={(e) => e.preventDefault()}
131
+ ondrop={drop}
132
+ for={id}
133
+ aria-disabled={!canUploadFiles}
134
+ class={cn(
135
+ 'border-border hover:bg-accent/25 flex h-48 w-full place-items-center justify-center rounded-lg border-2 border-dashed p-6 transition-all hover:cursor-pointer aria-disabled:opacity-50 aria-disabled:hover:cursor-not-allowed',
136
+ className
137
+ )}
138
+ >
139
+ {#if children}
140
+ {@render children()}
141
+ {:else}
142
+ <div class="flex flex-col place-items-center justify-center gap-2">
143
+ <div
144
+ class="border-border text-muted-foreground flex size-14 place-items-center justify-center rounded-full border border-dashed"
145
+ >
146
+ <UploadIcon class="size-7" />
147
+ </div>
148
+ <div class="flex flex-col gap-0.5 text-center">
149
+ <span class="text-muted-foreground font-medium">
150
+ Drag 'n' drop files here, or click to select files
151
+ </span>
152
+ {#if maxFiles || maxFileSize}
153
+ <span class="text-muted-foreground/75 text-sm">
154
+ {#if maxFiles}
155
+ <span>You can upload {maxFiles} files</span>
156
+ {/if}
157
+ {#if maxFiles && maxFileSize}
158
+ <span>(up to {displaySize(maxFileSize)} each)</span>
159
+ {/if}
160
+ {#if maxFileSize && !maxFiles}
161
+ <span>Maximum size {displaySize(maxFileSize)}</span>
162
+ {/if}
163
+ </span>
164
+ {/if}
165
+ </div>
166
+ </div>
167
+ {/if}
168
+ <input
169
+ {...rest}
170
+ disabled={!canUploadFiles}
171
+ {id}
172
+ {accept}
173
+ multiple={maxFiles === undefined || maxFiles - (fileCount ?? 0) > 1}
174
+ type="file"
175
+ onchange={change}
176
+ class="hidden"
177
+ />
178
+ </label>
@@ -0,0 +1,29 @@
1
+ /*
2
+ Installed from @ieedan/shadcn-svelte-extras
3
+ */
4
+
5
+ import FileDropZone from "./file-drop-zone.svelte";
6
+ import { type FileRejectedReason, type FileDropZoneProps } from "./types";
7
+
8
+ export const displaySize = (bytes: number): string => {
9
+ if (bytes < KILOBYTE) return `${bytes.toFixed(0)} B`;
10
+
11
+ if (bytes < MEGABYTE) return `${(bytes / KILOBYTE).toFixed(0)} KB`;
12
+
13
+ if (bytes < GIGABYTE) return `${(bytes / MEGABYTE).toFixed(0)} MB`;
14
+
15
+ return `${(bytes / GIGABYTE).toFixed(0)} GB`;
16
+ };
17
+
18
+ // Utilities for working with file sizes
19
+ export const BYTE = 1;
20
+ export const KILOBYTE = 1024;
21
+ export const MEGABYTE = 1024 * KILOBYTE;
22
+ export const GIGABYTE = 1024 * MEGABYTE;
23
+
24
+ // utilities for limiting accepted files
25
+ export const ACCEPT_IMAGE = "image/*";
26
+ export const ACCEPT_VIDEO = "video/*";
27
+ export const ACCEPT_AUDIO = "audio/*";
28
+
29
+ export { FileDropZone, type FileRejectedReason, type FileDropZoneProps };