mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-18 11:36:36 +00:00
feat: add ImageLoader component
This commit is contained in:
parent
a222d23037
commit
0dba95affd
11 changed files with 380 additions and 3 deletions
|
@ -1,6 +1,6 @@
|
||||||
# Component Index
|
# Component Index
|
||||||
|
|
||||||
> 158 components exported from carbon-components-svelte@0.29.2.
|
> 159 components exported from carbon-components-svelte@0.29.2.
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@
|
||||||
- [`HeaderUtilities`](#headerutilities)
|
- [`HeaderUtilities`](#headerutilities)
|
||||||
- [`Icon`](#icon)
|
- [`Icon`](#icon)
|
||||||
- [`IconSkeleton`](#iconskeleton)
|
- [`IconSkeleton`](#iconskeleton)
|
||||||
|
- [`ImageLoader`](#imageloader)
|
||||||
- [`InlineLoading`](#inlineloading)
|
- [`InlineLoading`](#inlineloading)
|
||||||
- [`InlineNotification`](#inlinenotification)
|
- [`InlineNotification`](#inlinenotification)
|
||||||
- [`Link`](#link)
|
- [`Link`](#link)
|
||||||
|
@ -1724,6 +1725,35 @@ None.
|
||||||
| mouseenter | forwarded | -- |
|
| mouseenter | forwarded | -- |
|
||||||
| mouseleave | forwarded | -- |
|
| mouseleave | forwarded | -- |
|
||||||
|
|
||||||
|
## `ImageLoader`
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
| Prop name | Kind | Reactive | Type | Default value | Description |
|
||||||
|
| :-------- | :----------------- | :------- | :------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| error | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` if an error occurs when loading the image |
|
||||||
|
| loaded | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` when the image is loaded |
|
||||||
|
| loading | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` when `loaded` is `true` and `error` is false |
|
||||||
|
| src | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the image source |
|
||||||
|
| alt | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the image alt text |
|
||||||
|
| ratio | <code>let</code> | No | <code>"2x1" | "16x9" | "4x3" | "1x1" | "3x4" | "9x16" | "1x2"</code> | -- | Specify the aspect ratio for the image wrapper |
|
||||||
|
| fadeIn | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to fade in the image on load<br />The duration uses the `fast-02` value following Carbon guidelines on motion |
|
||||||
|
| loadImage | <code>const</code> | No | <code>(url?: string) => void</code> | <code>(url) => { if (image != null) image = null; loaded = false; error = false; image = new Image(); image.src = url || src; image.onload = () => (loaded = true); image.onerror = () => (error = true); }</code> | Method invoked to load the image provided a `src` value |
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
| Slot name | Default | Props | Fallback |
|
||||||
|
| :-------- | :------ | :---- | :------- |
|
||||||
|
| error | No | -- | -- |
|
||||||
|
| loading | No | -- | -- |
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| Event name | Type | Detail |
|
||||||
|
| :--------- | :--------- | :--------------- |
|
||||||
|
| load | dispatched | <code>any</code> |
|
||||||
|
| error | dispatched | <code>any</code> |
|
||||||
|
|
||||||
## `InlineLoading`
|
## `InlineLoading`
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"total": 158,
|
"total": 159,
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"moduleName": "Accordion",
|
"moduleName": "Accordion",
|
||||||
|
@ -4052,6 +4052,101 @@
|
||||||
"typedefs": [],
|
"typedefs": [],
|
||||||
"rest_props": { "type": "Element", "name": "div" }
|
"rest_props": { "type": "Element", "name": "div" }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "ImageLoader",
|
||||||
|
"filePath": "src/ImageLoader/ImageLoader.svelte",
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"name": "src",
|
||||||
|
"kind": "let",
|
||||||
|
"description": "Specify the image source",
|
||||||
|
"type": "string",
|
||||||
|
"value": "\"\"",
|
||||||
|
"isFunction": false,
|
||||||
|
"constant": false,
|
||||||
|
"reactive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alt",
|
||||||
|
"kind": "let",
|
||||||
|
"description": "Specify the image alt text",
|
||||||
|
"type": "string",
|
||||||
|
"value": "\"\"",
|
||||||
|
"isFunction": false,
|
||||||
|
"constant": false,
|
||||||
|
"reactive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ratio",
|
||||||
|
"kind": "let",
|
||||||
|
"description": "Specify the aspect ratio for the image wrapper",
|
||||||
|
"type": "\"2x1\" | \"16x9\" | \"4x3\" | \"1x1\" | \"3x4\" | \"9x16\" | \"1x2\"",
|
||||||
|
"isFunction": false,
|
||||||
|
"constant": false,
|
||||||
|
"reactive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "loading",
|
||||||
|
"kind": "let",
|
||||||
|
"description": "Set to `true` when `loaded` is `true` and `error` is false",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": "false",
|
||||||
|
"isFunction": false,
|
||||||
|
"constant": false,
|
||||||
|
"reactive": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "loaded",
|
||||||
|
"kind": "let",
|
||||||
|
"description": "Set to `true` when the image is loaded",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": "false",
|
||||||
|
"isFunction": false,
|
||||||
|
"constant": false,
|
||||||
|
"reactive": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "error",
|
||||||
|
"kind": "let",
|
||||||
|
"description": "Set to `true` if an error occurs when loading the image",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": "false",
|
||||||
|
"isFunction": false,
|
||||||
|
"constant": false,
|
||||||
|
"reactive": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fadeIn",
|
||||||
|
"kind": "let",
|
||||||
|
"description": "Set to `true` to fade in the image on load\nThe duration uses the `fast-02` value following Carbon guidelines on motion",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": "false",
|
||||||
|
"isFunction": false,
|
||||||
|
"constant": false,
|
||||||
|
"reactive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "loadImage",
|
||||||
|
"kind": "const",
|
||||||
|
"description": "Method invoked to load the image provided a `src` value",
|
||||||
|
"type": "(url?: string) => void",
|
||||||
|
"value": "(url) => { if (image != null) image = null; loaded = false; error = false; image = new Image(); image.src = url || src; image.onload = () => (loaded = true); image.onerror = () => (error = true); }",
|
||||||
|
"isFunction": true,
|
||||||
|
"constant": true,
|
||||||
|
"reactive": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"slots": [
|
||||||
|
{ "name": "error", "default": false, "slot_props": "{}" },
|
||||||
|
{ "name": "loading", "default": false, "slot_props": "{}" }
|
||||||
|
],
|
||||||
|
"events": [
|
||||||
|
{ "type": "dispatched", "name": "load", "detail": "any" },
|
||||||
|
{ "type": "dispatched", "name": "error", "detail": "any" }
|
||||||
|
],
|
||||||
|
"typedefs": [],
|
||||||
|
"rest_props": { "type": "Element", "name": "img" }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "InlineLoading",
|
"moduleName": "InlineLoading",
|
||||||
"filePath": "src/InlineLoading/InlineLoading.svelte",
|
"filePath": "src/InlineLoading/InlineLoading.svelte",
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import Footer from "../components/Footer.svelte";
|
import Footer from "../components/Footer.svelte";
|
||||||
|
|
||||||
const deprecated = ["ToggleSmall", "Icon"];
|
const deprecated = ["ToggleSmall", "Icon"];
|
||||||
|
const new_components = ["ImageLoader"];
|
||||||
|
|
||||||
let isOpen = false;
|
let isOpen = false;
|
||||||
let isSideNavOpen = true;
|
let isSideNavOpen = true;
|
||||||
|
@ -106,6 +107,9 @@
|
||||||
{#if deprecated.includes(child.title)}
|
{#if deprecated.includes(child.title)}
|
||||||
<Tag size="sm" type="red">Deprecated</Tag>
|
<Tag size="sm" type="red">Deprecated</Tag>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if new_components.includes(child.title)}
|
||||||
|
<Tag size="sm" type="blue">New</Tag>
|
||||||
|
{/if}
|
||||||
</SideNavMenuItem>
|
</SideNavMenuItem>
|
||||||
{/each}
|
{/each}
|
||||||
</SideNavMenu>
|
</SideNavMenu>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
The `AspectRatio` component is useful for constraining fluid content within an aspect ratio. To demo this, resize your browser for the examples below.
|
The `AspectRatio` component is useful for constraining fluid content within an aspect ratio. To demo this, resize your browser for the examples below.
|
||||||
|
|
||||||
Supported aspect ratios: `"2x1"`, `"16x9"`, `"4x3"`, `"1x1"`, `"3x4"`, `"9x16"`, `"1x2"`
|
Supported aspect ratios include `"2x1"`, `"16x9"`, `"4x3"`, `"1x1"`, `"3x4"`, `"9x16"` and `"1x2"`.
|
||||||
|
|
||||||
### Default (2x1)
|
### Default (2x1)
|
||||||
|
|
||||||
|
|
46
docs/src/pages/components/ImageLoader.svx
Normal file
46
docs/src/pages/components/ImageLoader.svx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<script>
|
||||||
|
import { ImageLoader, Button, InlineLoading } from "carbon-components-svelte";
|
||||||
|
import Preview from "../../components/Preview.svelte";
|
||||||
|
|
||||||
|
let key = 0;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
This utility component uses the [Image API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image) to programmatically load an image with slottable loading and error states.
|
||||||
|
|
||||||
|
### Default
|
||||||
|
|
||||||
|
<ImageLoader src="https://upload.wikimedia.org/wikipedia/commons/5/51/IBM_logo.svg" />
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
Use the "loading" and "error" named slots to render an element when the image is loading or has an error.
|
||||||
|
|
||||||
|
<ImageLoader src="https://upload.wikimedia.org/wikipedia/commons/5/51/IBM_logo.svg">
|
||||||
|
<div slot="loading">
|
||||||
|
<InlineLoading />
|
||||||
|
</div>
|
||||||
|
<div slot="error">
|
||||||
|
An error occurred.
|
||||||
|
</div>
|
||||||
|
</ImageLoader>
|
||||||
|
|
||||||
|
### With aspect ratio
|
||||||
|
|
||||||
|
If `ratio` is set, this component uses the [AspectRatio](/components/AspectRatio) to constrain the image.
|
||||||
|
|
||||||
|
Supported aspect ratios include `"2x1"`, `"16x9"`, `"4x3"`, `"1x1"`, `"3x4"`, `"9x16"` and `"1x2"`.
|
||||||
|
|
||||||
|
<ImageLoader ratio="16x9" src="https://upload.wikimedia.org/wikipedia/commons/5/51/IBM_logo.svg" />
|
||||||
|
|
||||||
|
### Fade in
|
||||||
|
|
||||||
|
Set `fadeIn` to `true` to fade in the image if successfully loaded.
|
||||||
|
|
||||||
|
<Button kind="ghost" on:click="{() => { key++;}}">Reload image</Button>
|
||||||
|
{#key key}<ImageLoader fadeIn ratio="16x9" src="https://upload.wikimedia.org/wikipedia/commons/5/51/IBM_logo.svg" />{/key}
|
||||||
|
|
||||||
|
### Programmatic usage
|
||||||
|
|
||||||
|
In this example, a component reference is obtained to programmatically trigger the `loadImage` method.
|
||||||
|
|
||||||
|
<FileSource src="/framed/ImageLoader/ProgrammaticImageLoader" />
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
import { ImageLoader, Button } from "carbon-components-svelte";
|
||||||
|
|
||||||
|
const src =
|
||||||
|
"https://upload.wikimedia.org/wikipedia/commons/5/51/IBM_logo.svg";
|
||||||
|
const srcError = src + "1";
|
||||||
|
|
||||||
|
let imageLoader;
|
||||||
|
let error;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
kind="ghost"
|
||||||
|
disabled="{!imageLoader || error}"
|
||||||
|
on:click="{() => imageLoader.loadImage(srcError)}"
|
||||||
|
>
|
||||||
|
Simulate error
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ImageLoader bind:this="{imageLoader}" bind:error fadeIn src="{src}">
|
||||||
|
<div slot="error">
|
||||||
|
<Button kind="ghost" on:click="{() => imageLoader.loadImage(src)}">
|
||||||
|
Error. Try again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</ImageLoader>
|
113
src/ImageLoader/ImageLoader.svelte
Normal file
113
src/ImageLoader/ImageLoader.svelte
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* @event {any} load
|
||||||
|
* @event {any} error
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the image source
|
||||||
|
*/
|
||||||
|
export let src = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the image alt text
|
||||||
|
*/
|
||||||
|
export let alt = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the aspect ratio for the image wrapper
|
||||||
|
* @type {"2x1" | "16x9" | "4x3" | "1x1" | "3x4" | "9x16" | "1x2"}
|
||||||
|
*/
|
||||||
|
export let ratio = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` when `loaded` is `true` and `error` is false
|
||||||
|
*/
|
||||||
|
export let loading = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` when the image is loaded
|
||||||
|
*/
|
||||||
|
export let loaded = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` if an error occurs when loading the image
|
||||||
|
*/
|
||||||
|
export let error = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` to fade in the image on load
|
||||||
|
* The duration uses the `fast-02` value following Carbon guidelines on motion
|
||||||
|
*/
|
||||||
|
export let fadeIn = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method invoked to load the image provided a `src` value
|
||||||
|
* @type {(url?: string) => void}
|
||||||
|
*/
|
||||||
|
export const loadImage = (url) => {
|
||||||
|
if (image != null) image = null;
|
||||||
|
loaded = false;
|
||||||
|
error = false;
|
||||||
|
image = new Image();
|
||||||
|
image.src = url || src;
|
||||||
|
image.onload = () => (loaded = true);
|
||||||
|
image.onerror = () => (error = true);
|
||||||
|
};
|
||||||
|
|
||||||
|
import { onMount, createEventDispatcher } from "svelte";
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import AspectRatio from "../AspectRatio/AspectRatio.svelte";
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
// "fast-02" duration (ms) from Carbon motion recommended for fading micro-interactions
|
||||||
|
const fast02 = 110;
|
||||||
|
|
||||||
|
let image = null;
|
||||||
|
|
||||||
|
$: loading = !loaded && !error;
|
||||||
|
$: if (src) loadImage();
|
||||||
|
$: if (loaded) dispatch("load");
|
||||||
|
$: if (error) dispatch("error");
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
return () => (image = null);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if ratio === undefined}
|
||||||
|
{#if loading}
|
||||||
|
<slot name="loading" />
|
||||||
|
{/if}
|
||||||
|
{#if loaded}
|
||||||
|
<img
|
||||||
|
{...$$restProps}
|
||||||
|
style="width: 100%;{$$restProps.style}"
|
||||||
|
src="{src}"
|
||||||
|
alt="{alt}"
|
||||||
|
transition:fade="{{ duration: fadeIn ? fast02 : 0 }}"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if error}
|
||||||
|
<slot name="error" />
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<AspectRatio ratio="{ratio}">
|
||||||
|
{#if loading}
|
||||||
|
<slot name="loading" />
|
||||||
|
{/if}
|
||||||
|
{#if loaded}
|
||||||
|
<img
|
||||||
|
{...$$restProps}
|
||||||
|
style="width: 100%;{$$restProps.style}"
|
||||||
|
src="{src}"
|
||||||
|
alt="{alt}"
|
||||||
|
transition:fade="{{ duration: fadeIn ? fast02 : 0 }}"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if error}
|
||||||
|
<slot name="error" />
|
||||||
|
{/if}
|
||||||
|
</AspectRatio>
|
||||||
|
{/if}
|
1
src/ImageLoader/index.js
Normal file
1
src/ImageLoader/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default as ImageLoader } from "./ImageLoader.svelte";
|
|
@ -48,6 +48,7 @@ export { FormItem } from "./FormItem";
|
||||||
export { FormLabel } from "./FormLabel";
|
export { FormLabel } from "./FormLabel";
|
||||||
export { Grid, Row, Column } from "./Grid";
|
export { Grid, Row, Column } from "./Grid";
|
||||||
export { Icon, IconSkeleton } from "./Icon";
|
export { Icon, IconSkeleton } from "./Icon";
|
||||||
|
export { ImageLoader } from "./ImageLoader";
|
||||||
export { InlineLoading } from "./InlineLoading";
|
export { InlineLoading } from "./InlineLoading";
|
||||||
export { Link, OutboundLink } from "./Link";
|
export { Link, OutboundLink } from "./Link";
|
||||||
export {
|
export {
|
||||||
|
|
60
types/ImageLoader/ImageLoader.d.ts
vendored
Normal file
60
types/ImageLoader/ImageLoader.d.ts
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/// <reference types="svelte" />
|
||||||
|
import { SvelteComponentTyped } from "svelte";
|
||||||
|
|
||||||
|
export interface ImageLoaderProps
|
||||||
|
extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["img"]> {
|
||||||
|
/**
|
||||||
|
* Specify the image source
|
||||||
|
* @default ""
|
||||||
|
*/
|
||||||
|
src?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the image alt text
|
||||||
|
* @default ""
|
||||||
|
*/
|
||||||
|
alt?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the aspect ratio for the image wrapper
|
||||||
|
*/
|
||||||
|
ratio?: "2x1" | "16x9" | "4x3" | "1x1" | "3x4" | "9x16" | "1x2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` when `loaded` is `true` and `error` is false
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
loading?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` when the image is loaded
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
loaded?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` if an error occurs when loading the image
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
error?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` to fade in the image on load
|
||||||
|
* The duration uses the `fast-02` value following Carbon guidelines on motion
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
fadeIn?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method invoked to load the image provided a `src` value
|
||||||
|
* @constant
|
||||||
|
* @default (url) => { if (image != null) image = null; loaded = false; error = false; image = new Image(); image.src = url || src; image.onload = () => (loaded = true); image.onerror = () => (error = true); }
|
||||||
|
*/
|
||||||
|
loadImage?: (url?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ImageLoader extends SvelteComponentTyped<
|
||||||
|
ImageLoaderProps,
|
||||||
|
{ load: CustomEvent<any>; error: CustomEvent<any> },
|
||||||
|
{ error: {}; loading: {} }
|
||||||
|
> {}
|
1
types/index.d.ts
vendored
1
types/index.d.ts
vendored
|
@ -57,6 +57,7 @@ export { default as Row } from "./Grid/Row";
|
||||||
export { default as Column } from "./Grid/Column";
|
export { default as Column } from "./Grid/Column";
|
||||||
export { default as Icon } from "./Icon/Icon";
|
export { default as Icon } from "./Icon/Icon";
|
||||||
export { default as IconSkeleton } from "./Icon/IconSkeleton";
|
export { default as IconSkeleton } from "./Icon/IconSkeleton";
|
||||||
|
export { default as ImageLoader } from "./ImageLoader/ImageLoader";
|
||||||
export { default as InlineLoading } from "./InlineLoading/InlineLoading";
|
export { default as InlineLoading } from "./InlineLoading/InlineLoading";
|
||||||
export { default as Link } from "./Link/Link";
|
export { default as Link } from "./Link/Link";
|
||||||
export { default as OutboundLink } from "./Link/OutboundLink";
|
export { default as OutboundLink } from "./Link/OutboundLink";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue