Merge pull request #153 from Conor-Mack/feature/md-datepicker-and-iconbutton

MD DatePicker and IconButton
This commit is contained in:
Conor_Mack 2020-03-12 14:15:59 +00:00 committed by GitHub
commit 88f1129688
16 changed files with 416 additions and 11 deletions

View File

@ -24,5 +24,8 @@
"lint:fix": "eslint --fix packages",
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\""
},
"dependencies": {}
"dependencies": {
"@material/icon-button": "4.0.0",
"date-fns": "^2.10.0"
}
}

View File

@ -169,6 +169,16 @@
"description": "Material Design <tr>.",
"props": {}
},
"DatePicker": {
"name": "DatePicker",
"description": "Material Design DatePicker",
"props": {
"date": "string",
"label": "string",
"onSelect": "event"
},
"tags": []
},
"H1": {
"name": "H1",
"description": "Sets the font properties as Roboto Headline1",
@ -217,6 +227,18 @@
},
"tags": []
},
"IconButton": {
"onClick": "event",
"disabled": "bool",
"href": "string",
"icon": "string",
"size": {
"type":"options",
"options": ["small", "medium", "large"],
"default": "medium"
},
"tags": []
},
"Label": {
"name": "Label",
"description": "A simple label component that displays its text in the standard Roboto Material Design font",

View File

@ -18,6 +18,7 @@
"@material/checkbox": "^4.0.0",
"@material/data-table": "4.0.0",
"@material/form-field": "^4.0.0",
"@material/icon-button": "4.0.0",
"@material/list": "4.0.0",
"@material/menu": "4.0.0",
"@material/radio": "^4.0.0",
@ -25,6 +26,7 @@
"@material/textfield": "^4.0.0",
"@nx-js/compiler-util": "^2.0.0",
"bcryptjs": "^2.4.3",
"date-fns": "^2.10.0",
"fs-extra": "^8.1.0",
"lodash": "^4.17.15",
"npm-run-all": "^4.1.5",

View File

@ -2,7 +2,7 @@ import { MDCRipple } from "@material/ripple"
export default function ripple(
node,
props = { colour: "primary", unbounded: false }
props = { colour: "primary", unbounded: true }
) {
node.classList.add("mdc-ripple-surface")
let component = new MDCRipple(node)

View File

@ -0,0 +1,178 @@
<script>
import { onMount } from "svelte"
import {
startOfMonth,
endOfMonth,
getDate,
getMonth,
getYear,
addMonths,
subMonths,
format,
} from "date-fns"
import { MDCMenu } from "@material/menu"
import { Textfield } from "../Textfield"
import Icon from "../Common/Icon.svelte"
import ripple from "../Common/Ripple.js"
import { Body1, Body2, Caption } from "../Typography"
import { IconButton } from "../IconButton"
let menu
let instance
let textfieldValue = ""
let daysArr = []
let navDate = new Date()
const weekdayMap = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
export let date = new Date()
export let label = ""
export let onSelect = selectedDate => {}
onMount(() => {
if (!!menu) {
instance = new MDCMenu(menu)
instance.open = false
instance.setFixedPostion = true
}
})
function selectDate(dayOfMonth) {
let month = getMonth(navDate)
let year = getYear(navDate)
date = new Date(year, month, dayOfMonth)
onSelect(date)
}
function addMonth() {
navDate = addMonths(navDate, 1)
}
function subtractMonth() {
navDate = subMonths(navDate, 1)
}
function openCalendar(isOpen) {
instance.open = isOpen === undefined ? !instance.open : isOpen
}
function textFieldChange(value) {
const isDate = /^\d{1,2}\/\d{1,2}\/\d{4}$/
if (isDate.test(value)) {
const [year, month, day] = value.split("/").reverse()
if (month > 0 && month <= 12 && (day > 0 && day <= 31)) {
date = new Date(year, month - 1, day)
navDate = date
openCalendar(true)
onSelect(date)
}
}
}
$: dateMonthEnds = endOfMonth(navDate).getDate()
$: dateMonthBegins = startOfMonth(navDate).getDay()
$: dayStart = dateMonthBegins + 1 //1 = sunday
$: monthAndYear = format(navDate, "MMMM y")
$: selectedDate = format(date, "dd/MM/yyyy")
$: dayOfSelectedDate = getDate(date)
$: for (let d = 1; d <= dateMonthEnds; d++) {
if (d === 1) {
daysArr = [d]
} else {
daysArr = [...daysArr, d]
}
}
$: rowRepeater =
dateMonthBegins > 5 && daysArr[daysArr.length - 1] > 30 ? 6 : 5
$: sameMonthAndYear =
getMonth(date) === getMonth(navDate) && getYear(date) === getYear(navDate)
</script>
<div class="mdc-menu-surface--anchor">
<Textfield
{label}
onChange={textFieldChange}
value={selectedDate}
trailingIcon={true}
useIconButton={true}
iconButtonClick={openCalendar}
icon="calendar_today" />
<div
bind:this={menu}
class="mdc-menu mdc-menu-surface bbmd-menu"
style={`margin-top: 70px`}>
<div class="calendar-container">
<div class="month-picker">
<div>
<IconButton icon="chevron_left" onClick={subtractMonth} />
</div>
<div class="centreText">
<Body1 text={monthAndYear} />
</div>
<div>
<IconButton icon="chevron_right" onClick={addMonth} />
</div>
</div>
<div class="week-days">
{#each weekdayMap as day, i}
<div class="centreText">
<Caption text={day} />
</div>
{/each}
</div>
<div
class="day-picker"
style={`grid-template-rows: repeat(${rowRepeater}, 40px)`}>
{#each daysArr as day, i}
<div
use:ripple
style={i === 0 ? `grid-column-start: ${dayStart}` : ``}
on:click={() => selectDate(day)}
class={`bbmd-day ${dayOfSelectedDate === day && sameMonthAndYear ? 'selected' : ''}`}>
<Body2 text={day} />
</div>
{/each}
</div>
</div>
<!-- Superfluous but necessary to keep the menu instance sweet -->
<ul class="mdc-list" role="menu" />
</div>
</div>
<style>
.bbmd-menu {
width: 330px;
height: auto;
padding: 5px;
}
.month-picker {
display: grid;
grid-template-columns: 40px 1fr 40px;
justify-content: center;
align-items: center;
}
.calendar-container {
display: grid;
height: 100%;
grid-template-rows: repeat(3, auto);
grid-gap: 5px;
}
.week-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
}
.day-picker {
display: grid;
grid-template-columns: repeat(7, 1fr);
}
.centreText {
text-align: center;
}
</style>

View File

@ -0,0 +1,15 @@
@import "@material/ripple/mdc-ripple.scss";
@import "@material/theme/mixins";
.bbmd-day {
transition: background-color .25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
display: flex;
border-radius: 50%;
justify-content: center;
align-items: center;
cursor: pointer;
}
.selected {
@include mdc-theme-prop(background-color, primary);
@include mdc-theme-prop(color, on-primary);
}

View File

@ -0,0 +1,2 @@
import "./_style.scss";
export { default as DatePicker } from "./DatePicker.svelte";

View File

@ -0,0 +1,66 @@
<script>
import ripple from "../Common/Ripple.js"
import ClassBuilder from "../ClassBuilder.js"
const cb = new ClassBuilder("icon-button")
let on = false
export let _bb
export let context = ""
export let onClick = () => {}
export let disabled = false
export let href = ""
export let icon = ""
export let onIcon = "" //on state icon for toggle button
export let size = "medium"
function onButtonClick() {
open = !open
onClick()
}
$: isToggleButton = !!icon && !!onIcon
$: useLinkButton = !!href
$: customs = { size }
$: props = { customs, extras: ["material-icons", context] }
$: iconBtnClass = cb.build({ props })
</script>
{#if useLinkButton}
<a
on:click={onButtonClick}
class={iconBtnClass}
{href}
{disabled}
role="button"
tabindex="0">
{#if isToggleButton}
<i
use:ripple
class="material-icons mdc-icon-button__icon mdc-icon-button__icon--on">
{onIcon}
</i>
<i use:ripple class="material-icons mdc-icon-button__icon">{icon}</i>
{:else}{icon}{/if}
</a>
{:else}
<button
on:click={onButtonClick}
class={iconBtnClass}
{disabled}
role="button"
aria-label="Add to favorites"
aria-pressed="false"
tabindex="0">
{#if isToggleButton}
<i use:ripple class="material-icons mdc-icon-button__icon">{icon}</i>
<i
use:ripple
class="material-icons mdc-icon-button__icon mdc-icon-button__icon--on">
{onIcon}
</i>
{:else}{icon}{/if}
</button>
{/if}

View File

@ -0,0 +1,18 @@
@import "@material/icon-button/mdc-icon-button";
.mdc-icon-button {
&.bbmd-mdc-icon-button--size-large {
@include mdc-icon-button-icon-size(24px);
}
&.bbmd-mdc-icon-button--size-medium {
@include mdc-icon-button-icon-size(20px);
}
&.bbmd-mdc-icon-button--size-small {
@include mdc-icon-button-icon-size(16px);
}
}

View File

@ -0,0 +1,2 @@
import "./_style.scss";
export { default as IconButton } from "./IconButton.svelte";

View File

@ -17,6 +17,8 @@
Icon,
List,
Select,
DatePicker,
IconButton,
} = props
let currentComponent
@ -34,6 +36,8 @@
Select,
Radiobutton,
Radiobuttongroup,
DatePicker,
IconButton,
],
},
}

View File

@ -205,5 +205,16 @@ export const props = {
value: "2",
},
],
}
},
DatePicker: {
_component: "@budibase/materialdesign-components/DatePicker",
_children: [],
label: "Date of Admission",
onSelect: date => console.log("SELECTED DATE", date)
},
IconButton: {
_component: "@budibase/materialdesign-components/IconButton",
_children: [],
icon: "calendar_today",
},
}

View File

@ -9,10 +9,12 @@
import HelperText from "./HelperText.svelte"
import CharacterCounter from "./CharacterCounter.svelte"
import Icon from "../Common/Icon.svelte"
import { IconButton } from "../IconButton"
const cb = new ClassBuilder("text-field", ["primary", "medium"])
let tf = null
export let tfHeight = null
let tfInstance = null
onMount(() => {
@ -40,6 +42,8 @@
export let placeholder = ""
export let icon = ""
export let trailingIcon = false
export let useIconButton = false
export let iconButtonClick = () => {}
export let textarea = false
export let rows = 4
export let cols = 40
@ -93,10 +97,11 @@
function changed(e) {
const val = e.target.value
value = val
if (_bb.isBound(_bb.props.value)) {
_bb.setStateFromBinding(_bb.props.value, val)
}
_bb.call(onChange, val)
onChange(value)
// if (_bb.isBound(_bb.props.value)) {
// _bb.setStateFromBinding(_bb.props.value, val)
// }
// _bb.call(onChange, val)
}
</script>
@ -105,7 +110,7 @@ TODO:Needs error handling - this will depend on how Budibase handles errors
-->
<div class="textfield-container" class:fullwidth>
<div bind:this={tf} class={blockClasses}>
<div bind:this={tf} bind:clientHeight={tfHeight} class={blockClasses}>
{#if textarea}
<CharacterCounter />
<textarea
@ -123,8 +128,15 @@ TODO:Needs error handling - this will depend on how Budibase handles errors
on:change={changed} />
{:else}
{#if renderLeadingIcon}
{#if useIconButton}
<IconButton
{icon}
context="mdc-text-field__icon mdc-text-field__icon--leading"
onClick={iconButtonClick} />
{:else}
<Icon context="text-field" {icon} />
{/if}
{/if}
<input
{id}
{disabled}
@ -139,8 +151,15 @@ TODO:Needs error handling - this will depend on how Budibase handles errors
on:focus={focus}
on:input={changed} />
{#if renderTrailingIcon}
{#if useIconButton}
<IconButton
{icon}
context="mdc-text-field__icon mdc-text-field__icon--trailing"
onClick={iconButtonClick} />
{:else}
<Icon context="text-field" {icon} />
{/if}
{/if}
{#if variant !== 'outlined'}
<div class="mdc-line-ripple" />
{/if}

View File

@ -2,4 +2,4 @@
export let text = ""
</script>
<div class="mdc-typography--caption">{text}</div>
<span class="mdc-typography--caption">{text}</span>

View File

@ -19,3 +19,5 @@ export { default as recordForm } from "./Templates/recordForm"
export { List, ListItem } from "./List"
export { Menu } from "./Menu"
export { Select } from "./Select"
export { DatePicker } from "./DatePicker"
export { IconButton } from "./IconButton"

View File

@ -756,6 +756,62 @@
npmlog "^4.1.2"
write-file-atomic "^2.3.0"
"@material/animation@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/animation/-/animation-4.0.0.tgz#19eaf377809f20978f8452a4237d27867d3ffd1d"
integrity sha512-IfzXzstWdtKQcsNWu+s2Hpz5dBwkTHtgtzoesr+FC7TqENH+SJdsF1ntnZI1XVi2C9ZlBf7f4BSmXpWHD0MIlw==
dependencies:
tslib "^1.9.3"
"@material/base@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/base/-/base-4.0.0.tgz#535574d0b63c18892cfb70e88b27bc8f8090677a"
integrity sha512-vHm7fkqXzjdfxifXvlmaZColoIfKuWmO+1rvdzDORTWP+A8Dq70cgKd2I1SBqxzDGjOasMzHbQI6f9MISQf2vQ==
dependencies:
tslib "^1.9.3"
"@material/dom@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/dom/-/dom-4.0.0.tgz#f0e68c4429e555040304c958bb3e11614276fdfa"
integrity sha512-GRCJT9+PGWqygZwGf1XLTrbmzP35YWG7+T0hpfhoIJO8VDiMTeyfvhJXFuA2wh9pD0noEjte0lmbdBlykrbWZw==
dependencies:
tslib "^1.9.3"
"@material/feature-targeting@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-4.0.0.tgz#8d9d1470801a1fd166773731613d9fa89e0fd85e"
integrity sha512-0gk+f151vqmEdWkrQ9ocPlQRU9aUtSGsVBhletqIbsthLUsZIz9qk25FHjV1wHd/bGHknd9NH+T8ENprv3KLFg==
"@material/icon-button@4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/icon-button/-/icon-button-4.0.0.tgz#f7293462f1b4967712f9b8755b87c2228dcccc3d"
integrity sha512-b1SyWtr2cwlKgIVo+irxRgW0lpIzauADLAxQEJ8/RTO9qVFviohJUnS+5QQCkC5zex5Q52OmQ+aNl0KRjkUdvQ==
dependencies:
"@material/base" "^4.0.0"
"@material/feature-targeting" "^4.0.0"
"@material/ripple" "^4.0.0"
"@material/theme" "^4.0.0"
tslib "^1.9.3"
"@material/ripple@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-4.0.0.tgz#906ba421a3e6c68651d829d44c50a95060aa1e75"
integrity sha512-9BLIOvyCP5sM+fQpLlcJZWyrHguusJq8E5A1pxg0wQwputOyaPBM7recHhYkJmVjzRpTcPgf1PkvkpN6DKGcNg==
dependencies:
"@material/animation" "^4.0.0"
"@material/base" "^4.0.0"
"@material/dom" "^4.0.0"
"@material/feature-targeting" "^4.0.0"
"@material/theme" "^4.0.0"
tslib "^1.9.3"
"@material/theme@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/theme/-/theme-4.0.0.tgz#8de388fcdbae20fd09b02a3bdef3049bf2f623a8"
integrity sha512-vS4G4rusJTatTH50kSYO1U3UGN8EY9kGRvPaFsEFKikJBOqcR6KWK9H9/wCLqqd6nDNifEj9H2sdWw1AV4NA6Q==
dependencies:
"@material/feature-targeting" "^4.0.0"
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@ -1630,6 +1686,11 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
date-fns@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.10.0.tgz#abd10604d8bafb0bcbd2ba2e9b0563b922ae4b6b"
integrity sha512-EhfEKevYGWhWlZbNeplfhIU/+N+x0iCIx7VzKlXma2EdQyznVlZhCptXUY+BegNpPW2kjdx15Rvq503YcXXrcA==
dateformat@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"