mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-14 18:01:06 +00:00
Merge pull request #73 from IBM/file-uploader
feat(component): add FileUploader
This commit is contained in:
commit
4481c5a97f
25 changed files with 546 additions and 28 deletions
|
@ -31,6 +31,11 @@ Currently, the following components are supported:
|
|||
- Copy
|
||||
- CopyButton
|
||||
- DataTableSkeleton
|
||||
- FileUploader
|
||||
- FileUploaderButton
|
||||
- FileUploaderItem
|
||||
- FileUploaderDropContainer
|
||||
- Filename
|
||||
- Form
|
||||
- FormGroup
|
||||
- FormItem
|
||||
|
|
|
@ -70,4 +70,4 @@
|
|||
}</script><style>#root[hidden],
|
||||
#docs-root[hidden] {
|
||||
display: none !important;
|
||||
}</style></head><body><div class="sb-nopreview sb-wrapper"><div class="sb-nopreview_main"><h1 class="sb-nopreview_heading sb-heading">No Preview</h1><p>Sorry, but you either have no stories or none are selected somehow.</p><ul><li>Please check the Storybook config.</li><li>Try reloading the page.</li></ul><p>If the problem persists, check the browser console, or the terminal you've run Storybook from.</p></div></div><div class="sb-errordisplay sb-wrapper"><pre id="error-message" class="sb-heading"></pre><pre class="sb-errordisplay_code"><code id="error-stack"></code></pre></div><div id="root"></div><div id="docs-root"></div><script src="runtime~main.e24a7c3ee0d97007ea8a.bundle.js"></script><script src="vendors~main.e24a7c3ee0d97007ea8a.bundle.js"></script><script src="main.e24a7c3ee0d97007ea8a.bundle.js"></script></body></html>
|
||||
}</style></head><body><div class="sb-nopreview sb-wrapper"><div class="sb-nopreview_main"><h1 class="sb-nopreview_heading sb-heading">No Preview</h1><p>Sorry, but you either have no stories or none are selected somehow.</p><ul><li>Please check the Storybook config.</li><li>Try reloading the page.</li></ul><p>If the problem persists, check the browser console, or the terminal you've run Storybook from.</p></div></div><div class="sb-errordisplay sb-wrapper"><pre id="error-message" class="sb-heading"></pre><pre class="sb-errordisplay_code"><code id="error-stack"></code></pre></div><div id="root"></div><div id="docs-root"></div><script src="runtime~main.9ba103da9a704f605f83.bundle.js"></script><script src="vendors~main.9ba103da9a704f605f83.bundle.js"></script><script src="main.9ba103da9a704f605f83.bundle.js"></script></body></html>
|
2
docs/main.9ba103da9a704f605f83.bundle.js
Normal file
2
docs/main.9ba103da9a704f605f83.bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/main.9ba103da9a704f605f83.bundle.js.map
Normal file
1
docs/main.9ba103da9a704f605f83.bundle.js.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"main.9ba103da9a704f605f83.bundle.js","sources":["webpack:///main.9ba103da9a704f605f83.bundle.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"main.e24a7c3ee0d97007ea8a.bundle.js","sources":["webpack:///main.e24a7c3ee0d97007ea8a.bundle.js"],"mappings":"AAAA","sourceRoot":""}
|
|
@ -1,2 +1,2 @@
|
|||
!function(modules){function webpackJsonpCallback(data){for(var moduleId,chunkId,chunkIds=data[0],moreModules=data[1],executeModules=data[2],i=0,resolves=[];i<chunkIds.length;i++)chunkId=chunkIds[i],Object.prototype.hasOwnProperty.call(installedChunks,chunkId)&&installedChunks[chunkId]&&resolves.push(installedChunks[chunkId][0]),installedChunks[chunkId]=0;for(moduleId in moreModules)Object.prototype.hasOwnProperty.call(moreModules,moduleId)&&(modules[moduleId]=moreModules[moduleId]);for(parentJsonpFunction&&parentJsonpFunction(data);resolves.length;)resolves.shift()();return deferredModules.push.apply(deferredModules,executeModules||[]),checkDeferredModules()}function checkDeferredModules(){for(var result,i=0;i<deferredModules.length;i++){for(var deferredModule=deferredModules[i],fulfilled=!0,j=1;j<deferredModule.length;j++){var depId=deferredModule[j];0!==installedChunks[depId]&&(fulfilled=!1)}fulfilled&&(deferredModules.splice(i--,1),result=__webpack_require__(__webpack_require__.s=deferredModule[0]))}return result}var installedModules={},installedChunks={1:0},deferredModules=[];function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={i:moduleId,l:!1,exports:{}};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.l=!0,module.exports}__webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.d=function(exports,name,getter){__webpack_require__.o(exports,name)||Object.defineProperty(exports,name,{enumerable:!0,get:getter})},__webpack_require__.r=function(exports){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})},__webpack_require__.t=function(value,mode){if(1&mode&&(value=__webpack_require__(value)),8&mode)return value;if(4&mode&&"object"==typeof value&&value&&value.__esModule)return value;var ns=Object.create(null);if(__webpack_require__.r(ns),Object.defineProperty(ns,"default",{enumerable:!0,value:value}),2&mode&&"string"!=typeof value)for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns},__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module.default}:function getModuleExports(){return module};return __webpack_require__.d(getter,"a",getter),getter},__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)},__webpack_require__.p="";var jsonpArray=window.webpackJsonp=window.webpackJsonp||[],oldJsonpFunction=jsonpArray.push.bind(jsonpArray);jsonpArray.push=webpackJsonpCallback,jsonpArray=jsonpArray.slice();for(var i=0;i<jsonpArray.length;i++)webpackJsonpCallback(jsonpArray[i]);var parentJsonpFunction=oldJsonpFunction;checkDeferredModules()}([]);
|
||||
//# sourceMappingURL=runtime~main.e24a7c3ee0d97007ea8a.bundle.js.map
|
||||
//# sourceMappingURL=runtime~main.9ba103da9a704f605f83.bundle.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"runtime~main.e24a7c3ee0d97007ea8a.bundle.js","sources":["webpack:///runtime~main.ce61f8335d8fdea2cda4.bundle.js"],"mappings":"AAAA","sourceRoot":""}
|
||||
{"version":3,"file":"runtime~main.9ba103da9a704f605f83.bundle.js","sources":["webpack:///runtime~main.ce61f8335d8fdea2cda4.bundle.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
1
docs/vendors~main.9ba103da9a704f605f83.bundle.js.map
Normal file
1
docs/vendors~main.9ba103da9a704f605f83.bundle.js.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"vendors~main.9ba103da9a704f605f83.bundle.js","sources":["webpack:///vendors~main.9ba103da9a704f605f83.bundle.js"],"mappings":"AAAA;;;;;AAyqeA;;;;;AAi9JA;;;;;AAkkEA;;;;;;;;;AAukBA;;;AA8odA;;;;;;;;AAg/BA;;;;;;;;AAqEA;;;;;;;;AAkTA;;;;;;;AAyrDA;;;;;;;AAy7CA;;;;;;;AAufA;;;;;;;AAfA","sourceRoot":""}
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"vendors~main.e24a7c3ee0d97007ea8a.bundle.js","sources":["webpack:///vendors~main.e24a7c3ee0d97007ea8a.bundle.js"],"mappings":"AAAA;;;;;AAwqeA;;;;;AAi9JA;;;;;AAkkEA;;;;;;;;;AAukBA;;;AA8odA;;;;;;;;AAg/BA;;;;;;;;AAqEA;;;;;;;;AAkTA;;;;;;;AAyrDA;;;;;;;AAy7CA;;;;;;;AAufA;;;;;;;AAfA","sourceRoot":""}
|
|
@ -23,13 +23,10 @@
|
|||
|
||||
let buttonRef = undefined;
|
||||
|
||||
$: {
|
||||
if (ctx && buttonRef) {
|
||||
ctx.declareRef({ name: 'buttonRef', ref: buttonRef });
|
||||
}
|
||||
$: if (ctx && buttonRef) {
|
||||
ctx.declareRef({ name: 'buttonRef', ref: buttonRef });
|
||||
}
|
||||
|
||||
const _class = cx(
|
||||
$: _class = cx(
|
||||
'--btn',
|
||||
size === 'field' && '--btn--field',
|
||||
(size === 'small' || small) && '--btn--sm',
|
||||
|
@ -47,7 +44,7 @@
|
|||
hasIconOnly && tooltipAlignment && `--tooltip--align-${tooltipAlignment}`,
|
||||
className
|
||||
);
|
||||
const buttonProps = {
|
||||
$: buttonProps = {
|
||||
role: 'button',
|
||||
type: href && !disabled ? undefined : type,
|
||||
tabindex,
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
);
|
||||
</script>
|
||||
|
||||
<table on:click on:mouseover on:mouseenter on:mouseleave {style} class={_class}>
|
||||
<table on:click on:mouseover on:mouseenter on:mouseleave class={_class} {style}>
|
||||
<thead>
|
||||
<tr>
|
||||
{#each columns as column, i (column)}
|
||||
|
|
17
src/components/FileUploader/FileUploader.Skeleton.svelte
Normal file
17
src/components/FileUploader/FileUploader.Skeleton.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let style = undefined;
|
||||
|
||||
import SkeletonText from '../SkeletonText';
|
||||
import { ButtonSkeleton } from '../Button';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
const _class = cx('--form-item', className);
|
||||
</script>
|
||||
|
||||
<div on:click on:mouseover on:mouseenter on:mouseleave class={_class} {style}>
|
||||
<SkeletonText heading width="100px" />
|
||||
<SkeletonText width="225px" class={cx('--label-description')} />
|
||||
<ButtonSkeleton />
|
||||
</div>
|
65
src/components/FileUploader/FileUploader.Story.svelte
Normal file
65
src/components/FileUploader/FileUploader.Story.svelte
Normal file
|
@ -0,0 +1,65 @@
|
|||
<script>
|
||||
export let story = undefined;
|
||||
|
||||
import Layout from '../../internal/ui/Layout.svelte';
|
||||
import { cx } from '../../lib';
|
||||
import Button from '../Button';
|
||||
import FileUploader from './FileUploader.svelte';
|
||||
import FileUploaderButton from './FileUploaderButton.svelte';
|
||||
import FileUploaderItem from './FileUploaderItem.svelte';
|
||||
import FileUploaderDropContainer from './FileUploaderDropContainer.svelte';
|
||||
import FileUploaderSkeleton from './FileUploader.Skeleton.svelte';
|
||||
|
||||
let files = [];
|
||||
|
||||
$: disabled = files.length === 0;
|
||||
</script>
|
||||
|
||||
<Layout>
|
||||
<div>
|
||||
{#if story === 'button'}
|
||||
<FileUploaderButton {...$$props} />
|
||||
{:else if story === 'drop container'}
|
||||
<FileUploaderDropContainer
|
||||
{...$$props}
|
||||
on:add={({ detail }) => {
|
||||
console.log(detail);
|
||||
}} />
|
||||
{:else if story === 'item'}
|
||||
<FileUploaderItem
|
||||
{...$$props}
|
||||
on:delete={({ detail }) => {
|
||||
console.log(detail);
|
||||
}}
|
||||
on:click={() => {
|
||||
console.log('click');
|
||||
}} />
|
||||
{:else if story === 'uploader'}
|
||||
<div class={cx('--file__container')}>
|
||||
<FileUploader
|
||||
{...$$props}
|
||||
bind:files
|
||||
on:add={({ detail }) => {
|
||||
console.log('add', detail);
|
||||
}}
|
||||
on:remove={({ detail }) => {
|
||||
console.log('remove', detail);
|
||||
}} />
|
||||
<Button
|
||||
kind="secondary"
|
||||
size="small"
|
||||
style={'margin-top: 1rem'}
|
||||
{disabled}
|
||||
on:click={() => {
|
||||
files = [];
|
||||
}}>
|
||||
Clear File{files.length === 1 ? '' : 's'}
|
||||
</Button>
|
||||
</div>
|
||||
{:else if story === 'skeleton'}
|
||||
<div style="width: 500px">
|
||||
<FileUploaderSkeleton />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Layout>
|
98
src/components/FileUploader/FileUploader.stories.js
Normal file
98
src/components/FileUploader/FileUploader.stories.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
import { withKnobs, text, select, boolean, number, array } from '@storybook/addon-knobs';
|
||||
import Component from './FileUploader.Story.svelte';
|
||||
|
||||
export default { title: 'FileUploader', decorators: [withKnobs] };
|
||||
|
||||
const buttonKinds = {
|
||||
'Primary (primary)': 'primary',
|
||||
'Secondary (secondary)': 'secondary',
|
||||
'Danger (danger)': 'danger',
|
||||
'Ghost (ghost)': 'ghost',
|
||||
'Tertiary (tertiary)': 'tertiary'
|
||||
};
|
||||
|
||||
const filenameStatuses = {
|
||||
'Edit (edit)': 'edit',
|
||||
'Complete (complete)': 'complete',
|
||||
'Uploading (uploading)': 'uploading'
|
||||
};
|
||||
|
||||
export const FileUploaderButton = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'button',
|
||||
kind: select('Button kind (kind)', buttonKinds, 'primary'),
|
||||
labelText: text('Label text (labelText)', 'Add files'),
|
||||
name: text('Form item name: (name)', ''),
|
||||
multiple: boolean('Supports multiple files (multiple)', true),
|
||||
disabled: boolean('Disabled (disabled)', false),
|
||||
disableLabelChanges: boolean(
|
||||
'Prevent the label from being replaced with file selected file (disableLabelChanges)',
|
||||
false
|
||||
),
|
||||
role: text('ARIA role of the button (role)', 'button'),
|
||||
tabindex: text('Tab index (tabindex)', '0')
|
||||
}
|
||||
});
|
||||
|
||||
FileUploaderButton.story = { name: 'FileUploaderButton' };
|
||||
|
||||
export const FileUploader = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'uploader',
|
||||
labelTitle: text('The label title (labelTitle)', 'Upload'),
|
||||
labelDescription: text(
|
||||
'The label description (labelDescription)',
|
||||
'only .jpg files at 500mb or less'
|
||||
),
|
||||
buttonLabel: text('The button label (buttonLabel)', 'Add files'),
|
||||
status: select('Status for file name (status)', filenameStatuses, 'edit'),
|
||||
accept: array('Accepted file extensions (accept)', ['.jpg', '.png'], ','),
|
||||
name: text('Form item name: (name)', ''),
|
||||
multiple: boolean('Supports multiple files (multiple)', true),
|
||||
iconDescription: text('Close button icon description (iconDescription)', 'Clear file')
|
||||
}
|
||||
});
|
||||
|
||||
FileUploader.story = { name: 'FileUploader' };
|
||||
|
||||
export const FileUploaderItem = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'item',
|
||||
name: text('Filename (name)', 'README.md'),
|
||||
status: select('Status for file name (status)', filenameStatuses, 'edit'),
|
||||
iconDescription: text('Close button icon description (iconDescription)', 'Clear file'),
|
||||
invalid: boolean('Invalid (invalid)', false),
|
||||
errorSubject: text('Error subject (errorSubject)', 'File size exceeds limit'),
|
||||
errorBody: text(
|
||||
'Error body (errorBody)',
|
||||
'500kb max file size. Select a new file and try again.'
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
FileUploaderItem.story = { name: 'FileUploaderItem' };
|
||||
|
||||
export const FileUploaderDropContainer = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'drop container',
|
||||
labelText: text('Label text (labelText)', 'Drag and drop files here or click to upload'),
|
||||
name: text('Form item name (name)', ''),
|
||||
multiple: boolean('Supports multiple files (multiple)', true),
|
||||
accept: array(
|
||||
'Accepted MIME types or file extensions (accept)',
|
||||
['image/jpeg', 'image/png'],
|
||||
','
|
||||
),
|
||||
disabled: boolean('Disabled (disabled)', false),
|
||||
role: text('ARIA role of the button (role)', ''),
|
||||
tabindex: number('Tab index (tabindex)', '0')
|
||||
}
|
||||
});
|
||||
|
||||
FileUploaderDropContainer.story = { name: 'FileUploaderDropContainer' };
|
||||
|
||||
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } });
|
77
src/components/FileUploader/FileUploader.svelte
Normal file
77
src/components/FileUploader/FileUploader.svelte
Normal file
|
@ -0,0 +1,77 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let files = [];
|
||||
export let name = '';
|
||||
export let labelDescription = '';
|
||||
export let labelTitle = '';
|
||||
export let iconDescription = 'Provide icon description';
|
||||
export let status = 'uploading';
|
||||
export let buttonLabel = '';
|
||||
export let kind = 'primary';
|
||||
export let multiple = false;
|
||||
export let accept = [];
|
||||
export let style = undefined;
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { cx } from '../../lib';
|
||||
import Filename from './Filename.svelte';
|
||||
import FileUploaderButton from './FileUploaderButton.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const _class = cx('--form-item', className);
|
||||
|
||||
// let files = [];
|
||||
let prevFiles = [];
|
||||
|
||||
$: {
|
||||
if (files.length > prevFiles.length) {
|
||||
dispatch('add', files);
|
||||
} else {
|
||||
dispatch(
|
||||
'remove',
|
||||
prevFiles.filter(_ => !files.includes(_))
|
||||
);
|
||||
}
|
||||
|
||||
prevFiles = [...files];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div on:click on:mouseover on:mouseenter on:mouseleave class={_class} {style}>
|
||||
<strong class={cx('--file--label')}>{labelTitle}</strong>
|
||||
<p class={cx('--label-description')}>{labelDescription}</p>
|
||||
<FileUploaderButton
|
||||
disableLabelChanges
|
||||
labelText={buttonLabel}
|
||||
on:change
|
||||
on:change={({ target }) => {
|
||||
files = [...target.files].map(({ name }) => name);
|
||||
}}
|
||||
{accept}
|
||||
{name}
|
||||
{multiple}
|
||||
{kind} />
|
||||
<div class={cx('--file-container')}>
|
||||
{#each files as name, i (name)}
|
||||
<span class={cx('--file__selected-file')}>
|
||||
<p class={cx('--file-filename')}>{name}</p>
|
||||
<span class={cx('--file__state-container')}>
|
||||
<Filename
|
||||
on:keydown
|
||||
on:keydown={({ key }) => {
|
||||
if (key === ' ' || key === 'Enter') {
|
||||
files = files.filter((_, index) => index !== i);
|
||||
}
|
||||
}}
|
||||
on:click
|
||||
on:click={evt => {
|
||||
files = files.filter((_, index) => index !== i);
|
||||
}}
|
||||
{iconDescription}
|
||||
{status} />
|
||||
</span>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
64
src/components/FileUploader/FileUploaderButton.svelte
Normal file
64
src/components/FileUploader/FileUploaderButton.svelte
Normal file
|
@ -0,0 +1,64 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let disableLabelChanges = false;
|
||||
export let id = Math.random();
|
||||
export let labelText = 'Add file';
|
||||
export let multiple = false;
|
||||
export let name = '';
|
||||
export let role = 'button';
|
||||
export let tabindex = '0';
|
||||
export let kind = 'primary';
|
||||
export let accept = [];
|
||||
export let disabled = false;
|
||||
export let style = undefined;
|
||||
|
||||
import { cx } from '../../lib';
|
||||
|
||||
const _class = cx(
|
||||
'--btn',
|
||||
'--btn--sm',
|
||||
kind && `--btn--${kind}`,
|
||||
disabled && '--btn--disabled',
|
||||
className
|
||||
);
|
||||
|
||||
let inputRef = undefined;
|
||||
</script>
|
||||
|
||||
<label
|
||||
tabindex={disabled ? '-1' : tabindex}
|
||||
aria-disabled={disabled}
|
||||
class={_class}
|
||||
for={id}
|
||||
on:keydown
|
||||
on:keydown={({ key }) => {
|
||||
if (key === ' ' || key === 'Enter') {
|
||||
inputRef.click();
|
||||
}
|
||||
}}
|
||||
{style}>
|
||||
<span {role}>{labelText}</span>
|
||||
</label>
|
||||
<input
|
||||
bind:this={inputRef}
|
||||
type="file"
|
||||
tabindex="-1"
|
||||
class={cx('--visually-hidden')}
|
||||
on:change|stopPropagation
|
||||
on:change|stopPropagation={({ target }) => {
|
||||
const files = target.files;
|
||||
const length = files.length;
|
||||
if (files && !disableLabelChanges) {
|
||||
labelText = length > 1 ? `${length} files` : files[0].name;
|
||||
}
|
||||
}}
|
||||
on:click
|
||||
on:click={event => {
|
||||
event.target.value = null;
|
||||
}}
|
||||
{id}
|
||||
{disabled}
|
||||
{multiple}
|
||||
{accept}
|
||||
{name} />
|
85
src/components/FileUploader/FileUploaderDropContainer.svelte
Normal file
85
src/components/FileUploader/FileUploaderDropContainer.svelte
Normal file
|
@ -0,0 +1,85 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let name = '';
|
||||
export let role = 'button';
|
||||
export let id = Math.random();
|
||||
export let disabled = false;
|
||||
export let tabindex = '0';
|
||||
export let labelText = 'Add file';
|
||||
export let multiple = false;
|
||||
export let accept = [];
|
||||
export let validateFiles = files => files;
|
||||
export let style = undefined;
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import SkeletonText from '../SkeletonText';
|
||||
import { ButtonSkeleton } from '../Button';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const _labelClass = cx('--file-browse-btn', disabled && '--file-browse-btn--disabled');
|
||||
|
||||
let over = false;
|
||||
let inputRef = undefined;
|
||||
|
||||
$: _class = cx('--file__drop-container', over && '--file__drop-container--drag-over', className);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cx('--file')}
|
||||
on:dragover
|
||||
on:dragover|preventDefault|stopPropagation={({ dataTransfer }) => {
|
||||
if (!disabled) {
|
||||
over = true;
|
||||
dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
}}
|
||||
on:dragleave
|
||||
on:dragleave|preventDefault|stopPropagation={({ dataTransfer }) => {
|
||||
if (!disabled) {
|
||||
over = false;
|
||||
dataTransfer.dropEffect = 'move';
|
||||
}
|
||||
}}
|
||||
on:drop
|
||||
on:drop|preventDefault|stopPropagation={({ dataTransfer }) => {
|
||||
if (!disabled) {
|
||||
over = false;
|
||||
dispatch('add', validateFiles(dataTransfer.files));
|
||||
}
|
||||
}}
|
||||
{style}>
|
||||
<label
|
||||
class={_labelClass}
|
||||
for={id}
|
||||
on:keydown
|
||||
on:keydown={({ key }) => {
|
||||
if (key === ' ' || key === 'Enter') {
|
||||
inputRef.click();
|
||||
}
|
||||
}}
|
||||
{tabindex}>
|
||||
<div class={_class} {role}>
|
||||
{labelText}
|
||||
<input
|
||||
bind:this={inputRef}
|
||||
type="file"
|
||||
tabindex="-1"
|
||||
class={cx('--file-input')}
|
||||
on:change
|
||||
on:change={({ target }) => {
|
||||
dispatch('add', validateFiles(target.files));
|
||||
}}
|
||||
on:click
|
||||
on:click={({ target }) => {
|
||||
target.value = null;
|
||||
}}
|
||||
{id}
|
||||
{disabled}
|
||||
{accept}
|
||||
{name}
|
||||
{multiple} />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
49
src/components/FileUploader/FileUploaderItem.svelte
Normal file
49
src/components/FileUploader/FileUploaderItem.svelte
Normal file
|
@ -0,0 +1,49 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let id = Math.random();
|
||||
export let status = 'uploading';
|
||||
export let iconDescription = '';
|
||||
export let name = '';
|
||||
export let invalid = false;
|
||||
export let errorSubject = '';
|
||||
export let errorBody = '';
|
||||
export let style = undefined;
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { cx } from '../../lib';
|
||||
import Filename from './Filename.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const _class = cx(
|
||||
'--file__selected-file',
|
||||
invalid && '--file__selected-file--invalid',
|
||||
className
|
||||
);
|
||||
</script>
|
||||
|
||||
<span on:mouseover on:mouseenter on:mouseleave class={_class} {style}>
|
||||
<p class={cx('--file-filename')}>{name}</p>
|
||||
<span class={cx('--file__state-container')}>
|
||||
<Filename
|
||||
{iconDescription}
|
||||
{status}
|
||||
{invalid}
|
||||
on:keydown={({ key }) => {
|
||||
if (key === ' ' || key === 'Enter') {
|
||||
dispatch('delete', id);
|
||||
}
|
||||
}}
|
||||
on:click={() => {
|
||||
dispatch('delete', id);
|
||||
}} />
|
||||
</span>
|
||||
{#if invalid && errorSubject}
|
||||
<div class={cx('--form-requirement')}>
|
||||
<div class={cx('--form-requirement__title')}>{errorSubject}</div>
|
||||
{#if errorBody}
|
||||
<p class={cx('--form-requirement__supplement')}>{errorBody}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</span>
|
43
src/components/FileUploader/Filename.svelte
Normal file
43
src/components/FileUploader/Filename.svelte
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let status = 'uploading';
|
||||
export let iconDescription = '';
|
||||
export let invalid = false;
|
||||
export let tabindex = '0';
|
||||
export let style = undefined;
|
||||
|
||||
import Close16 from 'carbon-icons-svelte/lib/Close16';
|
||||
import CheckmarkFilled16 from 'carbon-icons-svelte/lib/CheckmarkFilled16';
|
||||
import WarningFilled16 from 'carbon-icons-svelte/lib/WarningFilled16';
|
||||
import { cx } from '../../lib';
|
||||
import Loading from '../Loading';
|
||||
</script>
|
||||
|
||||
{#if status === 'uploading'}
|
||||
<Loading description={iconDescription} withOverlay={false} small class={className} {style} />
|
||||
{/if}
|
||||
|
||||
{#if status === 'edit'}
|
||||
{#if invalid}
|
||||
<WarningFilled16 class={cx('--file-invalid')} />
|
||||
{/if}
|
||||
<!-- TODO: forward keydown event to Svelte icon -->
|
||||
<Close16
|
||||
class={cx('--file-close', className)}
|
||||
aria-label={iconDescription}
|
||||
title={iconDescription}
|
||||
on:click
|
||||
on:keydown
|
||||
{tabindex}
|
||||
{style} />
|
||||
{/if}
|
||||
|
||||
{#if status === 'complete'}
|
||||
<CheckmarkFilled16
|
||||
class={cx('--file-complete', className)}
|
||||
aria-label={iconDescription}
|
||||
title={iconDescription}
|
||||
{tabindex}
|
||||
{style} />
|
||||
{/if}
|
7
src/components/FileUploader/index.js
Normal file
7
src/components/FileUploader/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import FileUploader from './FileUploader.svelte';
|
||||
|
||||
export default FileUploader;
|
||||
export { default as FileUploaderButton } from './FileUploaderButton.svelte';
|
||||
export { default as FileUploaderItem } from './FileUploaderItem.svelte';
|
||||
export { default as FileUploaderDropContainer } from './FileUploaderDropContainer.svelte';
|
||||
export { default as Filename } from './Filename.svelte';
|
|
@ -10,7 +10,7 @@ export const Default = () => ({
|
|||
disabled: boolean('Disabled (disabled in <Tab>)', false),
|
||||
href: text('The href for tab (href in <Tab>)', '#'),
|
||||
role: text('ARIA role (role in <Tab>)', 'presentation'),
|
||||
tabindex: number('Tab index (tabindex in <Tab>)', 0)
|
||||
tabindex: text('Tab index (tabindex in <Tab>)', '0')
|
||||
},
|
||||
tabsProps: {
|
||||
className: 'some-class',
|
||||
|
|
|
@ -48,7 +48,7 @@ export const Expandable = () => ({
|
|||
Component,
|
||||
props: {
|
||||
story: 'expandable',
|
||||
tabIndex: number('Tab index (tabIndex)', 0),
|
||||
tabindex: text('Tab index (tabindex)', '0'),
|
||||
expanded: boolean('Expanded (expanded)', false),
|
||||
tileMaxHeight: number('Max height (tileMaxHeight)', 0),
|
||||
tileCollapsedIconText: text(
|
||||
|
|
11
src/index.js
11
src/index.js
|
@ -8,6 +8,12 @@ import CopyButton from './components/CopyButton';
|
|||
import ComposedModal, { ModalHeader, ModalBody, ModalFooter } from './components/ComposedModal';
|
||||
import CodeSnippet, { CodeSnippetSkeleton } from './components/CodeSnippet';
|
||||
import DataTableSkeleton from './components/DataTableSkeleton';
|
||||
import FileUploader, {
|
||||
FileUploaderButton,
|
||||
FileUploaderItem,
|
||||
FileUploaderDropContainer,
|
||||
Filename
|
||||
} from './components/FileUploader';
|
||||
import Form from './components/Form';
|
||||
import FormGroup from './components/FormGroup';
|
||||
import FormItem from './components/FormItem';
|
||||
|
@ -86,6 +92,11 @@ export {
|
|||
Copy,
|
||||
CopyButton,
|
||||
DataTableSkeleton,
|
||||
FileUploader,
|
||||
FileUploaderButton,
|
||||
FileUploaderItem,
|
||||
FileUploaderDropContainer,
|
||||
Filename,
|
||||
Form,
|
||||
FormGroup,
|
||||
FormItem,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue