How to grey out the font as well as the checkbox - css

I would like to grey out the text within my filter section of a calendar app I am building but only seem to be able to code this onto the checkbox itself.
I have already tried passing this through my SCSS containers and labels with the ~ syntax to pass done from parent to child selectors. Cannot seem to find a way to solve this without breaking DRY principles. I have managed to get this to work on the checkbox but not the font within the same container.
Checkbox/index.js
import React from 'react'
import './index.css';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import PropTypes from 'prop-types'
/*
based off the fake checkbox styles from here
should still be screen-reader/keyboard accessible
https://www.w3schools.com/howto/howto_css_custom_checkbox.asp
*/
class Checkbox extends React.Component {
render() {
const { name, label, value, checked, onChange, handleClose, dataObject, ...rest } = this.props
return (
<div {...rest}>
<label className="checkbox-container">
<span className="checkbox-label">{label}</span>
<input
type="checkbox"
checked={checked}
value={value}
onChange={onChange} />
<span className="checkmark"></span>
{this.props.handleClose && <FontAwesomeIcon onClick={this.handleClick.bind(this)} className='checkbox-icon' size="1x" icon={['fal', 'times']} />}
</label>
</div>
)
}
handleClick(e) {
e.preventDefault()
this.props.handleClose(this.props.dataObject)
}
}
Checkbox.propTypes = {
handleClose: PropTypes.func, // optional
onChange: PropTypes.func.isRequired,
label: PropTypes.string.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
dataObject: PropTypes.object, // optional -> used for the 'x' button
checked: PropTypes.bool.isRequired,
}
export default Checkbox
CalendersFiltersSection/index.js
import React from 'react'
import './index.css';
import PropTypes from 'prop-types'
import Checkbox from '../../../Checkbox'
import { defaultCalendarFilters} from '../../../../../constants'
class CalendarFiltersSection extends React.Component {
render() {
const { filters } = this.props
// let orderedFilters = Object.values(filters)
// orderedFilters.sort((a,b) => {
// return a.displayOrder - b.displayOrder
// })
return (
<section className="calendar-filters-section">
<div className='title'>{this.props.title}</div>
{ Object.values(filters).map(filter => {
let className = defaultCalendarFilters[filter.id].label.toLowerCase()
return <Checkbox className={`checkbox-event-type-${className}`} key={filter.id} label={filter.label} name={filter.id} checked={filter.checked} value={filter.id} onChange={this.props.handleInputChange} />
})
}
</section>
)
}
}
CalendarFiltersSection.propTypes = {
handleInputChange: PropTypes.func.isRequired,
filters: PropTypes.object.isRequired,
title: PropTypes.string.isRequired
}
export default CalendarFiltersSection
Checkbox/index.css
.checkbox-container {
display: block;
position: relative;
padding-left: 26px;
margin-bottom: 8px;
cursor: pointer;
font-size: 14px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Hide the browser's default checkbox */
.checkbox-container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
/* Create a custom checkbox */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 18px;
width: 18px;
background-color: #525259;
}
/* On mouse-over, add a grey background color */
/*.checkbox-container:hover input ~ .checkmark {
background-color: #ccc;
}
*/
/* When the checkbox is checked, add a blue background */
/*.checkbox-container input:checked ~ .checkmark {
background-color: #2196F3;
}*/
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.checkbox-container input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.checkbox-container .checkmark:after {
left: 7px;
top: 3px;
width: 3px;
height: 7px;
border: solid black;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
box-sizing: initial;
color: var(--secondary-font-color);
}
.checkbox-event-type-trainings .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-trainings-color);
}
.checkbox-event-type-medizinisch .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-medizinisch-color);
}
.checkbox-event-type-spiele .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-spiele-color);
}
.checkbox-event-type-tests .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-tests-color);
}
.checkbox-event-type-andere .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-andere-color);
}
.checkbox-label {
color: var(--secondary-font-color);
}
.checkbox-icon {
position:absolute;
right: 0;
top: 4px;
z-index:5;
}
.checkbox-container .checkbox-icon {
visibility: hidden;
}
.checkbox-container:hover .checkbox-icon {
visibility: visible;
}
CalendarFiltersSection/index.css
.calendar-filters-section {
align-self: flex-start;
color: var(--secondary-font-color);
margin-bottom: 4rem;
}
Constants.js
import { history } from './util/history'
export const API_HOST = process.env.REACT_APP_USE_STAGING_API ?
"http://example.com" : "http://127.0.0.1:8000"
export const EVENTS_LIST_URL = `${API_HOST}/events/`
export const CALENDARS_LIST_URL = `${API_HOST}/users/calendars/`
export const USERS_LIST_URL = `${API_HOST}/users/`
export const getTeamsListUrl = (clubId) =>
`${API_HOST}/clubs/${clubId}/teams/`
export const getClubsTeamsPlayersListUrl = (clubId, teamId) =>
`${API_HOST}/clubs/${clubId}/teams/${teamId}/players/`
//TODO combine all of these into one data structure .
export const mapNameToEventTypeNumber = {
0: 'andere',
1: 'trainings',
2: 'tests',
3: 'medizinisch',
4: 'medizinisch',
5: 'medizinisch',
6: 'spiele',
8: 'medizinisch',
}
//TODO combine all of these into one data structure .
// updaet: the backend updated to use strings instead of numbers...
export const mapMainCategoryNameToSubCategoryName = {
social: 'andere',
practice: 'trainings',
assessment: 'tests',
doctor: 'medizinisch',
physio: 'medizinisch',
operation: 'medizinisch',
match: 'spiele',
meeting: 'andere',
}
// 'event_types': ((0, 'SOCIAL'),
// (1, 'PRACTICE'),
// (2, 'ASSESSMENT'),
// (3, 'DOCTOR'),
// (4, 'PHYSIO'),
// (5, 'OPERATION'),
// (6, 'MATCH'),
// (7, 'PRIVATE'),
// (8, 'MEETING'),
// (9, 'SCHOOL/JOB'),
// (10, 'MISC')),
export const mapSpecificEventTypeCategoryToEventTypeNumber = {
0: 'Social',
1: 'Practice',
2: 'Examination',
3: 'Doctor',
4: 'Rehab',
5: 'Physio',
6: 'Match',
7: 'Injury',
8: 'Treatment',
}
export const mappingOfSpecificEventTypeToFilterCategories = {
1: 0,
3: 1,
4: 1,
5: 1,
6: 2,
2: 3,
0: 4
}
export const defaultCalendarFilters = [
{
id: 0,
checked: true,
label: 'Trainings',
eventTypeNumbers: ['practice']
// eventTypeNumbers: ['1']
},
{
id: 1,
checked: true,
label: 'Medizinisch',
eventTypeNumbers: ['doctor','physio','operation']
// eventTypeNumbers: ['3','4','5']
},
{
id: 2,
checked: true,
label: 'Spiele',
eventTypeNumbers: ['match']
// eventTypeNumbers: ['6']
},
{
id: 3,
checked: true,
label: 'Tests',
eventTypeNumbers: ['assessment']
// eventTypeNumbers: ['2']
},
{
id: 4,
checked: true,
label: 'Andere',
eventTypeNumbers: ['meeting', 'misc']
// eventTypeNumbers: ['0']
},
]
export const mapClubRoleNumberToSlug = {
'trainer': 0,
'athletiktrainer': 1,
'torwarttrainer': 2,
'individualtrainer': 3,
'rehabilitationtrainer': 4,
'arzt': 5,
'physiotherapeut': 6,
'videoanalyst': 7,
'athlet': 8,
'betreuer': 9,
'clubadmin': 10,
}
export const clubRolesData = {
'trainer': {
slug: 'trainer',
roleNum: 0,
isStaff: true,
isAthlete: false
},
'athletiktrainer': {
slug: 'athletiktrainer',
roleNum: 1,
isStaff: true,
isAthlete: false
},
'torwarttrainer': {
slug: 'torwarttrainer',
roleNum: 2,
isStaff: true,
isAthlete: false
},
'individualtrainer': {
slug: 'individualtrainer',
roleNum: 3,
isStaff: true,
isAthlete: false
},
'rehabilitationtrainer': {
slug: 'rehabilitationtrainer',
roleNum: 4,
isStaff: true,
isAthlete: false
},
'arzt': {
slug: 'arzt',
roleNum: 5,
isStaff: true,
isAthlete: false
},
'physiotherapeut': {
slug: 'physiotherapeut',
roleNum: 6,
isStaff: true,
isAthlete: false
},
'videoanalyst': {
slug: 'videoanalyst',
roleNum: 7,
isStaff: true,
isAthlete: false
},
'athlet': {
slug: 'athlet',
roleNum: 8,
isStaff: false,
isAthlete: true
},
'betreuer': {
slug: 'betreuer',
roleNum: 9,
isStaff: true,
isAthlete: false
},
'clubadmin': {
slug: 'clubadmin',
roleNum: 10,
isStaff: true,
isAthlete: false
},
}
export const ATHLETE_ROLES = [
'athlet'
]
export const STAFF_ROLES = [
'arzt',
'athletiktrainer',
'betreuer',
// 'clubadmin',
'individualtrainer',
'physiotherapeut',
'rehabilitationtrainer',
'torwarttrainer',
'trainer',
'videoanalyst',
]
export const getUserData = () => {
let userData = null
const userDataJson = localStorage.userData
if(userDataJson) {
userData = JSON.parse(userDataJson)
} else {
history.push('./login')
return
}
return userData
}
export const NAVBAR_TABS = [
{
text: 'Dein Club',
link: '/club-member',
active: false,
icon: 'fa-shield'
},
{
text: 'Kalender',
link: '/calendar',
active: false,
icon: 'fa-calendar-alt'
},
{
text: 'Library',
link: '/library/abilities',
active: false,
icon: 'fa-books'
}
]

