mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-15 10:21:05 +00:00
Merge pull request #80 from IBM/datepicker
feat(component): add DatePicker
This commit is contained in:
commit
446032d8ee
23 changed files with 602 additions and 21 deletions
|
@ -31,6 +31,8 @@ Currently, the following components are supported:
|
|||
- Copy
|
||||
- CopyButton
|
||||
- DataTableSkeleton
|
||||
- DatePicker
|
||||
- DatePickerInput
|
||||
- FileUploader
|
||||
- FileUploaderButton
|
||||
- FileUploaderItem
|
||||
|
|
|
@ -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.ee2166195d34dba757a3.bundle.js"></script><script src="vendors~main.ee2166195d34dba757a3.bundle.js"></script><script src="main.ee2166195d34dba757a3.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.8d22be1b99a499135109.bundle.js"></script><script src="vendors~main.8d22be1b99a499135109.bundle.js"></script><script src="main.8d22be1b99a499135109.bundle.js"></script></body></html>
|
2
docs/main.8d22be1b99a499135109.bundle.js
Normal file
2
docs/main.8d22be1b99a499135109.bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/main.8d22be1b99a499135109.bundle.js.map
Normal file
1
docs/main.8d22be1b99a499135109.bundle.js.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"main.8d22be1b99a499135109.bundle.js","sources":["webpack:///main.8d22be1b99a499135109.bundle.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"main.ee2166195d34dba757a3.bundle.js","sources":["webpack:///main.ee2166195d34dba757a3.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.ee2166195d34dba757a3.bundle.js.map
|
||||
//# sourceMappingURL=runtime~main.8d22be1b99a499135109.bundle.js.map
|
1
docs/runtime~main.8d22be1b99a499135109.bundle.js.map
Normal file
1
docs/runtime~main.8d22be1b99a499135109.bundle.js.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"runtime~main.8d22be1b99a499135109.bundle.js","sources":["webpack:///runtime~main.3fc22874a0adf047f1de.bundle.js"],"mappings":"AAAA","sourceRoot":""}
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"runtime~main.ee2166195d34dba757a3.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.8d22be1b99a499135109.bundle.js.map
Normal file
1
docs/vendors~main.8d22be1b99a499135109.bundle.js.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"vendors~main.8d22be1b99a499135109.bundle.js","sources":["webpack:///vendors~main.3fc22874a0adf047f1de.bundle.js"],"mappings":"AAAA;;;;;;;;;;;;;;AAs2OA;;;;;AAy9WA;;AAu8BA;;;;;;;;;;;;;;AAmBA;;;;;AAu9NA;;;;;AAkkEA;;;;;;;;;AAukBA;;;AA8odA;;;;;;;;AAg/BA;;;;;;;;AAqEA;;;;;;;;AAkTA;;;;;;;AAyrDA;;;;;;;AAy7CA;;;;;;;AAycA;;;;;;;AAfA","sourceRoot":""}
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"vendors~main.ee2166195d34dba757a3.bundle.js","sources":["webpack:///vendors~main.ee2166195d34dba757a3.bundle.js"],"mappings":"AAAA;;;;;AAm7eA;;;;;AAs8JA;;;;;AAkkEA;;;;;;;;;AAukBA;;;AA8odA;;;;;;;;AAg/BA;;;;;;;;AAqEA;;;;;;;;AAkTA;;;;;;;AAyrDA;;;;;;;AAy7CA;;;;;;;AA6fA;;;;;;;AAfA","sourceRoot":""}
|
|
@ -15,7 +15,8 @@
|
|||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"carbon-icons-svelte": "^10.8.0-rc.0"
|
||||
"carbon-icons-svelte": "^10.8.0-rc.0",
|
||||
"flatpickr": "^4.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"carbon-components": "^10.8.0",
|
||||
|
@ -46,6 +47,7 @@
|
|||
"pretty-quick": "^2.0.1",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"rollup": "^1.27.13",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-svelte": "^5.1.1",
|
||||
"rollup-plugin-terser": "^5.1.3",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { terser } from 'rollup-plugin-terser';
|
||||
import pkg from './package.json';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
|
||||
export default ['es', 'umd'].map(format => {
|
||||
|
@ -8,7 +9,10 @@ export default ['es', 'umd'].map(format => {
|
|||
|
||||
const output = {
|
||||
format,
|
||||
file: UMD ? pkg.main : pkg.module
|
||||
file: UMD ? pkg.main : pkg.module,
|
||||
globals: {
|
||||
flatpickr: 'flatpickr'
|
||||
}
|
||||
};
|
||||
|
||||
if (UMD) {
|
||||
|
@ -19,6 +23,6 @@ export default ['es', 'umd'].map(format => {
|
|||
input: 'src',
|
||||
output,
|
||||
external: Object.keys(pkg.dependencies || {}),
|
||||
plugins: [svelte(), resolve(), UMD && terser()]
|
||||
plugins: [svelte(), resolve(), commonjs(), UMD && terser()]
|
||||
};
|
||||
});
|
||||
|
|
21
src/components/DatePicker/DatePicker.Skeleton.svelte
Normal file
21
src/components/DatePicker/DatePicker.Skeleton.svelte
Normal file
|
@ -0,0 +1,21 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let id = Math.random();
|
||||
export let range = false;
|
||||
export let style = undefined;
|
||||
|
||||
import { cx, fillArray } from '../../lib';
|
||||
</script>
|
||||
|
||||
<div on:click on:mouseover on:mouseenter on:mouseleave class={cx('--form-item')} {style}>
|
||||
<div
|
||||
class={cx('--date-picker', '--skeleton', range && '--date-picker--range', !range && '--date-picker--short', !range && '--date-picker--simple', className)}>
|
||||
{#each fillArray(range ? 2 : 1) as input, i (input)}
|
||||
<div class={cx('--date-picker-container')}>
|
||||
<label class={cx('--label')} for={id} />
|
||||
<div class={cx('--date-picker__input', '--skeleton')} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
65
src/components/DatePicker/DatePicker.Story.svelte
Normal file
65
src/components/DatePicker/DatePicker.Story.svelte
Normal file
|
@ -0,0 +1,65 @@
|
|||
<script>
|
||||
export let story = undefined;
|
||||
|
||||
import Layout from '../../internal/ui/Layout.svelte';
|
||||
import DatePicker from './DatePicker.svelte';
|
||||
import DatePickerSkeleton from './DatePicker.Skeleton.svelte';
|
||||
import DatePickerInput from './DatePickerInput.svelte';
|
||||
|
||||
let datePickerType = 'simple';
|
||||
let value = '';
|
||||
</script>
|
||||
|
||||
<Layout>
|
||||
|
||||
{#if story === 'skeleton'}
|
||||
<DatePickerSkeleton range />
|
||||
{:else if story === 'single'}
|
||||
<div>
|
||||
<DatePicker
|
||||
{...$$props.datePicker}
|
||||
bind:value
|
||||
datePickerType="single"
|
||||
on:change={({ detail }) => {
|
||||
console.log('change', detail);
|
||||
}}>
|
||||
<DatePickerInput
|
||||
{...$$props.datePickerInput}
|
||||
on:close={() => {
|
||||
console.log('on:close');
|
||||
}}
|
||||
on:input={() => {
|
||||
console.log('on:input');
|
||||
}} />
|
||||
</DatePicker>
|
||||
<button
|
||||
on:click|preventDefault={() => {
|
||||
value = '12/12/2020';
|
||||
}}
|
||||
style="margin-top: 1rem">
|
||||
Set date to 12/12/2020
|
||||
</button>
|
||||
</div>
|
||||
{:else if story === 'range'}
|
||||
<DatePicker {...$$props.datePicker} bind:value datePickerType="range">
|
||||
<DatePickerInput
|
||||
{...$$props.datePickerInput}
|
||||
id="date-picker-input-id-start"
|
||||
labelText="Start date" />
|
||||
<DatePickerInput
|
||||
{...$$props.datePickerInput}
|
||||
id="date-picker-input-id-end"
|
||||
labelText="End date" />
|
||||
</DatePicker>
|
||||
{:else}
|
||||
<DatePicker
|
||||
{...$$props.datePicker}
|
||||
bind:datePickerType
|
||||
bind:value
|
||||
on:change={({ detail }) => {
|
||||
console.log('on:change', detail);
|
||||
}}>
|
||||
<DatePickerInput {...$$props.datePickerInput} />
|
||||
</DatePicker>
|
||||
{/if}
|
||||
</Layout>
|
99
src/components/DatePicker/DatePicker.stories.js
Normal file
99
src/components/DatePicker/DatePicker.stories.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { withKnobs, select, text, boolean } from '@storybook/addon-knobs';
|
||||
import Component from './DatePicker.Story.svelte';
|
||||
|
||||
export default { title: 'DatePicker', decorators: [withKnobs] };
|
||||
|
||||
const patterns = {
|
||||
'Short (d{1,2}/d{4})': 'd{1,2}/d{4}',
|
||||
'Regular (d{1,2}/d{1,2}/d{4})': 'd{1,2}/d{1,2}/d{4}'
|
||||
};
|
||||
|
||||
export const Default = () => ({
|
||||
Component,
|
||||
props: {
|
||||
datePicker: {
|
||||
id: 'date-picker',
|
||||
light: boolean('Light variant (light in <DatePicker>)', false),
|
||||
short: boolean('Use shorter width (short in <DatePicker>)', false)
|
||||
},
|
||||
datePickerInput: {
|
||||
id: 'date-picker-input-id',
|
||||
labelText: text('Label text (labelText in <DatePickerInput>)', 'Date Picker label'),
|
||||
hideLabel: boolean('Hide label (hideLabel)', false),
|
||||
pattern: select('The date format (pattern in <DatePickerInput>)', patterns, 'd{1,2}/d{4}'),
|
||||
placeholder: text('Placeholder text (placeholder in <DatePickerInput>)', 'mm/dd/yyyy'),
|
||||
disabled: boolean('Disabled (disabled in <DatePickerInput>)', false),
|
||||
invalid: boolean('Show form validation UI (invalid in <DatePickerInput>)', false),
|
||||
invalidText: text(
|
||||
'Form validation UI content (invalidText in <DatePickerInput>)',
|
||||
'A valid value is required'
|
||||
),
|
||||
iconDescription: text(
|
||||
'Icon description (iconDescription in <DatePickerInput>)',
|
||||
'Icon description'
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Default.story = { name: 'Default (simple)' };
|
||||
|
||||
export const Single = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'single',
|
||||
datePicker: {
|
||||
id: 'date-picker',
|
||||
light: boolean('Light variant (light in <DatePicker>)', false),
|
||||
dateFormat: text('The date format (dateFormat in <DatePicker>)', 'm/d/Y')
|
||||
},
|
||||
datePickerInput: {
|
||||
id: 'date-picker-input-id',
|
||||
labelText: text('Label text (labelText in <DatePickerInput>)', 'Date Picker label'),
|
||||
hideLabel: boolean('Hide label (hideLabel)', false),
|
||||
pattern: select('The date format (pattern in <DatePickerInput>)', patterns, 'd{1,2}/d{4}'),
|
||||
placeholder: text('Placeholder text (placeholder in <DatePickerInput>)', 'mm/dd/yyyy'),
|
||||
disabled: boolean('Disabled (disabled in <DatePickerInput>)', false),
|
||||
invalid: boolean('Show form validation UI (invalid in <DatePickerInput>)', false),
|
||||
invalidText: text(
|
||||
'Form validation UI content (invalidText in <DatePickerInput>)',
|
||||
'A valid value is required'
|
||||
),
|
||||
iconDescription: text(
|
||||
'Icon description (iconDescription in <DatePickerInput>)',
|
||||
'Icon description'
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const Range = () => ({
|
||||
Component,
|
||||
props: {
|
||||
story: 'range',
|
||||
datePicker: {
|
||||
id: 'date-picker',
|
||||
light: boolean('Light variant (light in <DatePicker>)', false),
|
||||
dateFormat: text('The date format (dateFormat in <DatePicker>)', 'm/d/Y')
|
||||
},
|
||||
datePickerInput: {
|
||||
id: 'date-picker-input-id',
|
||||
labelText: text('Label text (labelText in <DatePickerInput>)', 'Date Picker label'),
|
||||
hideLabel: boolean('Hide label (hideLabel)', false),
|
||||
pattern: select('The date format (pattern in <DatePickerInput>)', patterns, 'd{1,2}/d{4}'),
|
||||
placeholder: text('Placeholder text (placeholder in <DatePickerInput>)', 'mm/dd/yyyy'),
|
||||
disabled: boolean('Disabled (disabled in <DatePickerInput>)', false),
|
||||
invalid: boolean('Show form validation UI (invalid in <DatePickerInput>)', false),
|
||||
invalidText: text(
|
||||
'Form validation UI content (invalidText in <DatePickerInput>)',
|
||||
'A valid value is required'
|
||||
),
|
||||
iconDescription: text(
|
||||
'Icon description (iconDescription in <DatePickerInput>)',
|
||||
'Icon description'
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } });
|
145
src/components/DatePicker/DatePicker.svelte
Normal file
145
src/components/DatePicker/DatePicker.svelte
Normal file
|
@ -0,0 +1,145 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let id = Math.random();
|
||||
export let short = false;
|
||||
export let datePickerType = 'simple';
|
||||
export let dateFormat = 'm/d/Y';
|
||||
export let locale = 'en';
|
||||
export let value = '';
|
||||
export let appendTo = document.body;
|
||||
export let minDate = null;
|
||||
export let maxDate = null;
|
||||
export let light = false;
|
||||
export let style = undefined;
|
||||
|
||||
import { createEventDispatcher, setContext, afterUpdate, onDestroy } from 'svelte';
|
||||
import { writable, derived } from 'svelte/store';
|
||||
import { createCalendar } from './flatpickr';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let inputs = writable([]);
|
||||
let inputIds = derived(inputs, _ => _.map(({ id }) => id));
|
||||
let inputsById = derived(inputs, _ => _.reduce((a, c) => ({ ...a, [c.id]: c }), {}));
|
||||
let labelTextEmpty = derived(inputs, _ => _.filter(({ labelText }) => !!labelText).length === 0);
|
||||
let inputValue = writable(value);
|
||||
let mode = writable(datePickerType);
|
||||
let range = derived(mode, _ => _ === 'range');
|
||||
let hasCalendar = derived(mode, _ => _ === 'single' || _ === 'range');
|
||||
|
||||
let calendar = undefined;
|
||||
let datePickerRef = undefined;
|
||||
let inputRef = undefined;
|
||||
let inputRefTo = undefined;
|
||||
|
||||
setContext('DatePicker', {
|
||||
range,
|
||||
inputValue,
|
||||
hasCalendar,
|
||||
add: data => {
|
||||
inputs.update(_ => [..._, data]);
|
||||
},
|
||||
declareRef: ({ id, ref }) => {
|
||||
if ($inputIds.indexOf(id) === 0) {
|
||||
inputRef = ref;
|
||||
} else {
|
||||
inputRefTo = ref;
|
||||
}
|
||||
},
|
||||
updateValue: ({ id, type, value }) => {
|
||||
if ((!calendar && type === 'input') || type === 'change') {
|
||||
inputValue.set(value);
|
||||
}
|
||||
|
||||
if (!calendar && type === 'change') {
|
||||
dispatch('change', value);
|
||||
}
|
||||
},
|
||||
blurInput: relatedTarget => {
|
||||
if (calendar && !calendar.calendarContainer.contains(relatedTarget)) {
|
||||
calendar.close();
|
||||
}
|
||||
},
|
||||
openCalendar: () => {
|
||||
calendar.open();
|
||||
},
|
||||
focusCalendar: () => {
|
||||
(
|
||||
calendar.selectedDateElem ||
|
||||
calendar.todayDateElem ||
|
||||
calendar.calendarContainer.querySelector('.flatpickr-day[tabindex]') ||
|
||||
calendar.calendarContainer
|
||||
).focus();
|
||||
}
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
if ($hasCalendar && !calendar) {
|
||||
calendar = createCalendar({
|
||||
options: {
|
||||
appendTo,
|
||||
dateFormat,
|
||||
defaultDate: $inputValue,
|
||||
locale,
|
||||
maxDate,
|
||||
minDate,
|
||||
mode: $mode
|
||||
},
|
||||
base: inputRef,
|
||||
input: inputRefTo,
|
||||
dispatch: event => {
|
||||
const detail = { selectedDates: calendar.selectedDates };
|
||||
|
||||
if ($range) {
|
||||
detail.dateStr = {
|
||||
from: inputRef.value,
|
||||
to: inputRefTo.value
|
||||
};
|
||||
} else {
|
||||
detail.dateStr = inputRef.value;
|
||||
}
|
||||
|
||||
return dispatch(event, detail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (calendar && !$range) {
|
||||
calendar.setDate($inputValue);
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (calendar) {
|
||||
calendar.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// $: hasCalendar.set($mode === 'single' || $mode === 'range');
|
||||
$: inputValue.set(value);
|
||||
$: value = $inputValue;
|
||||
</script>
|
||||
|
||||
<svelte:body
|
||||
on:click={({ target }) => {
|
||||
if (!calendar || !calendar.isOpen) {
|
||||
return;
|
||||
}
|
||||
if (datePickerRef && datePickerRef.contains(target)) {
|
||||
return;
|
||||
}
|
||||
if (!calendar.calendarContainer.contains(target)) {
|
||||
calendar.close();
|
||||
}
|
||||
}} />
|
||||
|
||||
<div on:click on:mouseover on:mouseenter on:mouseleave class={cx('--form-item', className)} {style}>
|
||||
<div
|
||||
bind:this={datePickerRef}
|
||||
class={cx('--date-picker', short && '--date-picker--short', light && '--date-picker--light', datePickerType && `--date-picker--${datePickerType}`, datePickerType === 'range' && $labelTextEmpty && '--date-picker--nolabel')}
|
||||
{id}>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
92
src/components/DatePicker/DatePickerInput.svelte
Normal file
92
src/components/DatePicker/DatePickerInput.svelte
Normal file
|
@ -0,0 +1,92 @@
|
|||
<script>
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
export let id = Math.random();
|
||||
export let iconDescription = '';
|
||||
export let labelText = '';
|
||||
export let hideLabel = false;
|
||||
export let pattern = '\\d{1,2}\\/\\d{1,2}\\/\\d{4}';
|
||||
export let type = 'text';
|
||||
export let placeholder = '';
|
||||
export let disabled = false;
|
||||
export let invalid = false;
|
||||
export let invalidText = '';
|
||||
export let style = undefined;
|
||||
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import Calendar16 from 'carbon-icons-svelte/lib/Calendar16';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
const {
|
||||
range,
|
||||
add,
|
||||
hasCalendar,
|
||||
declareRef,
|
||||
updateValue,
|
||||
blurInput,
|
||||
openCalendar,
|
||||
focusCalendar,
|
||||
inputValue
|
||||
} = getContext('DatePicker');
|
||||
|
||||
let inputRef = undefined;
|
||||
let iconRef = undefined;
|
||||
|
||||
add({ id, labelText });
|
||||
|
||||
onMount(() => {
|
||||
declareRef({ id, ref: inputRef });
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cx('--date-picker-container', !labelText && '--date-picker--nolabel', className)}
|
||||
{style}>
|
||||
{#if labelText}
|
||||
<label
|
||||
class={cx('--label', hideLabel && '--visually-hidden', disabled && '--label--disabled')}
|
||||
for={id}>
|
||||
{labelText}
|
||||
</label>
|
||||
{/if}
|
||||
<div class={cx('--date-picker-input__wrapper')}>
|
||||
<input
|
||||
bind:this={inputRef}
|
||||
data-invalid={invalid || undefined}
|
||||
class={cx('--date-picker__input')}
|
||||
on:input
|
||||
on:input={({ target }) => {
|
||||
updateValue({ id, type: 'input', value: target.value });
|
||||
}}
|
||||
on:change={({ target }) => {
|
||||
updateValue({ id, type: 'change', value: target.value });
|
||||
}}
|
||||
on:keydown
|
||||
on:keydown={({ key }) => {
|
||||
if (key === 'ArrowDown') {
|
||||
focusCalendar();
|
||||
}
|
||||
}}
|
||||
on:blur
|
||||
on:blur={({ relatedTarget }) => {
|
||||
blurInput(relatedTarget);
|
||||
}}
|
||||
{id}
|
||||
{placeholder}
|
||||
{type}
|
||||
{pattern}
|
||||
{disabled}
|
||||
value={!$range ? $inputValue : undefined} />
|
||||
{#if $hasCalendar}
|
||||
<Calendar16
|
||||
role="img"
|
||||
class={cx('--date-picker__icon')}
|
||||
aria-label={iconDescription}
|
||||
title={iconDescription}
|
||||
on:click={openCalendar} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if invalid}
|
||||
<div class={cx('--form-requirement')}>{invalidText}</div>
|
||||
{/if}
|
||||
</div>
|
79
src/components/DatePicker/flatpickr.js
Normal file
79
src/components/DatePicker/flatpickr.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import flatpickr from 'flatpickr';
|
||||
import l10n from 'flatpickr/dist/l10n';
|
||||
import rangePlugin from 'flatpickr/dist/plugins/rangePlugin';
|
||||
import { cx } from '../../lib';
|
||||
|
||||
l10n.en.weekdays.shorthand.forEach((_, index) => {
|
||||
const shorthand = _.slice(0, 2);
|
||||
l10n.en.weekdays.shorthand[index] = shorthand === 'Th' ? 'Th' : shorthand.charAt(0);
|
||||
});
|
||||
|
||||
function updateClasses(instance) {
|
||||
const { calendarContainer, days, daysContainer, weekdayContainer, selectedDates } = instance;
|
||||
|
||||
calendarContainer.classList.add(cx('--date-picker__calendar'));
|
||||
calendarContainer.querySelector('.flatpickr-month').classList.add(cx('--date-picker__month'));
|
||||
|
||||
weekdayContainer.classList.add(cx('--date-picker__weekdays'));
|
||||
weekdayContainer.querySelectorAll('.flatpickr-weekday').forEach(node => {
|
||||
node.classList.add(cx('--date-picker__weekday'));
|
||||
});
|
||||
|
||||
daysContainer.classList.add(cx('--date-picker__days'));
|
||||
days.querySelectorAll('.flatpickr-day').forEach(node => {
|
||||
node.classList.add(cx('--date-picker__day'));
|
||||
if (node.classList.contains('today') && selectedDates.length > 0) {
|
||||
node.classList.add('no-border');
|
||||
} else if (node.classList.contains('today') && selectedDates.length === 0) {
|
||||
node.classList.remove('no-border');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateMonthNode(instance) {
|
||||
const monthText = instance.l10n.months.longhand[instance.currentMonth];
|
||||
const staticMonthNode = instance.monthNav.querySelector('.cur-month');
|
||||
|
||||
if (staticMonthNode) {
|
||||
staticMonthNode.textContent = monthText;
|
||||
} else {
|
||||
const monthSelectNode = instance.monthsDropdownContainer;
|
||||
const span = document.createElement('span');
|
||||
span.setAttribute('class', 'cur-month');
|
||||
span.textContent = monthText;
|
||||
monthSelectNode.parentNode.replaceChild(span, monthSelectNode);
|
||||
}
|
||||
}
|
||||
|
||||
function createCalendar({ options, base, input, dispatch }) {
|
||||
return new flatpickr(base, {
|
||||
...options,
|
||||
allowInput: true,
|
||||
disableMobile: true,
|
||||
clickOpens: true,
|
||||
locale: l10n[options.locale],
|
||||
plugins: [options.mode === 'range' && new rangePlugin({ position: 'left', input })].filter(
|
||||
Boolean
|
||||
),
|
||||
nextArrow:
|
||||
'<svg width="16px" height="16px" viewBox="0 0 16 16"><polygon points="11,8 6,13 5.3,12.3 9.6,8 5.3,3.7 6,3 "/><rect width="16" height="16" style="fill: none" /></svg>',
|
||||
prevArrow:
|
||||
'<svg width="16px" height="16px" viewBox="0 0 16 16"><polygon points="5,8 10,3 10.7,3.7 6.4,8 10.7,12.3 10,13 "/><rect width="16" height="16" style="fill: none" /></svg>',
|
||||
onChange: () => {
|
||||
dispatch('change');
|
||||
},
|
||||
onClose: () => {
|
||||
dispatch('close');
|
||||
},
|
||||
onMonthChange: ({}, {}, instance) => {
|
||||
updateMonthNode(instance);
|
||||
},
|
||||
onOpen: ({}, {}, instance) => {
|
||||
dispatch('open');
|
||||
updateClasses(instance);
|
||||
updateMonthNode(instance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { createCalendar };
|
4
src/components/DatePicker/index.js
Normal file
4
src/components/DatePicker/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import DatePicker from './DatePicker.svelte';
|
||||
|
||||
export default DatePicker;
|
||||
export { default as DatePickerInput } from './DatePickerInput.svelte';
|
|
@ -8,6 +8,7 @@ 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 DatePicker, { DatePickerInput } from './components/DatePicker';
|
||||
import FileUploader, {
|
||||
FileUploaderButton,
|
||||
FileUploaderItem,
|
||||
|
@ -95,6 +96,8 @@ export {
|
|||
Copy,
|
||||
CopyButton,
|
||||
DataTableSkeleton,
|
||||
DatePicker,
|
||||
DatePickerInput,
|
||||
FileUploader,
|
||||
FileUploaderButton,
|
||||
FileUploaderItem,
|
||||
|
|
35
yarn.lock
35
yarn.lock
|
@ -1738,6 +1738,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.40.tgz#0e6cb9b9bbd098031fa19e4b4e8131bc70e5de13"
|
||||
integrity sha512-p3KZgMto/JyxosKGmnLDJ/dG5wf+qTRMUjHJcspC2oQKa4jP7mz+tv0ND56lLBu3ojHlhzY33Ol+khLyNmilkA==
|
||||
|
||||
"@types/estree@0.0.39":
|
||||
version "0.0.39"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||
|
||||
"@types/events@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
|
@ -5097,6 +5102,11 @@ flatpickr@4.6.1:
|
|||
resolved "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.1.tgz#9eb498ab805dd27f5ae02e1ac6ac6c099ce45e94"
|
||||
integrity sha512-3ULSxbXmcMIRzer/2jLNweoqHpwDvsjEawO2FUd9UFR8uPwLM+LruZcPDpuZStcEgbQKhuFOfXo4nYdGladSNw==
|
||||
|
||||
flatpickr@^4.6.3:
|
||||
version "4.6.3"
|
||||
resolved "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.3.tgz#15a8b76b6e34e3a072861250503a5995b9d3bc60"
|
||||
integrity sha512-007VucCkqNOMMb9ggRLNuJowwaJcyOh4sKAFcdGfahfGc7JQbf94zSzjdBq/wVyHWUEs5o3+idhFZ0wbZMRmVQ==
|
||||
|
||||
flatted@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
|
||||
|
@ -6357,6 +6367,13 @@ is-promise@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
|
||||
integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
|
||||
|
||||
is-reference@^1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427"
|
||||
integrity sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==
|
||||
dependencies:
|
||||
"@types/estree" "0.0.39"
|
||||
|
||||
is-regex@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
|
||||
|
@ -7484,6 +7501,13 @@ lru-cache@^5.1.1:
|
|||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
|
||||
magic-string@^0.25.2:
|
||||
version "0.25.4"
|
||||
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz#325b8a0a79fc423db109b77fd5a19183b7ba5143"
|
||||
integrity sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.4"
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||
|
@ -9996,6 +10020,17 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
|||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rollup-plugin-commonjs@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb"
|
||||
integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==
|
||||
dependencies:
|
||||
estree-walker "^0.6.1"
|
||||
is-reference "^1.1.2"
|
||||
magic-string "^0.25.2"
|
||||
resolve "^1.11.0"
|
||||
rollup-pluginutils "^2.8.1"
|
||||
|
||||
rollup-plugin-node-resolve@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz#730f93d10ed202473b1fb54a5997a7db8c6d8523"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue