@riducms/admin 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.
- package/LICENSE +21 -0
- package/README.md +31 -0
- package/THIRD_PARTY_NOTICES.md +217 -0
- package/package.json +62 -0
- package/src/app/admin-core-view-route.svelte +89 -0
- package/src/app/admin-layout.svelte +457 -0
- package/src/app/admin-navigation-progress.svelte +204 -0
- package/src/app/admin-page-title.svelte +94 -0
- package/src/app/admin-provider-layer.svelte +26 -0
- package/src/app/admin-router-root.svelte +9 -0
- package/src/app/admin-routes.svelte +116 -0
- package/src/app/deferred-redirect.svelte +23 -0
- package/src/app/loading-boundary.svelte +70 -0
- package/src/app/not-found-route.svelte +29 -0
- package/src/app-meta.ts +2 -0
- package/src/app.css +467 -0
- package/src/app.svelte +88 -0
- package/src/assets/ridu-logo-arch.svg +3 -0
- package/src/assets/ridu-word-logo.svg +7 -0
- package/src/components/brand/ridu-logo.svelte +24 -0
- package/src/components/code-block/code-block.svelte +107 -0
- package/src/components/json-tree/json-tree-node.svelte +77 -0
- package/src/components/json-tree/json-viewer.svelte +113 -0
- package/src/components/ui/banner/banner.svelte +41 -0
- package/src/components/ui/banner/index.ts +5 -0
- package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +50 -0
- package/src/components/ui/bulk-action-bar/index.ts +1 -0
- package/src/components/ui/button/index.ts +8 -0
- package/src/components/ui/calendar/calendar-caption.svelte +72 -0
- package/src/components/ui/calendar/calendar-cell.svelte +20 -0
- package/src/components/ui/calendar/calendar-day.svelte +27 -0
- package/src/components/ui/calendar/calendar-grid-row.svelte +13 -0
- package/src/components/ui/calendar/calendar-grid.svelte +17 -0
- package/src/components/ui/calendar/calendar-head-cell.svelte +20 -0
- package/src/components/ui/calendar/calendar-header.svelte +20 -0
- package/src/components/ui/calendar/calendar-heading.svelte +17 -0
- package/src/components/ui/calendar/calendar-month-select.svelte +45 -0
- package/src/components/ui/calendar/calendar-month.svelte +16 -0
- package/src/components/ui/calendar/calendar-months.svelte +20 -0
- package/src/components/ui/calendar/calendar-nav.svelte +20 -0
- package/src/components/ui/calendar/calendar-next-button.svelte +30 -0
- package/src/components/ui/calendar/calendar-prev-button.svelte +30 -0
- package/src/components/ui/calendar/calendar-year-select.svelte +44 -0
- package/src/components/ui/calendar/calendar.svelte +118 -0
- package/src/components/ui/calendar/index.ts +1 -0
- package/src/components/ui/checkbox/checkbox.svelte +35 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/confirmation-dialog/confirmation-dialog.svelte +37 -0
- package/src/components/ui/confirmation-dialog/index.ts +1 -0
- package/src/components/ui/date-value-control/date-value-calendar.svelte +37 -0
- package/src/components/ui/date-value-control/date-value-control-loader.svelte +36 -0
- package/src/components/ui/date-value-control/date-value-control.svelte +177 -0
- package/src/components/ui/date-value-control/index.ts +3 -0
- package/src/components/ui/dialog/dialog-close.svelte +11 -0
- package/src/components/ui/dialog/dialog-content.svelte +52 -0
- package/src/components/ui/dialog/dialog-description.svelte +20 -0
- package/src/components/ui/dialog/dialog-footer.svelte +20 -0
- package/src/components/ui/dialog/dialog-header.svelte +20 -0
- package/src/components/ui/dialog/dialog-overlay.svelte +20 -0
- package/src/components/ui/dialog/dialog-title.svelte +17 -0
- package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/src/components/ui/dialog/dialog.svelte +7 -0
- package/src/components/ui/dialog/index.ts +8 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +27 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +20 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +16 -0
- package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/src/components/ui/dropdown-menu/index.ts +5 -0
- package/src/components/ui/empty-state/empty-state.svelte +34 -0
- package/src/components/ui/empty-state/index.ts +1 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/kbd/index.ts +1 -0
- package/src/components/ui/kbd/kbd.svelte +24 -0
- package/src/components/ui/pagination/index.ts +1 -0
- package/src/components/ui/pagination/pagination.svelte +88 -0
- package/src/components/ui/popover/index.ts +3 -0
- package/src/components/ui/popover/popover-content.svelte +29 -0
- package/src/components/ui/popover/popover-trigger.svelte +11 -0
- package/src/components/ui/popover/popover.svelte +7 -0
- package/src/components/ui/radio-group/index.ts +3 -0
- package/src/components/ui/radio-group/radio-card-item.svelte +38 -0
- package/src/components/ui/radio-group/radio-group-item.svelte +17 -0
- package/src/components/ui/radio-group/radio-group.svelte +19 -0
- package/src/components/ui/select/index.ts +4 -0
- package/src/components/ui/select/select-content.svelte +38 -0
- package/src/components/ui/select/select-item.svelte +40 -0
- package/src/components/ui/select/select-scroll-down-button.svelte +23 -0
- package/src/components/ui/select/select-scroll-up-button.svelte +23 -0
- package/src/components/ui/select/select-trigger.svelte +29 -0
- package/src/components/ui/select/select.svelte +11 -0
- package/src/components/ui/sheet/index.ts +6 -0
- package/src/components/ui/sheet/sheet-close.svelte +7 -0
- package/src/components/ui/sheet/sheet-content.svelte +77 -0
- package/src/components/ui/sheet/sheet-description.svelte +17 -0
- package/src/components/ui/sheet/sheet-overlay.svelte +36 -0
- package/src/components/ui/sheet/sheet-title.svelte +17 -0
- package/src/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/src/components/ui/sheet/sheet.svelte +7 -0
- package/src/components/ui/skeleton/index.ts +1 -0
- package/src/components/ui/skeleton/skeleton.svelte +31 -0
- package/src/components/ui/slider/index.ts +3 -0
- package/src/components/ui/slider/slider-content.svelte +59 -0
- package/src/components/ui/slider/slider.svelte +35 -0
- package/src/components/ui/sonner/index.ts +1 -0
- package/src/components/ui/sonner/sonner.svelte +55 -0
- package/src/components/ui/status-indicator/index.ts +5 -0
- package/src/components/ui/status-indicator/status-indicator.svelte +54 -0
- package/src/components/ui/table/index.ts +6 -0
- package/src/components/ui/table/table-body.svelte +20 -0
- package/src/components/ui/table/table-cell.svelte +23 -0
- package/src/components/ui/table/table-head.svelte +23 -0
- package/src/components/ui/table/table-header.svelte +20 -0
- package/src/components/ui/table/table-row.svelte +23 -0
- package/src/components/ui/table/table.svelte +22 -0
- package/src/components/ui/tabs/index.ts +8 -0
- package/src/components/ui/tabs/tabs-content.svelte +17 -0
- package/src/components/ui/tabs/tabs-list.svelte +40 -0
- package/src/components/ui/tabs/tabs-trigger.svelte +23 -0
- package/src/components/ui/tabs/tabs.svelte +19 -0
- package/src/components/ui/textarea/index.ts +1 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/components/ui/tooltip/tooltip-content.svelte +50 -0
- package/src/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/src/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/src/components/ui/tooltip/tooltip.svelte +7 -0
- package/src/core/api/admin-client.ts +45 -0
- package/src/core/forms/field-condition.ts +63 -0
- package/src/core/forms/form-controller.svelte.ts +369 -0
- package/src/core/forms/form-draft-recovery.ts +80 -0
- package/src/core/forms/form-schema.ts +609 -0
- package/src/core/forms/form-validation.ts +750 -0
- package/src/core/i18n/admin-i18n.svelte.ts +364 -0
- package/src/core/i18n/localized-schema.ts +111 -0
- package/src/core/notifications/notification-center.svelte.ts +69 -0
- package/src/core/notifications/validation-toast.svelte +18 -0
- package/src/core/plugins/field-registry.ts +60 -0
- package/src/core/plugins/row-label-registry.ts +62 -0
- package/src/core/preferences/preference-write-queue.ts +107 -0
- package/src/core/routing/admin-paths.ts +137 -0
- package/src/core/runtime/admin-runtime.svelte.ts +625 -0
- package/src/features/auth/account-menu.svelte +159 -0
- package/src/features/auth/account-navigation.svelte +44 -0
- package/src/features/auth/account-route.svelte +395 -0
- package/src/features/auth/account-security-controller.svelte.ts +119 -0
- package/src/features/auth/account-security-route.svelte +460 -0
- package/src/features/auth/auth-brand.svelte +17 -0
- package/src/features/auth/auth-frame.svelte +18 -0
- package/src/features/auth/create-first-user-controller.svelte.ts +220 -0
- package/src/features/auth/create-first-user-route.svelte +174 -0
- package/src/features/auth/forgot-password-route.svelte +78 -0
- package/src/features/auth/login-route.svelte +221 -0
- package/src/features/auth/request-verification-route.svelte +73 -0
- package/src/features/auth/reset-password-route.svelte +107 -0
- package/src/features/auth/verify-email-route.svelte +60 -0
- package/src/features/collections/collection-list-bulk-actions.svelte +143 -0
- package/src/features/collections/collection-list-bulk-editor.svelte +157 -0
- package/src/features/collections/collection-list-controller.svelte.ts +717 -0
- package/src/features/collections/collection-list-filter-popover.svelte +238 -0
- package/src/features/collections/collection-list-preferences-controller.svelte.ts +393 -0
- package/src/features/collections/collection-list-results.svelte +410 -0
- package/src/features/collections/collection-list-route.svelte +951 -0
- package/src/features/collections/collection-list-workspace-toolbar.svelte +409 -0
- package/src/features/collections/collection-table-skeleton.svelte +44 -0
- package/src/features/collections/list-workspace.ts +265 -0
- package/src/features/dashboard/dashboard-route.svelte +141 -0
- package/src/features/development/development-tools.svelte +217 -0
- package/src/features/documents/document-api-view-loader.svelte +31 -0
- package/src/features/documents/document-api-view.svelte +547 -0
- package/src/features/documents/document-controller.svelte.ts +1252 -0
- package/src/features/documents/document-field-partition.ts +18 -0
- package/src/features/documents/document-field-sections.svelte +46 -0
- package/src/features/documents/document-heading.ts +20 -0
- package/src/features/documents/document-route-dirty.ts +43 -0
- package/src/features/documents/document-route-loader.svelte +22 -0
- package/src/features/documents/document-route.svelte +1124 -0
- package/src/features/documents/live-preview-panel.svelte +390 -0
- package/src/features/documents/live-preview.ts +61 -0
- package/src/features/documents/upload-document-preview-loader.svelte +40 -0
- package/src/features/documents/upload-document-preview.svelte +525 -0
- package/src/features/localization/content-locale-switcher.svelte +93 -0
- package/src/features/navigation/admin-breadcrumbs.svelte +124 -0
- package/src/features/navigation/admin-command-menu.svelte +245 -0
- package/src/features/navigation/admin-resource-groups.ts +72 -0
- package/src/features/reference-browser/reference-browser-loader.svelte +15 -0
- package/src/features/reference-browser/reference-browser-workflow.svelte.ts +691 -0
- package/src/features/reference-browser/reference-browser.svelte +844 -0
- package/src/features/uploads/bulk-upload-route.svelte +624 -0
- package/src/features/uploads/bulk-upload.ts +39 -0
- package/src/features/uploads/upload-metadata-fields.svelte +112 -0
- package/src/features/versions/version-diff.ts +72 -0
- package/src/features/versions/version-history-route.svelte +581 -0
- package/src/fields/field-clipboard.ts +132 -0
- package/src/fields/field-layout.svelte +60 -0
- package/src/fields/field-layout.ts +57 -0
- package/src/fields/field-messages.svelte +29 -0
- package/src/fields/field-renderer.svelte +152 -0
- package/src/fields/field-shell.svelte +30 -0
- package/src/fields/field-tab-layout.svelte +99 -0
- package/src/fields/join/join-field.svelte +358 -0
- package/src/fields/localized-field-presentation.ts +17 -0
- package/src/fields/nested/nested-field.svelte +602 -0
- package/src/fields/nested/scoped-field.ts +78 -0
- package/src/fields/nested/sortable-row.svelte +32 -0
- package/src/fields/point/point-field.svelte +52 -0
- package/src/fields/radio/radio-field.svelte +34 -0
- package/src/fields/relationship/relationship-field-controller.svelte.ts +245 -0
- package/src/fields/relationship/relationship-field.svelte +258 -0
- package/src/fields/relationship/relationship-lookup-controller.svelte.ts +129 -0
- package/src/fields/relationship/relationship-query.ts +88 -0
- package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +198 -0
- package/src/fields/relationship/relationship-quick-picker.svelte +327 -0
- package/src/fields/relationship/relationship-value.ts +47 -0
- package/src/fields/scalar/date-control-value.ts +60 -0
- package/src/fields/scalar/date-value.ts +26 -0
- package/src/fields/scalar/scalar-field.svelte +137 -0
- package/src/fields/select/select-field.svelte +132 -0
- package/src/fields/select/select-value.ts +14 -0
- package/src/fields/text/slug-field.svelte +87 -0
- package/src/fields/text/slug.ts +38 -0
- package/src/fields/text/text-field.svelte +41 -0
- package/src/fields/ui/ui-field.svelte +12 -0
- package/src/fields/virtual/virtual-field.svelte +28 -0
- package/src/index.ts +49 -0
- package/src/main.ts +12 -0
- package/src/vite-env.d.ts +9 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type Side = "top" | "right" | "bottom" | "left";
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { Dialog as SheetPrimitive } from "bits-ui";
|
|
7
|
+
import XIcon from "~icons/lucide/x";
|
|
8
|
+
import { getAdminI18n } from "@riducms/plugin";
|
|
9
|
+
import { buttonVariants } from "@admin/components/ui/button/index.js";
|
|
10
|
+
import { cn, type WithoutChildrenOrChild } from "@riducms/ui";
|
|
11
|
+
import SheetOverlay from "@admin/components/ui/sheet/sheet-overlay.svelte";
|
|
12
|
+
import type { Snippet } from "svelte";
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
ref = $bindable(null),
|
|
16
|
+
class: className,
|
|
17
|
+
side = "right",
|
|
18
|
+
showCloseButton = true,
|
|
19
|
+
portalProps,
|
|
20
|
+
children,
|
|
21
|
+
...restProps
|
|
22
|
+
}: WithoutChildrenOrChild<SheetPrimitive.ContentProps> & {
|
|
23
|
+
portalProps?: WithoutChildrenOrChild<SheetPrimitive.PortalProps>;
|
|
24
|
+
side?: Side;
|
|
25
|
+
showCloseButton?: boolean;
|
|
26
|
+
children: Snippet;
|
|
27
|
+
} = $props();
|
|
28
|
+
const i18n = getAdminI18n();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<SheetPrimitive.Portal {...portalProps}>
|
|
32
|
+
<SheetOverlay />
|
|
33
|
+
<SheetPrimitive.Content
|
|
34
|
+
bind:ref
|
|
35
|
+
data-slot="sheet-content"
|
|
36
|
+
data-side={side}
|
|
37
|
+
class={cn(
|
|
38
|
+
"fixed z-50 flex flex-col bg-popover bg-clip-padding text-sm text-popover-foreground shadow-xl data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[side=bottom]:data-[state=open]:slide-in-from-bottom data-[side=left]:data-[state=open]:slide-in-from-left data-[side=right]:data-[state=open]:slide-in-from-right data-[side=top]:data-[state=open]:slide-in-from-top data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[side=bottom]:data-[state=closed]:slide-out-to-bottom data-[side=left]:data-[state=closed]:slide-out-to-left data-[side=right]:data-[state=closed]:slide-out-to-right data-[side=top]:data-[state=closed]:slide-out-to-top",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...restProps}
|
|
42
|
+
>
|
|
43
|
+
{@render children?.()}
|
|
44
|
+
{#if showCloseButton}
|
|
45
|
+
<SheetPrimitive.Close
|
|
46
|
+
type="button"
|
|
47
|
+
data-slot="sheet-close"
|
|
48
|
+
class={buttonVariants({
|
|
49
|
+
variant: "ghost",
|
|
50
|
+
size: "icon-sm",
|
|
51
|
+
class: "absolute top-4 end-4 bg-secondary",
|
|
52
|
+
})}
|
|
53
|
+
>
|
|
54
|
+
<XIcon />
|
|
55
|
+
<span class="sr-only">{i18n.t("general:close")}</span>
|
|
56
|
+
</SheetPrimitive.Close>
|
|
57
|
+
{/if}
|
|
58
|
+
</SheetPrimitive.Content>
|
|
59
|
+
</SheetPrimitive.Portal>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
:global([data-slot="sheet-content"][data-state="open"]) {
|
|
63
|
+
animation-duration: 300ms;
|
|
64
|
+
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:global([data-slot="sheet-content"][data-state="closed"]) {
|
|
68
|
+
animation-duration: 220ms;
|
|
69
|
+
animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@media (prefers-reduced-motion: reduce) {
|
|
73
|
+
:global([data-slot="sheet-content"]) {
|
|
74
|
+
animation-duration: 1ms;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Dialog as SheetPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "@riducms/ui";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: SheetPrimitive.DescriptionProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<SheetPrimitive.Description
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="sheet-description"
|
|
15
|
+
class={cn("text-sm text-muted-foreground", className)}
|
|
16
|
+
{...restProps}
|
|
17
|
+
/>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Dialog as SheetPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "@riducms/ui";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: SheetPrimitive.OverlayProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<SheetPrimitive.Overlay
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="sheet-overlay"
|
|
15
|
+
class={cn(
|
|
16
|
+
"fixed inset-0 z-50 bg-backdrop supports-backdrop-filter:backdrop-blur-sm data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...restProps}
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
<style>
|
|
23
|
+
:global([data-slot="sheet-overlay"][data-state="open"]) {
|
|
24
|
+
animation-duration: 240ms;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:global([data-slot="sheet-overlay"][data-state="closed"]) {
|
|
28
|
+
animation-duration: 180ms;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@media (prefers-reduced-motion: reduce) {
|
|
32
|
+
:global([data-slot="sheet-overlay"]) {
|
|
33
|
+
animation-duration: 1ms;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Dialog as SheetPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "@riducms/ui";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: SheetPrimitive.TitleProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<SheetPrimitive.Title
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="sheet-title"
|
|
15
|
+
class={cn("text-base font-medium text-foreground", className)}
|
|
16
|
+
{...restProps}
|
|
17
|
+
/>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Skeleton } from "@admin/components/ui/skeleton/skeleton.svelte";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef, type WithoutChildren } from "@riducms/ui";
|
|
3
|
+
import { onMount } from "svelte";
|
|
4
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
5
|
+
|
|
6
|
+
const revealDelayMilliseconds = 180;
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
ref = $bindable(null),
|
|
10
|
+
class: className,
|
|
11
|
+
...restProps
|
|
12
|
+
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
|
|
13
|
+
let revealed = $state(false);
|
|
14
|
+
|
|
15
|
+
onMount(() => {
|
|
16
|
+
const timer = window.setTimeout(() => (revealed = true), revealDelayMilliseconds);
|
|
17
|
+
return () => window.clearTimeout(timer);
|
|
18
|
+
});
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
bind:this={ref}
|
|
23
|
+
data-slot="skeleton"
|
|
24
|
+
class={cn(
|
|
25
|
+
"rounded bg-muted transition-opacity duration-150 ease-out motion-reduce:transition-none",
|
|
26
|
+
revealed ? "opacity-100" : "opacity-0",
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
{...restProps}
|
|
30
|
+
aria-hidden={!revealed}
|
|
31
|
+
></div>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Slider as SliderPrimitive } from "bits-ui";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@riducms/ui";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
id,
|
|
8
|
+
value,
|
|
9
|
+
min = 0,
|
|
10
|
+
max = 100,
|
|
11
|
+
step = 1,
|
|
12
|
+
disabled = false,
|
|
13
|
+
label,
|
|
14
|
+
describedBy,
|
|
15
|
+
class: className,
|
|
16
|
+
onValueChange,
|
|
17
|
+
}: {
|
|
18
|
+
id?: string;
|
|
19
|
+
value: number;
|
|
20
|
+
min?: number;
|
|
21
|
+
max?: number;
|
|
22
|
+
step?: number;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
label: string;
|
|
25
|
+
describedBy?: string;
|
|
26
|
+
class?: string;
|
|
27
|
+
onValueChange: (value: number) => void;
|
|
28
|
+
} = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<SliderPrimitive.Root
|
|
32
|
+
{id}
|
|
33
|
+
type="single"
|
|
34
|
+
{value}
|
|
35
|
+
{min}
|
|
36
|
+
{max}
|
|
37
|
+
{step}
|
|
38
|
+
{disabled}
|
|
39
|
+
{onValueChange}
|
|
40
|
+
data-slot="slider"
|
|
41
|
+
class={cn(
|
|
42
|
+
"relative flex h-5 w-full touch-none items-center select-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-45",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
{#snippet children({ thumbItems })}
|
|
47
|
+
<span class="relative h-1.5 w-full grow overflow-hidden rounded-full bg-control-disabled">
|
|
48
|
+
<SliderPrimitive.Range class="absolute h-full rounded-full bg-primary" />
|
|
49
|
+
</span>
|
|
50
|
+
{#each thumbItems as thumb (thumb.index)}
|
|
51
|
+
<SliderPrimitive.Thumb
|
|
52
|
+
index={thumb.index}
|
|
53
|
+
aria-label={label}
|
|
54
|
+
aria-describedby={describedBy}
|
|
55
|
+
class="block size-4 rounded-full border-2 border-primary-foreground bg-primary shadow-sm outline-none transition-[box-shadow,transform] hover:scale-110 focus-visible:scale-110 focus-visible:ring-2 focus-visible:ring-primary/45 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none"
|
|
56
|
+
/>
|
|
57
|
+
{/each}
|
|
58
|
+
{/snippet}
|
|
59
|
+
</SliderPrimitive.Root>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component } from "svelte";
|
|
3
|
+
|
|
4
|
+
type SliderProps = {
|
|
5
|
+
id?: string;
|
|
6
|
+
value: number;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
step?: number;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
label: string;
|
|
12
|
+
describedBy?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
onValueChange: (value: number) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let props: SliderProps = $props();
|
|
18
|
+
let Control = $state<Component<SliderProps> | undefined>(undefined);
|
|
19
|
+
|
|
20
|
+
void import("@admin/components/ui/slider/slider-content.svelte").then(
|
|
21
|
+
({ default: component }) => (Control = component)
|
|
22
|
+
);
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#if Control !== undefined}
|
|
26
|
+
<Control {...props} />
|
|
27
|
+
{:else}
|
|
28
|
+
<div
|
|
29
|
+
class={props.class ??
|
|
30
|
+
"relative flex h-5 w-full animate-pulse items-center rounded-full bg-control-disabled"}
|
|
31
|
+
role="status"
|
|
32
|
+
aria-label={props.label}
|
|
33
|
+
aria-busy="true"
|
|
34
|
+
></div>
|
|
35
|
+
{/if}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Toaster } from "@admin/components/ui/sonner/sonner.svelte";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import LoaderCircleIcon from "~icons/lucide/loader-circle";
|
|
3
|
+
import { Toaster as Sonner, type ToasterProps } from "svelte-sonner";
|
|
4
|
+
import { getAdminI18n } from "@riducms/plugin";
|
|
5
|
+
|
|
6
|
+
const i18n = getAdminI18n();
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
theme = "dark",
|
|
10
|
+
position,
|
|
11
|
+
closeButton = true,
|
|
12
|
+
pauseWhenPageIsHidden = true,
|
|
13
|
+
visibleToasts = 4,
|
|
14
|
+
offset = 16,
|
|
15
|
+
mobileOffset = 12,
|
|
16
|
+
toastOptions,
|
|
17
|
+
...restProps
|
|
18
|
+
}: ToasterProps = $props();
|
|
19
|
+
const resolvedPosition = $derived(
|
|
20
|
+
position ?? (i18n.direction === "rtl" ? "bottom-left" : "bottom-right")
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const options = $derived({
|
|
24
|
+
unstyled: true,
|
|
25
|
+
...toastOptions,
|
|
26
|
+
classes: {
|
|
27
|
+
toast:
|
|
28
|
+
"ridu-toast flex w-[min(calc(100vw-2rem),390px)] items-start gap-2.5 rounded-[10px] py-3 pe-3.5 ps-5 shadow-[var(--shadow-popover)]",
|
|
29
|
+
content: "min-w-0 flex-1",
|
|
30
|
+
title: "text-[13.5px] leading-5 font-semibold",
|
|
31
|
+
description: "mt-0.5 text-[12.5px] leading-5",
|
|
32
|
+
icon: "mt-0.75 shrink-0",
|
|
33
|
+
closeButton: "outline-none focus-visible:outline-2 focus-visible:outline-primary/60",
|
|
34
|
+
...toastOptions?.classes,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<Sonner
|
|
40
|
+
{theme}
|
|
41
|
+
position={resolvedPosition}
|
|
42
|
+
{closeButton}
|
|
43
|
+
{pauseWhenPageIsHidden}
|
|
44
|
+
{visibleToasts}
|
|
45
|
+
{offset}
|
|
46
|
+
{mobileOffset}
|
|
47
|
+
toastOptions={options}
|
|
48
|
+
containerAriaLabel={i18n.t("general:notifications")}
|
|
49
|
+
closeButtonAriaLabel={i18n.t("general:dismissNotification")}
|
|
50
|
+
{...restProps}
|
|
51
|
+
>
|
|
52
|
+
{#snippet loadingIcon()}
|
|
53
|
+
<LoaderCircleIcon class="size-3.5 animate-spin" aria-hidden="true" />
|
|
54
|
+
{/snippet}
|
|
55
|
+
</Sonner>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { tv, type VariantProps } from "@riducms/ui";
|
|
3
|
+
|
|
4
|
+
export const statusIndicatorVariants = tv({
|
|
5
|
+
base: "inline-flex items-center gap-1.75 text-[12.5px] text-foreground-muted",
|
|
6
|
+
variants: {
|
|
7
|
+
tone: {
|
|
8
|
+
live: "text-primary",
|
|
9
|
+
warning: "text-warning",
|
|
10
|
+
muted: "text-foreground-muted",
|
|
11
|
+
destructive: "text-destructive",
|
|
12
|
+
success: "text-success",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: { tone: "muted" },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type StatusTone = VariantProps<typeof statusIndicatorVariants>["tone"];
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
23
|
+
|
|
24
|
+
import { cn, type WithElementRef } from "@riducms/ui";
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
ref = $bindable(null),
|
|
28
|
+
tone = "muted",
|
|
29
|
+
pulse = false,
|
|
30
|
+
class: className,
|
|
31
|
+
children,
|
|
32
|
+
...restProps
|
|
33
|
+
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> & {
|
|
34
|
+
tone?: StatusTone;
|
|
35
|
+
pulse?: boolean;
|
|
36
|
+
} = $props();
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<span
|
|
40
|
+
bind:this={ref}
|
|
41
|
+
data-slot="status-indicator"
|
|
42
|
+
class={cn(statusIndicatorVariants({ tone }), className)}
|
|
43
|
+
{...restProps}
|
|
44
|
+
>
|
|
45
|
+
<span
|
|
46
|
+
class={[
|
|
47
|
+
"size-1.75 shrink-0 rounded-full",
|
|
48
|
+
tone === "muted" ? "border border-current bg-transparent" : "bg-current",
|
|
49
|
+
pulse && "shadow-[0_0_9px_currentColor]",
|
|
50
|
+
]}
|
|
51
|
+
aria-hidden="true"
|
|
52
|
+
></span>
|
|
53
|
+
{@render children?.()}
|
|
54
|
+
</span>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as Table } from "@admin/components/ui/table/table.svelte";
|
|
2
|
+
export { default as TableBody } from "@admin/components/ui/table/table-body.svelte";
|
|
3
|
+
export { default as TableCell } from "@admin/components/ui/table/table-cell.svelte";
|
|
4
|
+
export { default as TableHead } from "@admin/components/ui/table/table-head.svelte";
|
|
5
|
+
export { default as TableHeader } from "@admin/components/ui/table/table-header.svelte";
|
|
6
|
+
export { default as TableRow } from "@admin/components/ui/table/table-row.svelte";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@riducms/ui";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<tbody
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="table-body"
|
|
16
|
+
class={cn("[&_tr:last-child]:border-0", className)}
|
|
17
|
+
{...restProps}
|
|
18
|
+
>
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</tbody>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@riducms/ui";
|
|
3
|
+
import type { HTMLTdAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLTdAttributes> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<td
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="table-cell"
|
|
16
|
+
class={cn(
|
|
17
|
+
"px-2.5 py-2.5 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</td>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@riducms/ui";
|
|
3
|
+
import type { HTMLThAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLThAttributes> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<th
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="table-head"
|
|
16
|
+
class={cn(
|
|
17
|
+
"h-[31px] px-2.5 text-start align-middle text-[11.5px] font-medium whitespace-nowrap text-foreground-muted [&:has([role=checkbox])]:pe-0",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</th>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@riducms/ui";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<thead
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="table-header"
|
|
16
|
+
class={cn("[&_tr]:border-b [&_tr]:border-control-border", className)}
|
|
17
|
+
{...restProps}
|
|
18
|
+
>
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</thead>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@riducms/ui";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<tr
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="table-row"
|
|
16
|
+
class={cn(
|
|
17
|
+
"border-b border-control-border transition-colors hover:bg-control data-[state=selected]:bg-primary/5",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</tr>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@riducms/ui";
|
|
3
|
+
import type { HTMLTableAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLTableAttributes> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div data-slot="table-container" class="relative w-full overflow-x-auto">
|
|
14
|
+
<table
|
|
15
|
+
bind:this={ref}
|
|
16
|
+
data-slot="table"
|
|
17
|
+
class={cn("w-full caption-bottom text-sm", className)}
|
|
18
|
+
{...restProps}
|
|
19
|
+
>
|
|
20
|
+
{@render children?.()}
|
|
21
|
+
</table>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export {
|
|
2
|
+
default as TabsList,
|
|
3
|
+
tabsListVariants,
|
|
4
|
+
type TabsListVariant,
|
|
5
|
+
} from "@admin/components/ui/tabs/tabs-list.svelte";
|
|
6
|
+
export { default as Tabs } from "@admin/components/ui/tabs/tabs.svelte";
|
|
7
|
+
export { default as TabsContent } from "@admin/components/ui/tabs/tabs-content.svelte";
|
|
8
|
+
export { default as TabsTrigger } from "@admin/components/ui/tabs/tabs-trigger.svelte";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "@riducms/ui";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: TabsPrimitive.ContentProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<TabsPrimitive.Content
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="tabs-content"
|
|
15
|
+
class={cn("text-sm flex-1 outline-none", className)}
|
|
16
|
+
{...restProps}
|
|
17
|
+
/>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { tv, type VariantProps } from "@riducms/ui";
|
|
3
|
+
|
|
4
|
+
export const tabsListVariants = tv({
|
|
5
|
+
base: "rounded-lg p-0.5 group-data-horizontal/tabs:h-8 group-data-vertical/tabs:p-1 data-[variant=line]:rounded-none group/tabs-list inline-flex w-fit items-center justify-center text-muted-foreground group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: "cn-tabs-list-variant-default border border-control-border bg-tabs-surface",
|
|
9
|
+
line: "cn-tabs-list-variant-line gap-1 bg-transparent",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
variant: "default",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type TabsListVariant = VariantProps<typeof tabsListVariants>["variant"];
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
22
|
+
import { cn } from "@riducms/ui";
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
ref = $bindable(null),
|
|
26
|
+
variant = "default",
|
|
27
|
+
class: className,
|
|
28
|
+
...restProps
|
|
29
|
+
}: TabsPrimitive.ListProps & {
|
|
30
|
+
variant?: TabsListVariant;
|
|
31
|
+
} = $props();
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<TabsPrimitive.List
|
|
35
|
+
bind:ref
|
|
36
|
+
data-slot="tabs-list"
|
|
37
|
+
data-variant={variant}
|
|
38
|
+
class={cn(tabsListVariants({ variant }), className)}
|
|
39
|
+
{...restProps}
|
|
40
|
+
/>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "@riducms/ui";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: TabsPrimitive.TriggerProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<TabsPrimitive.Trigger
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="tabs-trigger"
|
|
15
|
+
class={cn(
|
|
16
|
+
"gap-1.5 rounded-md border border-transparent! px-3.5 py-1 text-[13px] font-medium group-data-vertical/tabs:px-3 group-data-vertical/tabs:py-1 [&_svg:not([class*='size-'])]:size-3.5 relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap text-foreground-sub transition-[background-color,color] duration-150 group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start data-[state=inactive]:hover:text-foreground-muted focus-visible:outline-2 focus-visible:outline-primary/60 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
17
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
18
|
+
"data-[state=active]:bg-tabs-active data-[state=active]:text-tabs-text-active",
|
|
19
|
+
"after:absolute after:bg-foreground-strong after:opacity-0 after:transition-opacity data-[orientation=horizontal]:after:inset-x-0 data-[orientation=horizontal]:after:bottom-0 data-[orientation=horizontal]:after:h-0.5 data-[orientation=vertical]:after:inset-y-0 data-[orientation=vertical]:after:-end-1 data-[orientation=vertical]:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...restProps}
|
|
23
|
+
/>
|