Related

I'm not able to change the style of DevExtreme's popup modal dialog using CSS

I have Vue.js app where use DevExtreme's DxDataGrid and DxPopup components. The datagrid contains a list of records (rows). When I double click particular row, the popup modal dialog opens and shows appropriate fields. That's fine.
The issue is I'm not able to change the style of popup modal dialog using CSS selectors in my component. I need to change all - border, colors, size of fields, fonts and so on.
The code looks as following:
The list:
<template>
<div>
<DxDataGrid
:data-source="testArray"
:row-alternation-enabled="true"
:show-borders="true"
:columns="columns"
#row-dbl-click="rowDblClick"
>
</DxDataGrid>
<v-app-types-by-product-modal ref="appTypesByProductModal" />
</div>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxDataGrid } from "devextreme-vue/data-grid";
import VAppTypesByProductModal from "./v-app-types-by-product-modal.vue";
export default {
components: {
DxDataGrid,
VAppTypesByProductModal,
},
data() {
return {
testArray: [
{ Id: 1, ProductName: "Product 1", Status: "Yes", StageName: "Begin", SignDate: "11.07.2022" },
{ Id: 2, ProductName: "Product 2", Status: "Yes", StageName: "Middle", SignDate: "12.07.2022" },
{ Id: 3, ProductName: "Product 3", Status: "No", StageName: "End", SignDate: "13.07.2022" },
],
columns: [
{ dataField: "Id", caption: "ID", dataType: "number", width: "50px" },
{ dataField: "ProductName", caption: "PRODUCT", dataType: "string", width: "150px" },
{ dataField: "Status", caption: "STATUS", dataType: "string", width: "150px" },
{ dataField: "StageName", caption: "STAGE", dataType: "string", width: "150px"},
{ dataField: "SignDate", caption: "DATA", dataType: "string",width: "150px"},
{
type: "buttons",
width: "150px",
buttons: [
{
hint: "Edit",
icon: "edit",
visible(e) {
return !e.row.isEditing;
},
onClick: (e) => {
this.$refs.appTypesByProductModal.showModal(e.row.data);
},
},
{
hint: "Remove",
icon: "remove",
visible(e) {
return !e.row.isEditing;
},
},
],
},
],
selectedElement: {},
};
},
methods: {
rowDblClick(e) {
this.$refs.appTypesByProductModal.showModal(e.data);
},
},
};
</script>
<style scoped>
</style>
The modal dialog:
<template>
<v-modal-dialog
:visible="isModalVisible"
title="Title"
:width="500"
:height="500"
#hiding="onHiding"
>
<DxForm :form-data="selectedItem" label-mode="static">
<DxSimpleItem data-field="Id" :is-required="true" />
<DxSimpleItem data-field="ProductName" :is-required="true" :editor-options="{disabled: true}" />
<DxSimpleItem data-field="Status" />
<DxSimpleItem data-field="StageName" />
<DxSimpleItem data-field="SignDate" />
</DxForm>
<DxToolbarItem
widget="dxButton"
toolbar="bottom"
:options="okButtonOptions"
/>
<DxToolbarItem
widget="dxButton"
toolbar="bottom"
:options="closeButtonOptions"
/>
</v-modal-dialog>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxForm, DxSimpleItem } from "devextreme-vue/form";
import { DxToolbarItem } from "devextreme-vue/popup";
import VModalDialog from "./v-modal-dialog.vue";
export default {
components: {
VModalDialog,
DxForm,
DxSimpleItem,
DxToolbarItem,
},
data() {
return {
isModalVisible: false,
selectedItem: {},
okButtonOptions: {
text: "Ok",
onClick: () => {
this.clearFormData();
this.isModalVisible = false;
},
},
closeButtonOptions: {
text: "Close",
onClick: () => {
this.clearFormData();
this.isModalVisible = false;
},
},
};
},
methods: {
showModal(item) {
this.selectedItem = {...item};
this.isModalVisible = true;
},
clearFormData() {
this.selectedItem = null;
},
onHiding() {
this.clearFormData();
this.isModalVisible = false;
},
},
};
</script>
<style scoped>
</style>
The modal dialog base:
<template>
<DxPopup
v-bind="$attrs"
v-on="$listeners"
:hide-on-outside-click="false"
:drag-enabled="true"
position="center"
#hiding="onHiding"
>
<slot v-for="(_, name) in $slots" :name="name" :slot="name" />
</DxPopup>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxPopup } from "devextreme-vue/popup";
export default {
inheritAttrs: false,
components: {
DxPopup,
},
data() {
return {}
},
methods: {
onHiding() {
this.$emit("hiding");
},
},
};
</script>
<style scoped>
/* ::v-deep .dx-popup-normal {
border-radius: 20px !important;
}
::v-deep .dx-popup-title {
border-bottom: 0px !important;
} */
/* #dxPopupContainer .dx-popup-normal {
border-radius: 20px !important;
}
#dxPopupContainer .dx-toolbar {
border-bottom: 0px;
} */
/* #dxPopupContainer {
color: red;
background-color: green;
} */
/* ::v-deep .dx-overlay-content .dx-popup-normal .dx-popup-draggable .dx-resizable {
border-radius: 20px;
}
::v-deep .dx-toolbar > .dx-widget > dx-visibility-change-handler > dx-collection > dx-popup-title dx-has-close-button {
border-bottom: 0px;
} */
/* #dxPopupContainer .dx-popup-normal {
border-radius: 20px !important;
}
#dxPopupContainer .dx-popup-title {
border-bottom: 0px !important;
} */
</style>
Here's the CodeSandbox demo:
https://codesandbox.io/s/overview-devextreme-data-grid-forked-5cegqw
As you can see I've tried something, but nothing helped.
How to overcome this issue?
Replace
::v-deep .dx-popup-normal {
border-radius: 20px !important;
}
with
:global(.dx-popup-normal) {
border-radius: 20px !important;
}

