@ternent/core 0.0.1
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/.changeset/README.md +8 -0
- package/.changeset/config.json +17 -0
- package/.github/workflows/deploy-armour.yml +42 -0
- package/.github/workflows/deploy-identity.yml +42 -0
- package/.github/workflows/deploy-seal.yml +42 -0
- package/.github/workflows/deploy-ui.yml +42 -0
- package/.github/workflows/deploy-utils.yml +42 -0
- package/.github/workflows/release-create.yml +59 -0
- package/.github/workflows/release-publish.yml +54 -0
- package/.nvmrc +1 -0
- package/.ops/publish.mjs +31 -0
- package/package.json +16 -0
- package/packages/README.md +0 -0
- package/packages/armour/CHANGELOG.md +66 -0
- package/packages/armour/CLAUDE.md +8 -0
- package/packages/armour/README.md +103 -0
- package/packages/armour/SPEC.md +92 -0
- package/packages/armour/package.json +45 -0
- package/packages/armour/src/constants.ts +5 -0
- package/packages/armour/src/deps.d.ts +56 -0
- package/packages/armour/src/errors.ts +172 -0
- package/packages/armour/src/files.ts +73 -0
- package/packages/armour/src/identity.ts +72 -0
- package/packages/armour/src/index.ts +56 -0
- package/packages/armour/src/init.ts +10 -0
- package/packages/armour/src/passphrase.ts +33 -0
- package/packages/armour/src/recipients.ts +73 -0
- package/packages/armour/src/text.ts +68 -0
- package/packages/armour/src/types.ts +93 -0
- package/packages/armour/test/armour.test.ts +270 -0
- package/packages/armour/tsconfig.build.json +12 -0
- package/packages/armour/tsconfig.json +12 -0
- package/packages/armour/vite.config.ts +29 -0
- package/packages/concord/CHANGELOG.md +83 -0
- package/packages/concord/CLAUDE.md +9 -0
- package/packages/concord/README.md +146 -0
- package/packages/concord/SPEC.md +287 -0
- package/packages/concord/package.json +51 -0
- package/packages/concord/src/app.ts +717 -0
- package/packages/concord/src/errors.ts +9 -0
- package/packages/concord/src/index.ts +20 -0
- package/packages/concord/src/types.ts +127 -0
- package/packages/concord/test/concord.test.ts +978 -0
- package/packages/concord/tsconfig.json +12 -0
- package/packages/concord/vite.browser.config.ts +27 -0
- package/packages/concord/vite.config.ts +35 -0
- package/packages/concord/vite.config.ts.timestamp-1774262297922-ffd76e35ea668.mjs +83 -0
- package/packages/identity/CHANGELOG.md +47 -0
- package/packages/identity/README.md +236 -0
- package/packages/identity/package.json +41 -0
- package/packages/identity/src/index.ts +538 -0
- package/packages/identity/test/identity.test.ts +172 -0
- package/packages/identity/tsconfig.build.json +12 -0
- package/packages/identity/vite.config.ts +17 -0
- package/packages/ledger/CHANGELOG.md +69 -0
- package/packages/ledger/CLAUDE.md +9 -0
- package/packages/ledger/SPEC.md +304 -0
- package/packages/ledger/package.json +48 -0
- package/packages/ledger/src/index.ts +2 -0
- package/packages/ledger/src/ledger.ts +1286 -0
- package/packages/ledger/src/seal-cli.d.ts +25 -0
- package/packages/ledger/src/types.ts +294 -0
- package/packages/ledger/test/ledger.test.ts +838 -0
- package/packages/ledger/tsconfig.json +12 -0
- package/packages/ledger/vite.browser.config.ts +27 -0
- package/packages/ledger/vite.config.ts +39 -0
- package/packages/seal/CHANGELOG.md +137 -0
- package/packages/seal/CLAUDE.md +8 -0
- package/packages/seal/README.md +258 -0
- package/packages/seal/bin/seal +6 -0
- package/packages/seal/package.json +59 -0
- package/packages/seal/src/artifact.ts +380 -0
- package/packages/seal/src/cli.ts +372 -0
- package/packages/seal/src/commands/identity.ts +52 -0
- package/packages/seal/src/commands/manifest.ts +71 -0
- package/packages/seal/src/commands/publicKey.ts +7 -0
- package/packages/seal/src/commands/sign.ts +56 -0
- package/packages/seal/src/commands/verify.ts +54 -0
- package/packages/seal/src/crypto.ts +85 -0
- package/packages/seal/src/errors.ts +88 -0
- package/packages/seal/src/index.ts +5 -0
- package/packages/seal/src/manifest.ts +114 -0
- package/packages/seal/src/node.ts +18 -0
- package/packages/seal/src/proof.ts +344 -0
- package/packages/seal/test/artifact.test.ts +86 -0
- package/packages/seal/test/cli.test.ts +208 -0
- package/packages/seal/test/crypto.test.ts +21 -0
- package/packages/seal/test/manifest.test.ts +32 -0
- package/packages/seal/test/proof.test.ts +60 -0
- package/packages/seal/tsconfig.json +12 -0
- package/packages/seal/vite.config.ts +54 -0
- package/packages/ui/CHANGELOG.md +393 -0
- package/packages/ui/README.md +57 -0
- package/packages/ui/jsconfig.json +19 -0
- package/packages/ui/package.json +64 -0
- package/packages/ui/scripts/check-tokens.js +56 -0
- package/packages/ui/scripts/generate-theme-css.mjs +85 -0
- package/packages/ui/src/design-system/base.css +8 -0
- package/packages/ui/src/design-system/docs/ACCESSIBILITY_RULES.md +186 -0
- package/packages/ui/src/design-system/docs/AI_SYSTEM.md +281 -0
- package/packages/ui/src/design-system/docs/PATTERN_RULES.md +83 -0
- package/packages/ui/src/design-system/docs/PRIMITIVE_RULES.md +258 -0
- package/packages/ui/src/design-system/docs/TOKEN_RULES.md +235 -0
- package/packages/ui/src/design-system/docs/VISUAL_DIRECTION.md +68 -0
- package/packages/ui/src/design-system/foundation.js +420 -0
- package/packages/ui/src/design-system/tokens.css +140 -0
- package/packages/ui/src/design-system/tokens.js +327 -0
- package/packages/ui/src/design-system/utils.js +246 -0
- package/packages/ui/src/main.js +4 -0
- package/packages/ui/src/patterns/FeatureCard/FeatureCard.spec.md +24 -0
- package/packages/ui/src/patterns/FeatureCard/FeatureCard.types.ts +8 -0
- package/packages/ui/src/patterns/FeatureCard/FeatureCard.vue +175 -0
- package/packages/ui/src/patterns/FormField/FormField.spec.md +65 -0
- package/packages/ui/src/patterns/FormField/FormField.types.ts +11 -0
- package/packages/ui/src/patterns/FormField/FormField.vue +87 -0
- package/packages/ui/src/patterns/IdentityGlyph/IdentityGlyph.vue +61 -0
- package/packages/ui/src/patterns/IdentityGlyph/IdentityHandle.vue +58 -0
- package/packages/ui/src/patterns/IdentityGlyph/identityGlyph.types.ts +36 -0
- package/packages/ui/src/patterns/IdentityGlyph/identityGlyph.utils.ts +585 -0
- package/packages/ui/src/patterns/IdentityGlyph/index.ts +5 -0
- package/packages/ui/src/patterns/KeyValueList/KeyValueList.spec.md +28 -0
- package/packages/ui/src/patterns/KeyValueList/KeyValueList.types.ts +16 -0
- package/packages/ui/src/patterns/KeyValueList/KeyValueList.vue +50 -0
- package/packages/ui/src/patterns/LandingPage/LandingIcon.vue +90 -0
- package/packages/ui/src/patterns/LandingPage/LandingPage.spec.md +24 -0
- package/packages/ui/src/patterns/LandingPage/LandingPage.types.ts +212 -0
- package/packages/ui/src/patterns/LandingPage/LandingPage.vue +599 -0
- package/packages/ui/src/patterns/ListWorkspaceLayout/ListWorkspaceLayout.test.ts +33 -0
- package/packages/ui/src/patterns/ListWorkspaceLayout/ListWorkspaceLayout.vue +44 -0
- package/packages/ui/src/patterns/Logo/Logo.spec.md +22 -0
- package/packages/ui/src/patterns/Logo/Logo.vue +160 -0
- package/packages/ui/src/patterns/PageSurface/PageSurface.spec.md +15 -0
- package/packages/ui/src/patterns/PageSurface/PageSurface.vue +85 -0
- package/packages/ui/src/patterns/PanelChrome/PanelChrome.spec.md +39 -0
- package/packages/ui/src/patterns/PanelChrome/PanelChrome.types.ts +1 -0
- package/packages/ui/src/patterns/PanelChrome/PanelChrome.vue +187 -0
- package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.spec.md +31 -0
- package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.types.ts +23 -0
- package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.vue +354 -0
- package/packages/ui/src/patterns/RecordList/RecordList.spec.md +35 -0
- package/packages/ui/src/patterns/RecordList/RecordList.test.ts +42 -0
- package/packages/ui/src/patterns/RecordList/RecordList.types.ts +9 -0
- package/packages/ui/src/patterns/RecordList/RecordList.utils.ts +5 -0
- package/packages/ui/src/patterns/RecordList/RecordList.vue +134 -0
- package/packages/ui/src/patterns/SectionClarifier/SectionClarifier.vue +85 -0
- package/packages/ui/src/patterns/SectionIntro/SectionIntro.spec.md +25 -0
- package/packages/ui/src/patterns/SectionIntro/SectionIntro.types.ts +7 -0
- package/packages/ui/src/patterns/SectionIntro/SectionIntro.vue +141 -0
- package/packages/ui/src/patterns/SidebarNav/SidebarNav.spec.md +34 -0
- package/packages/ui/src/patterns/SidebarNav/SidebarNav.types.ts +17 -0
- package/packages/ui/src/patterns/SidebarNav/SidebarNav.vue +110 -0
- package/packages/ui/src/patterns/SplitView/SplitView.spec.md +28 -0
- package/packages/ui/src/patterns/SplitView/SplitView.test.ts +22 -0
- package/packages/ui/src/patterns/SplitView/SplitView.types.ts +3 -0
- package/packages/ui/src/patterns/SplitView/SplitView.utils.ts +13 -0
- package/packages/ui/src/patterns/SplitView/SplitView.vue +39 -0
- package/packages/ui/src/patterns/StepList/StepList.spec.md +15 -0
- package/packages/ui/src/patterns/StepList/StepList.types.ts +4 -0
- package/packages/ui/src/patterns/StepList/StepList.vue +91 -0
- package/packages/ui/src/patterns/Verification/VerificationBadge.vue +97 -0
- package/packages/ui/src/patterns/Verification/VerificationComponents.test.ts +153 -0
- package/packages/ui/src/patterns/Verification/VerificationDetailsPanel.vue +270 -0
- package/packages/ui/src/patterns/Verification/VerificationSummary.vue +171 -0
- package/packages/ui/src/patterns/Verification/index.ts +6 -0
- package/packages/ui/src/patterns/Verification/verification.types.ts +8 -0
- package/packages/ui/src/patterns/Verification/verification.utils.test.ts +37 -0
- package/packages/ui/src/patterns/Verification/verification.utils.ts +75 -0
- package/packages/ui/src/patterns/index.ts +25 -0
- package/packages/ui/src/primitives/Accordian/Accordian.vue +11 -0
- package/packages/ui/src/primitives/Accordian/AccordianItem.vue +14 -0
- package/packages/ui/src/primitives/Accordion/Accordion.props.ts +21 -0
- package/packages/ui/src/primitives/Accordion/Accordion.spec.md +50 -0
- package/packages/ui/src/primitives/Accordion/Accordion.types.ts +4 -0
- package/packages/ui/src/primitives/Accordion/Accordion.variants.ts +12 -0
- package/packages/ui/src/primitives/Accordion/Accordion.vue +71 -0
- package/packages/ui/src/primitives/Accordion/AccordionItem.props.ts +14 -0
- package/packages/ui/src/primitives/Accordion/AccordionItem.vue +40 -0
- package/packages/ui/src/primitives/Badge/Badge.props.ts +17 -0
- package/packages/ui/src/primitives/Badge/Badge.spec.md +17 -0
- package/packages/ui/src/primitives/Badge/Badge.types.ts +15 -0
- package/packages/ui/src/primitives/Badge/Badge.variants.ts +48 -0
- package/packages/ui/src/primitives/Badge/Badge.vue +31 -0
- package/packages/ui/src/primitives/Button/Button.props.ts +29 -0
- package/packages/ui/src/primitives/Button/Button.spec.md +139 -0
- package/packages/ui/src/primitives/Button/Button.types.ts +19 -0
- package/packages/ui/src/primitives/Button/Button.variants.ts +72 -0
- package/packages/ui/src/primitives/Button/Button.vue +90 -0
- package/packages/ui/src/primitives/Card/Card.props.ts +17 -0
- package/packages/ui/src/primitives/Card/Card.spec.md +29 -0
- package/packages/ui/src/primitives/Card/Card.types.ts +12 -0
- package/packages/ui/src/primitives/Card/Card.variants.ts +27 -0
- package/packages/ui/src/primitives/Card/Card.vue +37 -0
- package/packages/ui/src/primitives/Checkbox/Checkbox.props.ts +21 -0
- package/packages/ui/src/primitives/Checkbox/Checkbox.spec.md +51 -0
- package/packages/ui/src/primitives/Checkbox/Checkbox.types.ts +4 -0
- package/packages/ui/src/primitives/Checkbox/Checkbox.variants.ts +34 -0
- package/packages/ui/src/primitives/Checkbox/Checkbox.vue +92 -0
- package/packages/ui/src/primitives/Dialog/Dialog.props.ts +29 -0
- package/packages/ui/src/primitives/Dialog/Dialog.spec.md +52 -0
- package/packages/ui/src/primitives/Dialog/Dialog.types.ts +3 -0
- package/packages/ui/src/primitives/Dialog/Dialog.variants.ts +27 -0
- package/packages/ui/src/primitives/Dialog/Dialog.vue +78 -0
- package/packages/ui/src/primitives/Drawer/Drawer.props.ts +33 -0
- package/packages/ui/src/primitives/Drawer/Drawer.spec.md +50 -0
- package/packages/ui/src/primitives/Drawer/Drawer.types.ts +5 -0
- package/packages/ui/src/primitives/Drawer/Drawer.variants.ts +35 -0
- package/packages/ui/src/primitives/Drawer/Drawer.vue +88 -0
- package/packages/ui/src/primitives/FieldMessage/FieldMessage.props.ts +17 -0
- package/packages/ui/src/primitives/FieldMessage/FieldMessage.spec.md +35 -0
- package/packages/ui/src/primitives/FieldMessage/FieldMessage.types.ts +5 -0
- package/packages/ui/src/primitives/FieldMessage/FieldMessage.variants.ts +14 -0
- package/packages/ui/src/primitives/FieldMessage/FieldMessage.vue +40 -0
- package/packages/ui/src/primitives/FileInput/FileInput.props.ts +41 -0
- package/packages/ui/src/primitives/FileInput/FileInput.types.ts +6 -0
- package/packages/ui/src/primitives/FileInput/FileInput.variants.ts +46 -0
- package/packages/ui/src/primitives/FileInput/FileInput.vue +163 -0
- package/packages/ui/src/primitives/Input/Input.props.ts +29 -0
- package/packages/ui/src/primitives/Input/Input.spec.md +79 -0
- package/packages/ui/src/primitives/Input/Input.types.ts +13 -0
- package/packages/ui/src/primitives/Input/Input.variants.ts +54 -0
- package/packages/ui/src/primitives/Input/Input.vue +99 -0
- package/packages/ui/src/primitives/Label/Label.props.ts +25 -0
- package/packages/ui/src/primitives/Label/Label.spec.md +31 -0
- package/packages/ui/src/primitives/Label/Label.types.ts +3 -0
- package/packages/ui/src/primitives/Label/Label.variants.ts +17 -0
- package/packages/ui/src/primitives/Label/Label.vue +38 -0
- package/packages/ui/src/primitives/Menu/Menu.props.ts +17 -0
- package/packages/ui/src/primitives/Menu/Menu.spec.md +38 -0
- package/packages/ui/src/primitives/Menu/Menu.types.ts +10 -0
- package/packages/ui/src/primitives/Menu/Menu.variants.ts +10 -0
- package/packages/ui/src/primitives/Menu/Menu.vue +57 -0
- package/packages/ui/src/primitives/Popover/Popover.props.ts +25 -0
- package/packages/ui/src/primitives/Popover/Popover.spec.md +49 -0
- package/packages/ui/src/primitives/Popover/Popover.types.ts +3 -0
- package/packages/ui/src/primitives/Popover/Popover.variants.ts +18 -0
- package/packages/ui/src/primitives/Popover/Popover.vue +74 -0
- package/packages/ui/src/primitives/RadioGroup/RadioGroup.props.ts +29 -0
- package/packages/ui/src/primitives/RadioGroup/RadioGroup.spec.md +50 -0
- package/packages/ui/src/primitives/RadioGroup/RadioGroup.types.ts +12 -0
- package/packages/ui/src/primitives/RadioGroup/RadioGroup.variants.ts +48 -0
- package/packages/ui/src/primitives/RadioGroup/RadioGroup.vue +87 -0
- package/packages/ui/src/primitives/Separator/Separator.props.ts +9 -0
- package/packages/ui/src/primitives/Separator/Separator.spec.md +15 -0
- package/packages/ui/src/primitives/Separator/Separator.types.ts +3 -0
- package/packages/ui/src/primitives/Separator/Separator.variants.ts +8 -0
- package/packages/ui/src/primitives/Separator/Separator.vue +23 -0
- package/packages/ui/src/primitives/Skeleton/Skeleton.props.ts +21 -0
- package/packages/ui/src/primitives/Skeleton/Skeleton.spec.md +18 -0
- package/packages/ui/src/primitives/Skeleton/Skeleton.types.ts +5 -0
- package/packages/ui/src/primitives/Skeleton/Skeleton.variants.ts +18 -0
- package/packages/ui/src/primitives/Skeleton/Skeleton.vue +37 -0
- package/packages/ui/src/primitives/Spinner/Spinner.props.ts +13 -0
- package/packages/ui/src/primitives/Spinner/Spinner.spec.md +16 -0
- package/packages/ui/src/primitives/Spinner/Spinner.types.ts +5 -0
- package/packages/ui/src/primitives/Spinner/Spinner.variants.ts +15 -0
- package/packages/ui/src/primitives/Spinner/Spinner.vue +33 -0
- package/packages/ui/src/primitives/SplitButton/SplitButton.vue +108 -0
- package/packages/ui/src/primitives/Switch/Switch.props.ts +21 -0
- package/packages/ui/src/primitives/Switch/Switch.spec.md +49 -0
- package/packages/ui/src/primitives/Switch/Switch.types.ts +3 -0
- package/packages/ui/src/primitives/Switch/Switch.variants.ts +34 -0
- package/packages/ui/src/primitives/Switch/Switch.vue +71 -0
- package/packages/ui/src/primitives/Tabs/Tabs.props.ts +25 -0
- package/packages/ui/src/primitives/Tabs/Tabs.spec.md +48 -0
- package/packages/ui/src/primitives/Tabs/Tabs.types.ts +11 -0
- package/packages/ui/src/primitives/Tabs/Tabs.variants.ts +28 -0
- package/packages/ui/src/primitives/Tabs/Tabs.vue +59 -0
- package/packages/ui/src/primitives/Textarea/Textarea.props.ts +33 -0
- package/packages/ui/src/primitives/Textarea/Textarea.spec.md +59 -0
- package/packages/ui/src/primitives/Textarea/Textarea.types.ts +5 -0
- package/packages/ui/src/primitives/Textarea/Textarea.variants.ts +27 -0
- package/packages/ui/src/primitives/Textarea/Textarea.vue +74 -0
- package/packages/ui/src/primitives/Tooltip/Tooltip.props.ts +21 -0
- package/packages/ui/src/primitives/Tooltip/Tooltip.spec.md +45 -0
- package/packages/ui/src/primitives/Tooltip/Tooltip.types.ts +3 -0
- package/packages/ui/src/primitives/Tooltip/Tooltip.variants.ts +4 -0
- package/packages/ui/src/primitives/Tooltip/Tooltip.vue +31 -0
- package/packages/ui/src/primitives/TreeView/TreeView.types.ts +10 -0
- package/packages/ui/src/primitives/TreeView/TreeView.vue +113 -0
- package/packages/ui/src/primitives/TreeView/TreeViewNode.vue +190 -0
- package/packages/ui/src/primitives/index.ts +29 -0
- package/packages/ui/src/style.css +7 -0
- package/packages/ui/src/style.js +1 -0
- package/packages/ui/src/themes/armour.css +147 -0
- package/packages/ui/src/themes/aurora.css +147 -0
- package/packages/ui/src/themes/citrine-ash.css +147 -0
- package/packages/ui/src/themes/concord.css +147 -0
- package/packages/ui/src/themes/garnet-honey.css +147 -0
- package/packages/ui/src/themes/harbor-rose.css +147 -0
- package/packages/ui/src/themes/ledger.css +147 -0
- package/packages/ui/src/themes/neon-noir.css +74 -0
- package/packages/ui/src/themes/obsidian-iris.css +147 -0
- package/packages/ui/src/themes/pixpax.css +147 -0
- package/packages/ui/src/themes/print.css +147 -0
- package/packages/ui/src/themes/prism.css +147 -0
- package/packages/ui/src/themes/proof.css +145 -0
- package/packages/ui/src/themes/semanticThemeContract.js +2256 -0
- package/packages/ui/src/themes/spruce-ink.css +147 -0
- package/packages/ui/src/themes/sunset.css +147 -0
- package/packages/ui/tailwind.config.js +64 -0
- package/packages/ui/vite.config.js +35 -0
- package/packages/ui/vite.config.js.timestamp-1780697224943-89fbc929987bc.mjs +38 -0
- package/packages/utils/CHANGELOG.md +111 -0
- package/packages/utils/README.md +3 -0
- package/packages/utils/package.json +46 -0
- package/packages/utils/src/index.test.js +39 -0
- package/packages/utils/src/index.ts +289 -0
- package/packages/utils/tsconfig.build.json +12 -0
- package/packages/utils/vite.config.js +28 -0
- package/pnpm-workspace.yaml +8 -0
- package/scripts/vite/package-lib-config.ts +59 -0
- package/tsconfig.json +24 -0
- package/tsconfig.node.json +9 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Dialog as ArkDialog } from "@ark-ui/vue/dialog";
|
|
3
|
+
import { computed, useSlots } from "vue";
|
|
4
|
+
import { dialogProps } from "./Dialog.props";
|
|
5
|
+
import {
|
|
6
|
+
dialogBackdropClass,
|
|
7
|
+
dialogBodyClass,
|
|
8
|
+
dialogCloseClass,
|
|
9
|
+
dialogContentBaseClass,
|
|
10
|
+
dialogContentSizeClasses,
|
|
11
|
+
dialogDescriptionClass,
|
|
12
|
+
dialogFooterClass,
|
|
13
|
+
dialogHeaderClass,
|
|
14
|
+
dialogPositionerClass,
|
|
15
|
+
dialogTitleClass,
|
|
16
|
+
} from "./Dialog.variants";
|
|
17
|
+
|
|
18
|
+
const open = defineModel<boolean>("open", { default: false });
|
|
19
|
+
const props = defineProps(dialogProps);
|
|
20
|
+
const slots = useSlots();
|
|
21
|
+
|
|
22
|
+
const hasHeader = computed(() =>
|
|
23
|
+
Boolean(props.title || props.description || slots.header || props.showClose),
|
|
24
|
+
);
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<ArkDialog.Root
|
|
29
|
+
v-model:open="open"
|
|
30
|
+
:close-on-escape="props.closeOnEscape"
|
|
31
|
+
:close-on-interact-outside="props.closeOnInteractOutside"
|
|
32
|
+
lazy-mount
|
|
33
|
+
unmount-on-exit
|
|
34
|
+
>
|
|
35
|
+
<ArkDialog.Trigger v-if="$slots.trigger" as-child>
|
|
36
|
+
<slot name="trigger" />
|
|
37
|
+
</ArkDialog.Trigger>
|
|
38
|
+
<ArkDialog.Backdrop :class="dialogBackdropClass" />
|
|
39
|
+
<ArkDialog.Positioner :class="dialogPositionerClass">
|
|
40
|
+
<ArkDialog.Content :class="[dialogContentBaseClass, dialogContentSizeClasses[props.size]]">
|
|
41
|
+
<div v-if="hasHeader" :class="dialogHeaderClass">
|
|
42
|
+
<slot name="header">
|
|
43
|
+
<div class="min-w-0 flex-1">
|
|
44
|
+
<ArkDialog.Title v-if="props.title" :class="dialogTitleClass">
|
|
45
|
+
{{ props.title }}
|
|
46
|
+
</ArkDialog.Title>
|
|
47
|
+
<ArkDialog.Description v-if="props.description" :class="dialogDescriptionClass">
|
|
48
|
+
{{ props.description }}
|
|
49
|
+
</ArkDialog.Description>
|
|
50
|
+
</div>
|
|
51
|
+
</slot>
|
|
52
|
+
<ArkDialog.CloseTrigger
|
|
53
|
+
v-if="props.showClose"
|
|
54
|
+
:class="dialogCloseClass"
|
|
55
|
+
aria-label="Close dialog"
|
|
56
|
+
>
|
|
57
|
+
<svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
|
|
58
|
+
<path
|
|
59
|
+
d="M6 6l12 12M18 6 6 18"
|
|
60
|
+
stroke="currentColor"
|
|
61
|
+
stroke-width="1.75"
|
|
62
|
+
stroke-linecap="round"
|
|
63
|
+
/>
|
|
64
|
+
</svg>
|
|
65
|
+
</ArkDialog.CloseTrigger>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div :class="dialogBodyClass">
|
|
69
|
+
<slot />
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div v-if="$slots.footer" :class="dialogFooterClass">
|
|
73
|
+
<slot name="footer" />
|
|
74
|
+
</div>
|
|
75
|
+
</ArkDialog.Content>
|
|
76
|
+
</ArkDialog.Positioner>
|
|
77
|
+
</ArkDialog.Root>
|
|
78
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PropType } from "vue";
|
|
2
|
+
import type { DrawerSide, DrawerSize } from "./Drawer.types";
|
|
3
|
+
|
|
4
|
+
export const drawerProps = {
|
|
5
|
+
title: {
|
|
6
|
+
type: String,
|
|
7
|
+
default: undefined,
|
|
8
|
+
},
|
|
9
|
+
description: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: undefined,
|
|
12
|
+
},
|
|
13
|
+
side: {
|
|
14
|
+
type: String as PropType<DrawerSide>,
|
|
15
|
+
default: "right",
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
type: String as PropType<DrawerSize>,
|
|
19
|
+
default: "md",
|
|
20
|
+
},
|
|
21
|
+
showClose: {
|
|
22
|
+
type: Boolean,
|
|
23
|
+
default: true,
|
|
24
|
+
},
|
|
25
|
+
closeOnEscape: {
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: true,
|
|
28
|
+
},
|
|
29
|
+
closeOnInteractOutside: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: true,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Drawer
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Side-sheet overlay primitive for longer workflows or contextual panels.
|
|
6
|
+
|
|
7
|
+
## Category
|
|
8
|
+
|
|
9
|
+
Primitive
|
|
10
|
+
|
|
11
|
+
## Public API
|
|
12
|
+
|
|
13
|
+
Props:
|
|
14
|
+
|
|
15
|
+
- `open` via `v-model:open`
|
|
16
|
+
- `title`
|
|
17
|
+
- `description`
|
|
18
|
+
- `side`: `left | right`
|
|
19
|
+
- `size`: `sm | md | lg`
|
|
20
|
+
- `showClose`
|
|
21
|
+
- `closeOnEscape`
|
|
22
|
+
- `closeOnInteractOutside`
|
|
23
|
+
|
|
24
|
+
Slots:
|
|
25
|
+
|
|
26
|
+
- `trigger`
|
|
27
|
+
- `header`
|
|
28
|
+
- default content
|
|
29
|
+
- `footer`
|
|
30
|
+
|
|
31
|
+
## Accessibility
|
|
32
|
+
|
|
33
|
+
- built on Ark dialog semantics
|
|
34
|
+
- traps focus while open
|
|
35
|
+
- behaves like a modal side sheet rather than a resizable app-specific panel
|
|
36
|
+
|
|
37
|
+
## Keyboard behavior
|
|
38
|
+
|
|
39
|
+
- focus moves into the drawer on open
|
|
40
|
+
- Escape closes when enabled
|
|
41
|
+
|
|
42
|
+
## Example
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<Drawer v-model:open="open" title="Filters" side="right">
|
|
46
|
+
<template #trigger>
|
|
47
|
+
<Button variant="secondary">Open filters</Button>
|
|
48
|
+
</template>
|
|
49
|
+
</Drawer>
|
|
50
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DrawerSide, DrawerSize } from "./Drawer.types";
|
|
2
|
+
|
|
3
|
+
export const drawerBackdropClass = "fixed inset-0 z-40 bg-[var(--ui-fg)]/40 backdrop-blur-sm";
|
|
4
|
+
export const drawerPositionerBaseClass = "fixed inset-0 z-50 flex";
|
|
5
|
+
export const drawerPositionerSideClasses: Record<DrawerSide, string> = {
|
|
6
|
+
left: "justify-start",
|
|
7
|
+
right: "justify-end",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const drawerContentBaseClass =
|
|
11
|
+
"flex h-full w-full max-w-full flex-col overflow-hidden border-[var(--ui-border)] bg-[var(--ui-surface)] text-[var(--ui-fg)] shadow-[var(--ui-shadow-md)]";
|
|
12
|
+
|
|
13
|
+
export const drawerContentSideClasses: Record<DrawerSide, string> = {
|
|
14
|
+
left: "border-r",
|
|
15
|
+
right: "border-l",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const drawerContentSizeClasses: Record<DrawerSize, string> = {
|
|
19
|
+
sm: "max-w-md",
|
|
20
|
+
md: "max-w-lg",
|
|
21
|
+
lg: "max-w-2xl",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const drawerHeaderClass =
|
|
25
|
+
"flex items-start justify-between gap-4 border-b border-[var(--ui-border)] px-4 py-3";
|
|
26
|
+
export const drawerTitleClass = "text-base font-semibold text-[var(--ui-fg)]";
|
|
27
|
+
export const drawerDescriptionClass = "mt-1 text-sm text-[var(--ui-fg-muted)]";
|
|
28
|
+
export const drawerBodyClass = "flex-1 overflow-auto px-4 py-4";
|
|
29
|
+
export const drawerFooterClass =
|
|
30
|
+
"flex items-center justify-end gap-3 border-t border-[var(--ui-border)] px-4 py-3";
|
|
31
|
+
export const drawerCloseClass =
|
|
32
|
+
"inline-flex size-9 items-center justify-center rounded-[var(--ui-radius-sm)] border border-[var(--ui-border)] " +
|
|
33
|
+
"text-[var(--ui-fg-muted)] transition-[border-color,background-color,color] duration-[var(--ui-duration-normal)] ease-[var(--ui-ease-out)] " +
|
|
34
|
+
"hover:border-[var(--ui-primary-muted)] hover:bg-[var(--ui-tonal-secondary)] hover:text-[var(--ui-fg)] " +
|
|
35
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-ring)]";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Dialog as ArkDialog } from "@ark-ui/vue/dialog";
|
|
3
|
+
import { computed, useSlots } from "vue";
|
|
4
|
+
import { drawerProps } from "./Drawer.props";
|
|
5
|
+
import {
|
|
6
|
+
drawerBackdropClass,
|
|
7
|
+
drawerBodyClass,
|
|
8
|
+
drawerCloseClass,
|
|
9
|
+
drawerContentBaseClass,
|
|
10
|
+
drawerContentSideClasses,
|
|
11
|
+
drawerContentSizeClasses,
|
|
12
|
+
drawerDescriptionClass,
|
|
13
|
+
drawerFooterClass,
|
|
14
|
+
drawerHeaderClass,
|
|
15
|
+
drawerPositionerBaseClass,
|
|
16
|
+
drawerPositionerSideClasses,
|
|
17
|
+
drawerTitleClass,
|
|
18
|
+
} from "./Drawer.variants";
|
|
19
|
+
|
|
20
|
+
const open = defineModel<boolean>("open", { default: false });
|
|
21
|
+
const props = defineProps(drawerProps);
|
|
22
|
+
const slots = useSlots();
|
|
23
|
+
|
|
24
|
+
const hasHeader = computed(() =>
|
|
25
|
+
Boolean(props.title || props.description || slots.header || props.showClose),
|
|
26
|
+
);
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<ArkDialog.Root
|
|
31
|
+
v-model:open="open"
|
|
32
|
+
:close-on-escape="props.closeOnEscape"
|
|
33
|
+
:close-on-interact-outside="props.closeOnInteractOutside"
|
|
34
|
+
lazy-mount
|
|
35
|
+
unmount-on-exit
|
|
36
|
+
>
|
|
37
|
+
<ArkDialog.Trigger v-if="$slots.trigger" as-child>
|
|
38
|
+
<slot name="trigger" />
|
|
39
|
+
</ArkDialog.Trigger>
|
|
40
|
+
<ArkDialog.Backdrop :class="drawerBackdropClass" />
|
|
41
|
+
<ArkDialog.Positioner
|
|
42
|
+
:class="[drawerPositionerBaseClass, drawerPositionerSideClasses[props.side]]"
|
|
43
|
+
>
|
|
44
|
+
<ArkDialog.Content
|
|
45
|
+
:class="[
|
|
46
|
+
drawerContentBaseClass,
|
|
47
|
+
drawerContentSideClasses[props.side],
|
|
48
|
+
drawerContentSizeClasses[props.size],
|
|
49
|
+
]"
|
|
50
|
+
>
|
|
51
|
+
<div v-if="hasHeader" :class="drawerHeaderClass">
|
|
52
|
+
<slot name="header">
|
|
53
|
+
<div class="min-w-0 flex-1">
|
|
54
|
+
<ArkDialog.Title v-if="props.title" :class="drawerTitleClass">
|
|
55
|
+
{{ props.title }}
|
|
56
|
+
</ArkDialog.Title>
|
|
57
|
+
<ArkDialog.Description v-if="props.description" :class="drawerDescriptionClass">
|
|
58
|
+
{{ props.description }}
|
|
59
|
+
</ArkDialog.Description>
|
|
60
|
+
</div>
|
|
61
|
+
</slot>
|
|
62
|
+
<ArkDialog.CloseTrigger
|
|
63
|
+
v-if="props.showClose"
|
|
64
|
+
:class="drawerCloseClass"
|
|
65
|
+
aria-label="Close drawer"
|
|
66
|
+
>
|
|
67
|
+
<svg viewBox="0 0 24 24" fill="none" class="size-4" aria-hidden="true">
|
|
68
|
+
<path
|
|
69
|
+
d="M6 6l12 12M18 6 6 18"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
stroke-width="1.75"
|
|
72
|
+
stroke-linecap="round"
|
|
73
|
+
/>
|
|
74
|
+
</svg>
|
|
75
|
+
</ArkDialog.CloseTrigger>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div :class="drawerBodyClass">
|
|
79
|
+
<slot />
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div v-if="$slots.footer" :class="drawerFooterClass">
|
|
83
|
+
<slot name="footer" />
|
|
84
|
+
</div>
|
|
85
|
+
</ArkDialog.Content>
|
|
86
|
+
</ArkDialog.Positioner>
|
|
87
|
+
</ArkDialog.Root>
|
|
88
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PropType } from "vue";
|
|
2
|
+
import type { FieldMessageSize, FieldMessageTone } from "./FieldMessage.types";
|
|
3
|
+
|
|
4
|
+
export const fieldMessageProps = {
|
|
5
|
+
id: {
|
|
6
|
+
type: String,
|
|
7
|
+
default: undefined,
|
|
8
|
+
},
|
|
9
|
+
size: {
|
|
10
|
+
type: String as PropType<FieldMessageSize>,
|
|
11
|
+
default: "md",
|
|
12
|
+
},
|
|
13
|
+
tone: {
|
|
14
|
+
type: String as PropType<FieldMessageTone>,
|
|
15
|
+
default: "muted",
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# FieldMessage
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Supporting field copy primitive for descriptions, hints, and validation messages.
|
|
6
|
+
|
|
7
|
+
## Category
|
|
8
|
+
|
|
9
|
+
Primitive
|
|
10
|
+
|
|
11
|
+
## Public API
|
|
12
|
+
|
|
13
|
+
Props:
|
|
14
|
+
|
|
15
|
+
- `id`: string
|
|
16
|
+
- `size`: `sm | md | lg`
|
|
17
|
+
- `tone`: `muted | critical`
|
|
18
|
+
|
|
19
|
+
## Accessibility
|
|
20
|
+
|
|
21
|
+
- uses a simple text container
|
|
22
|
+
- critical messages use `role="alert"`
|
|
23
|
+
- designed to be referenced from controls via `aria-describedby`
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
```vue
|
|
28
|
+
<FieldMessage id="email-help">We will never share your email.</FieldMessage>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```vue
|
|
32
|
+
<FieldMessage id="email-error" tone="critical">
|
|
33
|
+
Enter a valid email address.
|
|
34
|
+
</FieldMessage>
|
|
35
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const fieldMessageSizeValues = ["sm", "md", "lg"] as const;
|
|
2
|
+
export const fieldMessageToneValues = ["muted", "critical"] as const;
|
|
3
|
+
|
|
4
|
+
export type FieldMessageSize = (typeof fieldMessageSizeValues)[number];
|
|
5
|
+
export type FieldMessageTone = (typeof fieldMessageToneValues)[number];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FieldMessageSize, FieldMessageTone } from "./FieldMessage.types";
|
|
2
|
+
|
|
3
|
+
export const fieldMessageBaseClass = "text-[var(--ui-fg-muted)]";
|
|
4
|
+
|
|
5
|
+
export const fieldMessageSizeClasses: Record<FieldMessageSize, string> = {
|
|
6
|
+
sm: "text-xs",
|
|
7
|
+
md: "text-sm",
|
|
8
|
+
lg: "text-base",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const fieldMessageToneClasses: Record<FieldMessageTone, string> = {
|
|
12
|
+
muted: "text-[var(--ui-fg-muted)]",
|
|
13
|
+
critical: "text-[var(--ui-critical)]",
|
|
14
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, normalizeClass, useAttrs } from "vue";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
import { fieldMessageProps } from "./FieldMessage.props";
|
|
5
|
+
import {
|
|
6
|
+
fieldMessageBaseClass,
|
|
7
|
+
fieldMessageSizeClasses,
|
|
8
|
+
fieldMessageToneClasses,
|
|
9
|
+
} from "./FieldMessage.variants";
|
|
10
|
+
|
|
11
|
+
defineOptions({ inheritAttrs: false });
|
|
12
|
+
|
|
13
|
+
const attrs = useAttrs();
|
|
14
|
+
const props = defineProps(fieldMessageProps);
|
|
15
|
+
|
|
16
|
+
const classes = computed(() =>
|
|
17
|
+
twMerge(
|
|
18
|
+
fieldMessageBaseClass,
|
|
19
|
+
fieldMessageSizeClasses[props.size],
|
|
20
|
+
fieldMessageToneClasses[props.tone],
|
|
21
|
+
normalizeClass(attrs.class),
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const fieldMessageAttrs = computed(() => {
|
|
26
|
+
const { class: _class, role: _role, ...rest } = attrs;
|
|
27
|
+
return rest;
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<p
|
|
33
|
+
:id="props.id"
|
|
34
|
+
:class="classes"
|
|
35
|
+
:role="props.tone === 'critical' ? 'alert' : undefined"
|
|
36
|
+
v-bind="fieldMessageAttrs"
|
|
37
|
+
>
|
|
38
|
+
<slot />
|
|
39
|
+
</p>
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PropType } from "vue";
|
|
2
|
+
import type { FileInputSize, FileInputValue, FileInputVariant } from "./FileInput.types";
|
|
3
|
+
|
|
4
|
+
export const fileInputProps = {
|
|
5
|
+
modelValue: {
|
|
6
|
+
type: [Object, Array, null] as PropType<FileInputValue>,
|
|
7
|
+
default: null,
|
|
8
|
+
},
|
|
9
|
+
accept: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: undefined,
|
|
12
|
+
},
|
|
13
|
+
multiple: {
|
|
14
|
+
type: Boolean,
|
|
15
|
+
default: false,
|
|
16
|
+
},
|
|
17
|
+
disabled: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
invalid: {
|
|
22
|
+
type: Boolean,
|
|
23
|
+
default: false,
|
|
24
|
+
},
|
|
25
|
+
size: {
|
|
26
|
+
type: String as PropType<FileInputSize>,
|
|
27
|
+
default: "md",
|
|
28
|
+
},
|
|
29
|
+
variant: {
|
|
30
|
+
type: String as PropType<FileInputVariant>,
|
|
31
|
+
default: "default",
|
|
32
|
+
},
|
|
33
|
+
placeholder: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: "Choose file",
|
|
36
|
+
},
|
|
37
|
+
dropzoneTitle: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: "Drop files here or click to browse",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const fileInputVariantValues = ["default", "dropzone"] as const;
|
|
2
|
+
export const fileInputSizeValues = ["sm", "md", "lg"] as const;
|
|
3
|
+
|
|
4
|
+
export type FileInputVariant = (typeof fileInputVariantValues)[number];
|
|
5
|
+
export type FileInputSize = (typeof fileInputSizeValues)[number];
|
|
6
|
+
export type FileInputValue = File | File[] | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FileInputSize } from "./FileInput.types";
|
|
2
|
+
|
|
3
|
+
export const fileInputRootClass = "flex w-full flex-col gap-3";
|
|
4
|
+
|
|
5
|
+
export const fileInputButtonBaseClass =
|
|
6
|
+
"inline-flex w-full items-center gap-3 rounded-[var(--ui-radius-md)] border bg-[var(--ui-surface)] " +
|
|
7
|
+
"text-left text-[var(--ui-fg)] transition-[background-color,border-color,box-shadow,color] " +
|
|
8
|
+
"duration-[var(--ui-duration-normal)] ease-[var(--ui-ease-out)] " +
|
|
9
|
+
"border-[var(--ui-border)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-ring)] focus-visible:border-[var(--ui-primary)]";
|
|
10
|
+
|
|
11
|
+
export const fileInputButtonStateClasses = {
|
|
12
|
+
default: "hover:border-[var(--ui-primary-muted)] hover:bg-[var(--ui-surface-hover)]",
|
|
13
|
+
invalid:
|
|
14
|
+
"border-[var(--ui-critical)] focus-visible:border-[var(--ui-critical)] focus-visible:ring-[var(--ui-critical-muted)]",
|
|
15
|
+
disabled: "pointer-events-none cursor-not-allowed opacity-50",
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export const fileInputButtonSizeClasses: Record<FileInputSize, string> = {
|
|
19
|
+
sm: "min-h-10 px-3 py-2 text-sm",
|
|
20
|
+
md: "min-h-11 px-4 py-3 text-sm",
|
|
21
|
+
lg: "min-h-12 px-4 py-3 text-base",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const fileInputTextMutedClass = "text-[var(--ui-fg-muted)]";
|
|
25
|
+
export const fileInputTextStrongClass = "font-medium text-[var(--ui-fg)]";
|
|
26
|
+
export const fileInputIconClass =
|
|
27
|
+
"inline-flex size-10 shrink-0 items-center justify-center rounded-[var(--ui-radius-md)] border border-[var(--ui-border)] bg-[var(--ui-tonal-secondary)] text-[var(--ui-primary)]";
|
|
28
|
+
|
|
29
|
+
export const fileInputDropzoneBaseClass =
|
|
30
|
+
"flex w-full flex-col items-center justify-center rounded-[var(--ui-radius-lg)] border border-dashed " +
|
|
31
|
+
"border-[var(--ui-border)] bg-[var(--ui-tonal-tertiary)] text-center transition-[background-color,border-color,box-shadow,color] " +
|
|
32
|
+
"duration-[var(--ui-duration-normal)] ease-[var(--ui-ease-out)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-ring)] focus-visible:border-[var(--ui-primary)]";
|
|
33
|
+
|
|
34
|
+
export const fileInputDropzoneStateClasses = {
|
|
35
|
+
default: "hover:border-[var(--ui-primary-muted)] hover:bg-[var(--ui-tonal-secondary)]",
|
|
36
|
+
drag: "border-[var(--ui-primary)] bg-[var(--ui-primary-muted)]",
|
|
37
|
+
invalid:
|
|
38
|
+
"border-[var(--ui-critical)] focus-visible:border-[var(--ui-critical)] focus-visible:ring-[var(--ui-critical-muted)]",
|
|
39
|
+
disabled: "pointer-events-none cursor-not-allowed opacity-50",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export const fileInputDropzoneSizeClasses: Record<FileInputSize, string> = {
|
|
43
|
+
sm: "min-h-36 px-4 py-5",
|
|
44
|
+
md: "min-h-44 px-6 py-6",
|
|
45
|
+
lg: "min-h-52 px-8 py-8",
|
|
46
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, normalizeClass, ref, useAttrs, useId } from "vue";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
import { fileInputProps } from "./FileInput.props";
|
|
5
|
+
import {
|
|
6
|
+
fileInputButtonBaseClass,
|
|
7
|
+
fileInputButtonSizeClasses,
|
|
8
|
+
fileInputButtonStateClasses,
|
|
9
|
+
fileInputDropzoneBaseClass,
|
|
10
|
+
fileInputDropzoneSizeClasses,
|
|
11
|
+
fileInputDropzoneStateClasses,
|
|
12
|
+
fileInputIconClass,
|
|
13
|
+
fileInputRootClass,
|
|
14
|
+
fileInputTextMutedClass,
|
|
15
|
+
fileInputTextStrongClass,
|
|
16
|
+
} from "./FileInput.variants";
|
|
17
|
+
import type { FileInputValue } from "./FileInput.types";
|
|
18
|
+
|
|
19
|
+
defineOptions({ inheritAttrs: false });
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
"update:modelValue": [value: FileInputValue];
|
|
23
|
+
"update:filename": [value: string];
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
const attrs = useAttrs();
|
|
27
|
+
const props = defineProps(fileInputProps);
|
|
28
|
+
|
|
29
|
+
const inputId = useId();
|
|
30
|
+
const isDragOver = ref(false);
|
|
31
|
+
|
|
32
|
+
const rootClass = computed(() => twMerge(fileInputRootClass, normalizeClass(attrs.class)));
|
|
33
|
+
|
|
34
|
+
const rootAttrs = computed(() => {
|
|
35
|
+
const { class: _class, ...rest } = attrs;
|
|
36
|
+
return rest;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const selectedLabel = computed(() => {
|
|
40
|
+
const value = props.modelValue;
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return value.length ? value.map((item) => item.name).join(", ") : props.placeholder;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return value?.name || props.placeholder;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const defaultButtonClass = computed(() =>
|
|
50
|
+
twMerge(
|
|
51
|
+
fileInputButtonBaseClass,
|
|
52
|
+
fileInputButtonSizeClasses[props.size],
|
|
53
|
+
props.invalid ? fileInputButtonStateClasses.invalid : fileInputButtonStateClasses.default,
|
|
54
|
+
props.disabled ? fileInputButtonStateClasses.disabled : "",
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const dropzoneClass = computed(() =>
|
|
59
|
+
twMerge(
|
|
60
|
+
fileInputDropzoneBaseClass,
|
|
61
|
+
fileInputDropzoneSizeClasses[props.size],
|
|
62
|
+
props.invalid ? fileInputDropzoneStateClasses.invalid : fileInputDropzoneStateClasses.default,
|
|
63
|
+
isDragOver.value ? fileInputDropzoneStateClasses.drag : "",
|
|
64
|
+
props.disabled ? fileInputDropzoneStateClasses.disabled : "",
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
function emitFiles(fileList: FileList | File[]) {
|
|
69
|
+
const files = Array.from(fileList);
|
|
70
|
+
|
|
71
|
+
if (!files.length) {
|
|
72
|
+
emit("update:modelValue", null);
|
|
73
|
+
emit("update:filename", "");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (props.multiple) {
|
|
78
|
+
emit("update:modelValue", files);
|
|
79
|
+
emit("update:filename", files.map((file) => file.name).join(", "));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
emit("update:modelValue", files[0] ?? null);
|
|
84
|
+
emit("update:filename", files[0]?.name ?? "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function handleInput(event: Event) {
|
|
88
|
+
const target = event.target as HTMLInputElement;
|
|
89
|
+
emitFiles(target.files ?? []);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleDragOver(event: DragEvent) {
|
|
93
|
+
if (props.disabled) return;
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
isDragOver.value = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleDragLeave() {
|
|
99
|
+
isDragOver.value = false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handleDrop(event: DragEvent) {
|
|
103
|
+
if (props.disabled) return;
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
isDragOver.value = false;
|
|
106
|
+
emitFiles(event.dataTransfer?.files ?? []);
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<template>
|
|
111
|
+
<div :class="rootClass" v-bind="rootAttrs">
|
|
112
|
+
<input
|
|
113
|
+
:id="inputId"
|
|
114
|
+
class="sr-only"
|
|
115
|
+
type="file"
|
|
116
|
+
:accept="props.accept"
|
|
117
|
+
:multiple="props.multiple"
|
|
118
|
+
:disabled="props.disabled"
|
|
119
|
+
@change="handleInput"
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
<label
|
|
123
|
+
v-if="props.variant === 'dropzone'"
|
|
124
|
+
:for="inputId"
|
|
125
|
+
:class="dropzoneClass"
|
|
126
|
+
@dragover="handleDragOver"
|
|
127
|
+
@dragleave="handleDragLeave"
|
|
128
|
+
@drop="handleDrop"
|
|
129
|
+
>
|
|
130
|
+
<span :class="fileInputIconClass" aria-hidden="true">
|
|
131
|
+
<svg viewBox="0 0 24 24" fill="none" class="size-5">
|
|
132
|
+
<path
|
|
133
|
+
d="M12 16.5V9.75m0 0 3 3m-3-3-3 3M6.75 19.5a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z"
|
|
134
|
+
stroke="currentColor"
|
|
135
|
+
stroke-width="1.5"
|
|
136
|
+
stroke-linecap="round"
|
|
137
|
+
stroke-linejoin="round"
|
|
138
|
+
/>
|
|
139
|
+
</svg>
|
|
140
|
+
</span>
|
|
141
|
+
<span :class="fileInputTextStrongClass">{{ props.dropzoneTitle }}</span>
|
|
142
|
+
<span :class="fileInputTextMutedClass">{{ selectedLabel }}</span>
|
|
143
|
+
</label>
|
|
144
|
+
|
|
145
|
+
<label v-else :for="inputId" :class="defaultButtonClass">
|
|
146
|
+
<span :class="fileInputIconClass" aria-hidden="true">
|
|
147
|
+
<svg viewBox="0 0 24 24" fill="none" class="size-5">
|
|
148
|
+
<path
|
|
149
|
+
d="M12 16.5V9.75m0 0 3 3m-3-3-3 3M6.75 19.5a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z"
|
|
150
|
+
stroke="currentColor"
|
|
151
|
+
stroke-width="1.5"
|
|
152
|
+
stroke-linecap="round"
|
|
153
|
+
stroke-linejoin="round"
|
|
154
|
+
/>
|
|
155
|
+
</svg>
|
|
156
|
+
</span>
|
|
157
|
+
<span class="min-w-0">
|
|
158
|
+
<span :class="fileInputTextStrongClass">Upload file</span>
|
|
159
|
+
<span class="block truncate" :class="fileInputTextMutedClass">{{ selectedLabel }}</span>
|
|
160
|
+
</span>
|
|
161
|
+
</label>
|
|
162
|
+
</div>
|
|
163
|
+
</template>
|