Skip to content

Commit d0f20e2

Browse files
authored
feat: add Dark Mode option (#232)
- add a `darkMode` option, user can also change it at any time dynamically via `refreshOptions()`
1 parent 1f712f1 commit d0f20e2

File tree

12 files changed

+353
-17
lines changed

12 files changed

+353
-17
lines changed

packages/demo/src/app-routing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Example11 from './examples/example11';
1313
import Example12 from './examples/example12';
1414
import Example13 from './examples/example13';
1515
import Example14 from './examples/example14';
16+
import Example15 from './examples/example15';
1617
import GettingStarted from './getting-started';
1718
import I18n from './i18n/i18n';
1819
import Methods01 from './methods/methods01';
@@ -87,6 +88,7 @@ export const exampleRouting = [
8788
{ name: 'example12', view: '/src/examples/example12.html', viewModel: Example12, title: 'Checkbox/Radio Icons' },
8889
{ name: 'example13', view: '/src/examples/example13.html', viewModel: Example13, title: 'Dynamically Create Select' },
8990
{ name: 'example14', view: '/src/examples/example14.html', viewModel: Example14, title: 'The Divider' },
91+
{ name: 'example15', view: '/src/examples/example15.html', viewModel: Example15, title: 'Dark Mode' },
9092
],
9193
},
9294
{
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<div class="example15-container">
2+
<div class="row mb-2">
3+
<div class="col-md-12 title-desc">
4+
<h2 class="bd-title">
5+
Dark Mode
6+
<span class="float-end links">
7+
Code <span class="fa fa-link"></span>
8+
<span class="small">
9+
<a
10+
target="_blank"
11+
href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/packages/demo/src/examples/example15.html"
12+
>html</a
13+
>
14+
|
15+
<a target="_blank" href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/packages/demo/src/examples/example15.ts"
16+
>ts</a
17+
>
18+
</span>
19+
</span>
20+
</h2>
21+
<div class="demo-subtitle">
22+
Dark Mode requires <code>darkMode</code> option to be enabled, when that happens it will add <code>.ms-dark-mode</code> to the parent and drop elements.
23+
The dark theme is configured through CSS variables which you can also customize yourself.
24+
You can also toggle Dark Mode at any time dynamically via <code>refreshOptions()</code>
25+
</div>
26+
</div>
27+
</div>
28+
29+
<div>
30+
<div class="mb-3 row">
31+
<label class="col-sm-2">Single Select</label>
32+
33+
<div class="col-sm-10">
34+
<select id="single" class="select full-width ms-dark-mode" data-test="single">
35+
<option value="1">January</option>
36+
<option value="2">February</option>
37+
<option value="3">March</option>
38+
<option value="4">April</option>
39+
<option data-divider="true"></option>
40+
<option value="5">May</option>
41+
<option value="6">June</option>
42+
<option value="7">July</option>
43+
<option value="8">August</option>
44+
<option data-divider="true"></option>
45+
<option value="9">September</option>
46+
<option value="10">October</option>
47+
<option value="11">November</option>
48+
<option value="12">December</option>
49+
</select>
50+
</div>
51+
</div>
52+
53+
<div class="mb-3 row">
54+
<label class="col-sm-2">Single Radio</label>
55+
56+
<div class="col-sm-10">
57+
<select id="single" class="radio full-width ms-dark-mode" data-test="radio">
58+
<option value="1">January</option>
59+
<option value="2" selected>February</option>
60+
<option value="3">March</option>
61+
<option value="4">April</option>
62+
<option data-divider="true"></option>
63+
<option value="5">May</option>
64+
<option value="6">June</option>
65+
<option value="7">July</option>
66+
<option value="8">August</option>
67+
<option data-divider="true"></option>
68+
<option value="9">September</option>
69+
<option value="10">October</option>
70+
<option value="11">November</option>
71+
<option value="12">December</option>
72+
</select>
73+
</div>
74+
</div>
75+
76+
<div class="mb-3 row">
77+
<label class="col-sm-2">Multiple Select</label>
78+
79+
<div class="col-sm-10">
80+
<select id="multiple" class="select full-width" data-test="multiple" multiple>
81+
<option value="1">January</option>
82+
<option value="2">February</option>
83+
<option value="3">March</option>
84+
<option value="4">April</option>
85+
<option data-divider="true"></option>
86+
<option value="5">May</option>
87+
<option value="6">June</option>
88+
<option value="7">July</option>
89+
<option value="8">August</option>
90+
<option data-divider="true"></option>
91+
<option value="9">September</option>
92+
<option value="10">October</option>
93+
<option value="11">November</option>
94+
<option value="12">December</option>
95+
</select>
96+
</div>
97+
</div>
98+
99+
<div class="mb-3 row">
100+
<label class="col-sm-2">Group Select</label>
101+
102+
<div class="col-sm-10">
103+
<select id="group" class="select full-width" data-test="group" multiple>
104+
<option data-divider="true"></option>
105+
<optgroup label="Group 1">
106+
<option value="1">January</option>
107+
<option value="2">February</option>
108+
<option value="3">March</option>
109+
<option data-divider="true"></option>
110+
<option value="4">April</option>
111+
<option value="5">May</option>
112+
<option value="6">June</option>
113+
</optgroup>
114+
<option data-divider="true"></option>
115+
<optgroup label="Group 2">
116+
<option value="7">July</option>
117+
<option value="8">August</option>
118+
<option value="9">September</option>
119+
<option data-divider="true"></option>
120+
<option value="10">October</option>
121+
<option value="11">November</option>
122+
<option value="12">December</option>
123+
</optgroup>
124+
</select>
125+
</div>
126+
</div>
127+
128+
<div class="mb-3 row">
129+
<label class="col-sm-2">Data Select 1</label>
130+
131+
<div class="col-sm-10">
132+
<select id="data-select1" class="data-select full-width" data-test="data1" multiple></select>
133+
</div>
134+
</div>
135+
</div>
136+
</div>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.panel-wm-content.dark-mode {
2+
background-color: #33393e;
3+
h2.bd-title,
4+
label {
5+
color: #dddddd;
6+
}
7+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { type MultipleSelectInstance, multipleSelect } from 'multiple-select-vanilla';
2+
3+
import './example15.scss';
4+
5+
export default class Example {
6+
ms1: MultipleSelectInstance[] = [];
7+
ms2?: MultipleSelectInstance;
8+
ms3?: MultipleSelectInstance;
9+
10+
mount() {
11+
document.querySelector('.panel-wm-content')?.classList.add('dark-mode');
12+
13+
this.ms1 = multipleSelect('.select', { darkMode: true }) as MultipleSelectInstance[];
14+
this.ms2 = multipleSelect('.radio', { darkMode: true, singleRadio: true }) as MultipleSelectInstance;
15+
this.ms3 = multipleSelect('.data-select', {
16+
darkMode: true,
17+
dataTest: 'select1',
18+
showOkButton: true,
19+
data: [
20+
{
21+
value: 1,
22+
text: 'Option 1',
23+
},
24+
{
25+
value: 2,
26+
text: 'Option 2',
27+
},
28+
{
29+
value: 3,
30+
text: 'Option 3',
31+
},
32+
{
33+
divider: true,
34+
},
35+
{
36+
value: 4,
37+
text: 'Option 4',
38+
},
39+
{
40+
value: 5,
41+
text: 'Option 5',
42+
},
43+
{
44+
value: 6,
45+
text: 'Option 6',
46+
},
47+
],
48+
}) as MultipleSelectInstance;
49+
}
50+
51+
unmount() {
52+
// destroy ms instance(s) to avoid DOM leaks
53+
this.ms1.forEach(m => m.destroy());
54+
this.ms2?.destroy();
55+
this.ms3?.destroy();
56+
this.ms1 = [];
57+
document.querySelector('.panel-wm-content')?.classList.remove('dark-mode');
58+
}
59+
}

packages/demo/src/methods/methods02.html

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,33 @@ <h2 class="bd-title">
2727
<div class="mb-3 row">
2828
<label class="col-sm-2"> Methods </label>
2929

30-
<div class="col-sm-10">
30+
<div class="col-sm-5">
3131
<button id="refreshOptions" class="btn btn-secondary">refreshOptions</button>
32+
<button id="setDarkMode" class="btn btn-secondary">Toggle Dark Mode</button>
3233
</div>
3334
</div>
3435

3536
<div class="mb-3 row">
3637
<label class="col-sm-2"> Basic Select </label>
3738

3839
<div class="col-sm-10">
39-
<select multiple="multiple" class="full-width">
40+
<select multiple="multiple" class="full-width select1" data-test="select1">
4041
<option value="text1">text1</option>
4142
<option value="text2">text2</option>
4243
<option value="text3">text3</option>
4344
</select>
4445
</div>
4546
</div>
47+
48+
<div class="mb-3 row">
49+
<label class="col-sm-2"> Multiple Select </label>
50+
51+
<div class="col-sm-10">
52+
<select multiple="multiple" class="full-width select2" data-test="select2">
53+
<option value="task1">Task 1</option>
54+
<option value="task2">Task 2</option>
55+
<option value="task3">Task 3</option>
56+
</select>
57+
</div>
58+
</div>
4659
</div>

packages/demo/src/methods/methods02.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,33 @@ import { type MultipleSelectInstance, multipleSelect } from 'multiple-select-van
22

33
export default class Example {
44
ms1?: MultipleSelectInstance;
5+
ms2?: MultipleSelectInstance;
6+
darkMode2 = false;
57

68
mount() {
7-
this.ms1 = multipleSelect('select', {
8-
filter: true,
9-
}) as MultipleSelectInstance;
9+
this.ms1 = multipleSelect('.select1', { filter: true }) as MultipleSelectInstance;
10+
this.ms2 = multipleSelect('.select2', { darkMode: this.darkMode2, showOkButton: true }) as MultipleSelectInstance;
1011

11-
document.querySelector('#refreshOptions')!.addEventListener('click', () => {
12-
this.ms1?.refreshOptions({
13-
filter: false,
14-
});
15-
});
12+
document.querySelector('#refreshOptions')?.addEventListener('click', () => this.refreshOption1());
13+
document.querySelector('#setDarkMode')?.addEventListener('click', () => this.toggleDarkMode2());
14+
}
15+
16+
refreshOption1() {
17+
this.ms1?.refreshOptions({ filter: false });
18+
}
19+
20+
toggleDarkMode2() {
21+
this.darkMode2 = !this.darkMode2;
22+
this.ms2?.refreshOptions({ darkMode: this.darkMode2 });
1623
}
1724

1825
unmount() {
1926
// destroy ms instance(s) to avoid DOM leaks
2027
this.ms1?.destroy();
28+
this.ms2?.destroy();
2129
this.ms1 = undefined;
30+
this.ms2 = undefined;
31+
document.querySelector('#refreshOptions')?.removeEventListener('click', () => this.refreshOption1());
32+
document.querySelector('#setDarkMode')?.removeEventListener('click', () => this.toggleDarkMode2());
2233
}
2334
}

packages/multiple-select-vanilla/src/MultipleSelectInstance.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ export class MultipleSelectInstance {
183183
dataset: { test: 'sel' },
184184
});
185185

186+
if (this.options.darkMode) {
187+
this.parentElm.classList.add('ms-dark-mode');
188+
}
189+
186190
// add tooltip title only when provided
187191
const parentTitle = this.elm.getAttribute('title') || '';
188192
if (parentTitle) {
@@ -205,6 +209,10 @@ export class MultipleSelectInstance {
205209
// default position is bottom
206210
this.dropElm = createDomElement('div', { className: `ms-drop ${this.options.position}`, ariaExpanded: 'false' }, this.parentElm);
207211

212+
if (this.options.darkMode) {
213+
this.dropElm.classList.add('ms-dark-mode');
214+
}
215+
208216
// add data-name attribute when name option is defined
209217
if (name) {
210218
this.dropElm.dataset.name = name;
@@ -635,7 +643,6 @@ export class MultipleSelectInstance {
635643
const liBlock: HtmlStruct = {
636644
tagName: 'li',
637645
props: {
638-
className: liClasses,
639646
role: 'option',
640647
title,
641648
ariaSelected: String(!!dataRow.selected),
@@ -644,6 +651,10 @@ export class MultipleSelectInstance {
644651
children: [{ tagName: 'label', props: { className: labelClasses }, children: [inputBlock, spanLabelBlock] }],
645652
};
646653

654+
if (liClasses) {
655+
liBlock.props.className = liClasses;
656+
}
657+
647658
const customStyleRules = this.options.cssStyler(dataRow);
648659
const customStylerStr = String(this.options.styler(dataRow) || ''); // deprecated
649660
if (customStylerStr) {

packages/multiple-select-vanilla/src/interfaces/multipleSelectOption.interface.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ export interface MultipleSelectOption extends MultipleSelectLocale {
4949
/** Use optional string to override selected count text "# of % selected" instead of `formatCountSelected()`, the latter should be preferred */
5050
countSelectedText?: string;
5151

52+
/** Dark Mode will add `.ms-dark-mode` CSS class to drop and parent elements */
53+
darkMode?: boolean;
54+
5255
/** provide custom data */
5356
data?: { [value: string]: number | string | boolean } | Array<number | string | boolean | OptionRowData | OptionRowDivider | OptGroupRowData>;
5457

packages/multiple-select-vanilla/src/styles/_variables.scss

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ $ms-drop-list-item-level1-padding-left: 28px !default;
4343
$ms-drop-list-item-padding: 0.25rem 8px !default;
4444
$ms-drop-list-item-disabled-filter: Alpha(Opacity = 35) !default;
4545
$ms-drop-list-item-disabled-opacity: 0.35 !default;
46+
$ms-drop-scrollbar-color: #c1c1c1 #f1f1f1 !default;
4647
$ms-drop-zindex: 1050 !default;
4748
$ms-input-focus-outline: none !default;
4849
$ms-infinite-empty-option-height: 20px !default;
@@ -81,5 +82,28 @@ $ms-select-all-label-padding: 4px !default;
8182
$ms-select-all-label-span-padding: 0 0 0 20px !default;
8283
$ms-select-all-line-height: 18px !default;
8384
$ms-select-all-padding: 4px !default;
85+
$ms-select-all-text-font-weight: normal !default;
8486
$ms-select-all-text-color: darken($primary-color, 5%) !default;
8587
$ms-select-all-text-hover-color: transparent !default;
88+
89+
.ms-dark-mode {
90+
--ms-checkbox-color: #{lighten($primary-color, 5%)};
91+
--ms-choice-border: 1px solid #757575;
92+
--ms-choice-bgcolor: #262b2f;
93+
--ms-choice-color: #d4d4d4;
94+
--ms-drop-background: #2a2f34;
95+
--ms-drop-border: 1px solid #585858;
96+
--ms-drop-color: #cccccc;
97+
--ms-drop-hide-radio-hover-bgcolor: #{darken($primary-color, 5%)};
98+
--ms-drop-option-divider-border-top: 1px solid #696969;
99+
--ms-drop-scrollbar-color: #828282 #424242;
100+
--ms-option-highlight-bg-color: #{darken($primary-color, 10%)};
101+
--ms-ok-button-bg-color: #262b2f;
102+
--ms-ok-button-bg-hover-color: #24282c;
103+
--ms-ok-button-border-color: #4a4a4a;
104+
--ms-ok-button-text-color: #{lighten($primary-color, 5%)};
105+
--ms-ok-button-text-hover-color: #{lighten($primary-color, 5%)};
106+
--ms-select-all-border-bottom: 1px solid #5d5d5d;
107+
--ms-select-all-text-color: #d4d4d4;
108+
--ms-select-all-text-font-weight: bold;
109+
}

0 commit comments

Comments
 (0)