vuetify: how to make v-data-table row blink when item values are updated

I would like to create a table that when items is updated, the row blink once.
i managed to make a row blink when the component starts, but it does not blink when value is updated.
i created an example with two css class (only for test), one that blinks once and another that blinks infinite.
if we update items values, we can see that the infinite still blinks and change rows as the condition is filled, but the items that should blink once, didn't change.
any help will be appreciated.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
items: [{
id: 1,
name: 'Frozen Yogurt',
calories: 159,
},
{
id: 2,
name: 'Ice cream sandwich',
calories: 237,
},
{
id: 3,
name: 'Eclair',
calories: 262,
},
{
id: 4,
name: 'Cupcake',
calories: 305,
},
],
headers: [{
text: 'Dessert',
value: 'name',
},
{
text: 'Calories',
value: 'calories'
},
],
};
},
methods: {
blink(item) {
if (item.calories > 200){
return 'blink-great';
} else {
return 'blink-less';
}
},
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
},
updateValues() {
const newValues = this.items.map((el) => {
return {...el, calories: this.getRandomInt(100,500)};
})
this.items = newValues
}
},
computed: {
displayItems() {
var newItems = this.items.map((el) => {
return {...el, calories: el.calories * 1};
})
return newItems
}
},
});
.blink-less {
animation: blinking ease-out 1s 3;
--background-color: #FF0000
}
.blink-great {
animation: blinking ease-out 1s infinite;
--background-color: #0000FF
}
#keyframes blinking {
0% {
background-color: var(--background-color);
}
100% {
background-color: #fff;
}
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row class="pa-5">
<v-btn
#click="updateValues()"
> Update value </v-btn>
</v-row>
<v-row class="px-5">
<v-data-table
hide-default-footer
:headers="headers"
:items="displayItems"
:item-class="blink"
/>
</v-row>
</v-container>
</v-app>
</div>
I created a flag and added new css classes.. it works but has poor code. If i found a better and clean solution i post it here
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
controlAnimation: false,
items: [{
id: 1,
name: 'Frozen Yogurt',
calories: 159,
},
{
id: 2,
name: 'Ice cream sandwich',
calories: 237,
},
{
id: 3,
name: 'Eclair',
calories: 262,
},
{
id: 4,
name: 'Cupcake',
calories: 305,
},
],
headers: [{
text: 'Dessert',
value: 'name',
},
{
text: 'Calories',
value: 'calories'
},
],
};
},
methods: {
blink(item) {
if (this.controlAnimation) {
let itemClassTrue = 'blink-even-true'
/* check item.value, based on condition...
itemClassTrue = 'blink-less-true'
itemClassTrue = 'blink-even-true'
itemClassTrue = 'blink-great-true'
*/
return itemClassTrue;
} else {
let itemClassFalse = 'blink-great-false'
/* check item.value, based on condition...
itemClassTrue = 'blink-less-false'
itemClassTrue = 'blink-even-false'
itemClassTrue = 'blink-great-false'
*/
return itemClassFalse;
}
},
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
},
updateValues() {
const newValues = this.items.map((el) => {
return {...el, calories: this.getRandomInt(100,500)};
})
this.items = newValues
}
},
computed: {
displayItems() {
this.controlAnimation = !this.controlAnimation
var newItems = this.items.map((el) => {
return {...el, calories: el.calories * 1};
})
return newItems
}
},
});
.blink-less-true {
animation: blinking-less-true 1s 2 !important;
--background-color: rgb(196, 76, 76) !important;
}
.blink-even-true {
animation: blinking-even-true 1s 2 !important;
--background-color: #fa0 !important;
}
.blink-great-true {
animation: blinking-great-true 1s 2 !important;
--background-color: rgb(3, 163, 70) !important;
}
.blink-less-false {
animation: blinking-less-false 1s 2 !important;
--background-color: rgb(196, 76, 76) !important;
}
.blink-even-false {
animation: blinking-even-false 1s 2 !important;
--background-color: #fa0 !important;
}
.blink-great-false {
animation: blinking-great-false 1s 2 !important;
--background-color: rgb(3, 163, 70) !important;
}
#keyframes blinking-less-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-even-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-great-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-less-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-even-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-less-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row class="pa-5">
<v-btn
#click="updateValues()"
> Update value </v-btn>
</v-row>
<v-row class="px-5">
<v-data-table
hide-default-footer
:headers="headers"
:items="displayItems"
:item-class="blink"
/>
</v-row>
</v-container>
</v-app>
</div>
You're not far off with item-class. It can take a string, which tells it to look for a prop on the item's object. With this, as you're updating the data, you can just add/remove the blink class to a prop you create on the fly.
In this case, I'm just going to name the prop dynamicClass
something like this:
template:
<v-data-table
hide-default-footer
:headers="headers"
:items="items"
:item-class="dynamicClass"
/>
script:
methods: {
updateRandom() {
const randomIdx = this.generateRandomNumber(0, this.items.length - 1);
const randomNumber = this.generateRandomNumber(1, 1200);
let item = { ...this.items[randomIdx] };
item.calories = randomNumber;
item.dynamicClass = "blink-less";
this.$set(this.items, randomIdx, item);
setTimeout(() => {
delete item.dynamicClass;
this.$set(this.items, randomIdx, item);
}, 2000);
},
generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
},
},
The idea here is that we are adding & removing the class from the item/object in the array. doing it with this.$set() guarantees reactivity. But you could also use Array.splice() but either way, you want to replace the data object at that index to maintain reactivity.
Here is a working example in a codesandbox

Vue Carousel on iOS Safari only works with first slide

Good morning, I've build a custom vue carousel and implemented it into my progressive web app. Works fine on Chrome Android, whereas on iOS Safari - id doesn't. I received feedback that the carousel only allows to slide the first page and on the second - it doesn't react.
The problem is, I don't own Mac or Iphone and for now I can't test it myself. Could someone help me out and test the carousel on aforementioned devices? Here is the fiddle.
https://jsfiddle.net/LSliwa/97cpgq3z/
HTML:
<div id="app">
<Carousel>
<div v-for="(todo, index) in todos" :key="index">
<div class="app__element">
{{ todo.text }}
</div>
</div>
</Carousel>
</div>
CSS:
#app {
padding-top: 1rem;
}
.app__element {
text-align: center;
border: 1px solid red;
padding: 1rem;
margin: 0 1rem;
}
/* carousel styles */
.carousel {
overflow-x: hidden;
overflow-y: visible;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.carousel-navdots {
display: flex;
justify-content: center;
margin-bottom: 2rem;
}
.carousel-navdot {
height: 10px;
width: 10px;
border-radius: 50%;
background-color: gray;
margin: 0 5px;
transition: all 0.5s;
cursor: pointer;
}
.active {
background-color: #05AA19;
}
.carousel-wrapper {
display: flex;
align-items: stretch;
cursor: grab;
}
.carousel-wrapper:active {
cursor: grabbing;
}
.carousel-wrapper>div,
.carousel-wrapper>p,
.carousel-wrapper>span,
.carousel-wrapper>ul {
width: 100%;
flex-shrink: 0;
position: relative;
}
.scrolling {
transition: transform 0.5s;
}
.inactive {
flex-direction: column;
}
#media (min-width: 1024px) {
.inactive {
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
}
}
JS:
Vue.component('Carousel', {
template: '<div class="carousel"><div class="carousel-navdots" v-if="isActive" v-show="pagination"><div class="carousel-navdot" :class="{ \'active\': n == currentPage + 1 }" v-for="n in numberOfPages" v-show="numberOfPages > 1" :key="n" ref="navdot" #click="scrollWithNavdots(n)"></div></div><div class="carousel-wrapper a-stretch" :class="{ \'inactive\': !isActive }" :style="{ transform: `translateX(${translate}px)` }" ref="wrapper" v-on="isActive && active ? { touchstart: onTouchStart, touchmove: onTouchMove, touchend: onTouchEnd, mousedown: onTouchStart, mousemove: onTouchMove, mouseup: onTouchEnd } : {}"><slot></slot></div></div>',
props: {
active: {
type: Boolean,
default: () => true
},
activeOnViewport: {
type: Array,
default: () => [[1, true]]
},
columns: {
type: Array,
default: () => [[1, 1]]
},
pagination: {
type: Boolean,
default: () => true
},
sensitivity: {
type: Number,
default: () => 40
},
startFromPage: {
type: Number,
default: () => 0
},
autoplay: {
type: Boolean,
default: () => false
},
autoplaySpeed: {
type: Number,
default: () => 5
},
// usuń custom length jeżeli rerendering z :key zadziała
customLength: {
type: Number
}
},
data() {
return {
currentPage: this.startFromPage,
numberOfColumns: 1,
moveStart: null,
move: null,
currentTranslate: 0,
length: this.customLength == null ? this.$slots.default.length : this.customLength,
viewportColumnsMatched: null,
isActive: null,
mousedown: false,
elementWidth: 0,
autoplayInterval: null,
animateTimeout: null,
test: {
touchmove: false,
touchstart: false,
}
}
},
computed: {
maxScrollLeft() {
return this.currentPage == 0 ? true : false;
},
maxScrollRight() {
return this.currentPage + 1 == this.numberOfPages ? true : false;
},
numberOfPages() {
return Math.ceil(this.length / this.numberOfColumns);
},
sortedColumns() {
return this.columns.sort((a, b) => {
return a[0] - b[0];
});
},
sortedActive() {
return this.activeOnViewport.sort((a, b) => {
return a[0] - b[0];
})
},
translate() {
return -this.elementWidth * this.currentPage
}
},
watch: {
currentPage() {
this.animateCarousel();
this.$emit('change-page', this.currentPage);
},
startFromPage() {
this.currentPage = this.startFromPage;
},
// usuń watch customLength jeżeli rerendering z :key zadziała
customLength() {
this.length = this.customLength;
if (this.currentPage > this.length - 1) {
this.currentPage = this.length - 1;
}
}
},
methods: {
animateCarousel() {
this.$refs.wrapper.classList.add('scrolling');
this.animateTimeout = setTimeout(() => {
this.$refs.wrapper.classList.remove('scrolling');
}, 500);
},
scrollWithNavdots(index) {
this.currentPage = index - 1;
this.currentTranslate = parseFloat(this.$refs.wrapper.style.transform.slice(11, -3));
},
onTouchStart() {
clearInterval(this.autoplayInterval);
if (event.type == 'touchstart') {
this.moveStart = event.touches[0].screenX
} else {
this.moveStart = event.screenX;
this.mousedown = true;
}
},
onTouchMove() {
let translate;
if (event.type == 'touchmove') {
this.move = event.touches[0].screenX - this.moveStart;
} else if (event.type == 'mousemove' && this.mousedown == true) {
this.move = event.screenX - this.moveStart
}
if (this.move < 0 && this.maxScrollRight || this.move > 0 && this.maxScrollLeft) {
translate = this.translate + this.move*0.2;
} else {
translate = this.translate + this.move*0.5;
}
this.$refs.wrapper.style.transform = `translateX(${translate}px)`;
},
onTouchEnd() {
this.test.touchstart = false;
this.test.touchmove = false;
if (Math.abs(this.move) > this.sensitivity) {
if (this.move > 0 && !this.maxScrollLeft) {
this.currentPage--
} else if (this.move < 0 && !this.maxScrollRight) {
this.currentPage++
} else {
this.animateCarousel();
}
} else if (Math.abs(this.move) < this.sensitivity && Math.abs(this.move) > 1) {
this.animateCarousel();
}
this.$refs.wrapper.style.transform = `translateX(${this.translate}px)`;
this.mousedown = false;
this.moveStart = null;
this.move = null;
},
setColumns() {
this.viewportColumnsMatched = false;
this.sortedColumns.forEach(cur => {
if (window.matchMedia(`(min-width: ${cur[0]}px)`).matches) {
this.viewportColumnsMatched = true;
this.numberOfColumns = cur[1];
this.$refs.wrapper.childNodes.forEach(cur => {
cur.style.width = `${100/this.numberOfColumns}%`;
});
}
});
if (!this.viewportColumnsMatched) {
this.numberOfColumns = 1;
this.$refs.wrapper.childNodes.forEach(cur => {
cur.style.width = '100%';
});
}
setTimeout(() => {
this.elementWidth = this.$slots.default[0].elm.offsetWidth;
});
},
setActive() {
this.sortedActive.forEach(cur => {
if (window.matchMedia(`(min-width: ${cur[0]}px)`).matches) {
this.isActive = cur[1];
}
});
},
runCarousel() {
if (this.autoplay) {
this.autoplayInterval = setInterval(() => {
this.currentPage++
if (this.currentPage == this.numberOfPages) this.currentPage = 0;
}, this.autoplaySpeed * 1000);
}
},
},
mounted() {
this.setColumns();
this.setActive();
this.runCarousel();
window.addEventListener('resize', () => {
this.setColumns();
this.setActive();
});
},
destroyed() {
clearInterval(this.autoplayInterval);
clearTimeout(this.animateTimeout);
}
});
new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn Vue", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
},
})
Thank you in advance for your help, good people.

How to make "ng-multiselect-dropdown" a required field in Anguar7?

How can I make ng-multiselect-dropdown a required field, which must accept at least one selected item ?
<ng-multiselect-dropdown
[placeholder]="'Select countries'"
[data]="countries"
[(ngModel)]="countriesSelectedItems"
[settings]="countriesDropdownSettings"
(onSelect)="onItemSelect($event)"
(onSelectAll)="onSelectAll($event)"
name="countries"
class="form-control">
</ng-multiselect-dropdown>
There was no documentation about it on npmjs.org
So, we take the approach we take to make any field as required in a template driven form in Angular.
relevant html:
<form (ngSubmit)='submission()'>
<ng-multiselect-dropdown [placeholder]="'custom placeholder'" [data]="dropdownList" [(ngModel)]="selectedItems" [settings]="dropdownSettings"
(onSelect)="onItemSelect($event)" (onSelectAll)="onSelectAll($event)" [required]='requiredField' [ngClass]='setClass()'
name='countrySelect'>
</ng-multiselect-dropdown>
<p *ngIf="!requiredField">
You must select a value !!
</p>
<br/>
<button type='submit'>submit</button>
</form>
relevant TS:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular 5';
dropdownList = [];
selectedItems = [];
dropdownSettings = {};
requiredField: boolean = false;
ngOnInit() {
this.dropdownList = [
{ "item_id": 1, "item_text": "India" },
{ "item_id": 2, "item_text": "Singapore" },
{ "item_id": 3, "item_text": "Australia" },
{ "item_id": 4, "item_text": "Canada" },
{ "item_id": 5, "item_text": "Pakistan" },
{ "item_id": 6, "item_text": "Japan" }
];
this.selectedItems = [
{ "item_id": 2, "item_text": "Singapore" },
{ "item_id": 3, "item_text": "Australia" }
];
this.dropdownSettings = {
singleSelection: false,
idField: 'item_id',
textField: 'item_text',
selectAllText: 'Select All',
unSelectAllText: 'UnSelect All',
itemsShowLimit: 3,
allowSearchFilter: true
};
this.setStatus();
}
setStatus() {
(this.selectedItems.length > 0) ? this.requiredField = true : this.requiredField = false;
}
onItemSelect(item: any) {
//Do something if required
this.setClass();
}
onSelectAll(items: any) {
//Do something if required
this.setClass();
}
setClass() {
this.setStatus();
if (this.selectedItems.length > 0) { return 'validField' }
else { return 'invalidField' }
}
submission() {
if (this.requiredField == false) {
/* Print a message that not all required fields were filled... */
}
/* Do form submission... */
}
}
relevant css:
.validField { border:2px solid green; display: block; }
.invalidField { border:2px solid red; display: block; }
::ng-deep .multiselect-dropdown .dropdown-btn {width: -webkit-fill-available !important}
::ng-deep .multiselect-dropdown .dropdown-btn:focus {outline: none !important}
complete working stackblitz available here

Styling ReactSelect with CSS-in-JS

Here is a link to another good stackoverflow post that I am basing my initial answer on. I am attempting to style my ReactSelect component so that it looks something like this:
It's not obvious from the screenshot above, but the select box has a much smaller height (29 pixels total) than the default ReactSelect, and that's my first objective with styling (reduce the height). Here is my code right now attempting to reduce the height, derived mainly from the stackoverflow post i've linked to:
const customStyles = {
control: base => ({
...base,
height: 22,
minHeight: 22
})
};
const customControlStyles = base => ({
...base,
height: 22,
minHeight: 22
});
const selectOptions = [
{ value: 'pct', label: 'FG Percentage' },
{ value: 'attFreq', label: 'FG Frequency' },
{ value: 'made', label: 'Total Makes' },
{ value: 'att', label: 'Total Attempts' }];
const shotdistSelect =
(<Select
// arrowRenderer={null}
maxMenuHeight={30}
placeholder={'Knickerbockers'}
isClearable={false}
isDisabled={this.props.loading}
backspaceRemovesValue={false}
isSearchable={true}
value={this.state.shotdistType}
onChange={this.handleShotdistChange}
options={selectOptions}
styles={{ control: customControlStyles }}
// styles={{ customStyles }}
/>);
And here's the result of the example above:
... not exactly what I was going for. Further, when I use customStyles rather than customControlStyles in the example above, the styling no longer works, and I am not sure what I've done wrong in creating customStyles that is causing it to not work. I figure I will need to do something similar to customStyles, as it seems I'll need to style more than just the control part of ReactSelect.
And 2nd, I'd like to remove both the vertical bar and down caret in the ReactSelect, similar to the initial screenshot.
Any help with this styling would be greatly appreciated!! I've been working on this for quite some time with no success yet. Thanks!
Option 1
Make sure you're using the most up-to-date version of react-select (v2.3.0). I was able to accomplish what you want by using a bit of CSS with the style keys offered by react-select.
Working example: https://codesandbox.io/s/7y6901y950
containers/Form/Form.js
import React, { Component } from "react";
import CustomSelect from "../../components/CustomSelect/CustomSelect";
const fgOptions = [
{ value: "pct", label: "FG Percentage" },
{ value: "attFreq", label: "FG Frequency" },
{ value: "made", label: "Total Makes" },
{ value: "att", label: "Total Attempts" }
];
const saveOptions = [
{ value: "pct", label: "Save Percentage" },
{ value: "sFreq", label: "Save Frequency" },
{ value: "tSaves", label: "Total Saves" }
];
const assistOptions = [
{ value: "pct", label: "Assist Percentage" },
{ value: "aFreq", label: "Assist Frequency" },
{ value: "tAssist", label: "Total Assists" }
];
export default class Form extends Component {
handleChange = (name, value) => {
this.setState({ [name]: value });
};
handleSubmit = e => {
e.preventDefault();
alert(JSON.stringify(this.state, null, 4));
};
render = () => (
<form onSubmit={this.handleSubmit} className="app-container">
<h1>React Select Styling</h1>
<CustomSelect
name="fg"
label="FG:"
placeholder="Field Goals"
handleChange={this.handleChange}
selectOptions={fgOptions}
/>
<CustomSelect
name="assists"
label="AS:"
placeholder="Assists"
handleChange={this.handleChange}
selectOptions={assistOptions}
/>
<CustomSelect
name="saves"
label="SV:"
placeholder="Saves"
handleChange={this.handleChange}
selectOptions={saveOptions}
/>
<button type="submit" className="submit">
Submit
</button>
</form>
);
}
components/CustomSelect/CustomSelect.js
import React from "react";
import PropTypes from "prop-types";
import Select from "react-select";
import { labelStyles, selectStyles } from "./styles/styles";
const CustomSelect = ({
handleChange,
label,
name,
placeholder,
selectOptions,
value
}) => (
<div className="select-container">
<label htmlFor={name} style={labelStyles}>
{label}
</label>
<Select
name={name}
placeholder={placeholder}
isClearable={false}
backspaceRemovesValue={false}
isSearchable={true}
value={value}
onChange={value => handleChange(name, value)}
options={selectOptions}
styles={selectStyles}
/>
</div>
);
CustomSelect.propTypes = {
handleChange: PropTypes.func.isRequired,
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
selectOptions: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
})
),
value: PropTypes.objectOf({
value: PropTypes.string,
label: PropTypes.string
})
};
export default CustomSelect;
components/CustomSelect/styles/styles.js (see documentation for style keys -- if you add longer labels, then you must adjust labelStyles width property; otherwise, the label to select ratio will vary)
export const selectStyles = {
option: (provided, state) => ({
...provided,
borderBottom: "1px dotted pink",
color: state.isSelected ? "blue" : "",
fontSize: 16,
backgroundColor: state.isSelected ? "#eee" : "",
textAlign: "left",
cursor: "pointer"
}),
container: base => ({
...base,
width: "100%"
}),
control: base => ({
...base,
height: 32,
minHeight: 32,
fontSize: 16,
borderRadius: 0,
width: "100%",
textAlign: "left",
cursor: "pointer"
}),
dropdownIndicator: base => ({
...base,
display: "none"
}),
indicatorSeparator: base => ({
...base,
display: "none"
}),
valueContainer: base => ({
...base,
padding: 0,
paddingLeft: 2
})
};
export const labelStyles = {
fontSize: 16,
paddingTop: 8,
marginRight: 5,
width: 50,
textAlign: "right"
};
styles.css
.app-container {
padding: 0px 20px;
text-align: center;
font-family: sans-serif;
}
.select-container {
display: flex;
margin: 0 auto;
width: 100%;
max-width: 500px;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-flex: 1;
margin-bottom: 10px;
}
.submit {
cursor: pointer;
background-color: #1e87f0;
color: #fff;
border: 1px solid transparent;
box-sizing: border-box;
padding: 0 30px;
vertical-align: middle;
font-size: 14px;
line-height: 38px;
text-transform: uppercase;
transition: 0.1s ease-in-out;
transition-property: color, background-color, border-color;
}
.submit:hover {
background-color: #0f7ae5;
color: #fff;
}
.submit:focus {
background-color: #1e87f0;
color: #fff;
outline: none;
}
Option 2
Or, you can do everything without using react-select, which I'd highly recommend as it excludes yet another dependency! As such, you have the option to style it as you wish (entirely through css, entirely through css-in-js or a combination).
Working example: https://codesandbox.io/s/w72k49nn27 (this example only uses css)

Resources