Skip to content

Commit 6df468f

Browse files
committed
feat(customOptionLabels): Added support for creating custom option label rendering. Updated index handling for better support of any value type but also making override of rendering simple and ignorant of the component internals
1 parent 7caac3a commit 6df468f

5 files changed

Lines changed: 70 additions & 15 deletions

File tree

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const onChange = (event, val, fullOption) => {
2828

2929
## 2. Easy Custom Styling
3030

31-
A few basic props for those that fear or despise CSS.
31+
A few basic props for those that fear or despise CSS. (I love CSS, so I'm happy to share.)
3232
```html
3333
<SimpleSelect options={options} width='200px'/> //Fixed Width (Fluid by default)
3434
<SimpleSelect options={options} fixedHeight/> //Fixed Height - assumes single line of text with ellipsis (Fluid by default)
@@ -54,6 +54,24 @@ div.mySelectDefault .fieldset .legend {
5454
}
5555
```
5656

57+
The component pieces: The outer wrapper (.mySelectDefault), The div made to look like a fieldset(.fieldset), the legend(.legend), the currently displayed option (.selectedDisplay), the option list(.mySelectDefaultOptions), the dropdown arrow, the cancel button.
58+
```html
59+
<div class="mySelectDefault">
60+
<div class="fieldset">
61+
<div class="legend"></div>
62+
<div class="mainSectionWrapper">
63+
<div class="selectedDisplay"></div>
64+
<div class="cancelContainer"></div>
65+
<div class="downArrowContainer"></div>
66+
</div>
67+
<div class="mySelectDefaultOptions"></div>
68+
</div>
69+
</div>
70+
```
71+
72+
73+
74+
5775
## 3. Mobile
5876
It works out of the box...
5977

src/SimpleSelect.js

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,22 @@ class SimpleSelect extends React.Component {
1717
this.openSelect = this.openSelect.bind(this);
1818
this.handleOutsideOptionsClick = this.handleOutsideOptionsClick.bind(this);
1919
this.renderOption = this.renderOption.bind(this);
20+
this.renderOptionLabel = this.renderOptionLabel.bind(this);
2021

2122
const optionValidation = this.checkOptionType(props.options);
23+
24+
25+
//add index to each option for ease of rendering and value retrieval (this is what allows us to let value be any data type)
26+
const initialOptions = props.options ? props.options : [];
27+
const optionsWithIndexProp = initialOptions.map((x, idx) => {
28+
x._idx = idx;
29+
return x;
30+
});
2231
const blankValue = { value: "", label: "" };
2332
const defaultValue =
2433
props.defaultValue &&
25-
props.options.find(x => x.value === props.defaultValue)
26-
? props.options.find(x => x.value === props.defaultValue)
34+
optionsWithIndexProp.find(x => x.value === props.defaultValue)
35+
? optionsWithIndexProp.find(x => x.value === props.defaultValue)
2736
: blankValue;
2837

2938
this.state = {
@@ -34,7 +43,7 @@ class SimpleSelect extends React.Component {
3443
currentOptionSelected: defaultValue,
3544
legendLabel: props.legendLabel ? props.legendLabel : "Select...",
3645
selectOpen: false,
37-
options: props.options ? props.options : [],
46+
options: optionsWithIndexProp,
3847
width: props.width ? props.width : "",
3948
};
4049
}
@@ -95,9 +104,14 @@ class SimpleSelect extends React.Component {
95104
}
96105
}
97106

107+
findOptionWrapper(el) {
108+
if (el.getAttribute("index")) return el;
109+
return this.findOptionWrapper(el.parentNode);
110+
}
111+
98112
optionSelected(e) {
99-
console.log(e.target);
100-
const optionIndex = parseInt(e.target.getAttribute("index"), 10);
113+
const optionWrapper = this.findOptionWrapper(e.target)
114+
const optionIndex = parseInt(optionWrapper.getAttribute("index"), 10);
101115
const optionByIndex = this.state.options[optionIndex];
102116
if (this.props.onChange) {
103117
this.props.onChange(e, optionByIndex.value, optionByIndex);
@@ -119,7 +133,6 @@ class SimpleSelect extends React.Component {
119133
}
120134

121135
cancelSelection(e) {
122-
console.log("cancel button");
123136
if (this.props.onChange) {
124137
this.props.onChange(e);
125138
}
@@ -131,24 +144,32 @@ class SimpleSelect extends React.Component {
131144
e.stopPropagation(); //since cancel and dropdown are part of same dom tree we don't want the open/close to fire as well.
132145
}
133146

134-
/* We could eliminate the need for the index if we just matched the first option that has the exact value... */
135-
renderOption(opt, idx) {
147+
renderOptionLabel(opt) {
148+
if (this.props.renderOptionLabel && opt != this.state.blankValue) {
149+
return this.props.renderOptionLabel(opt);
150+
} else {
151+
return opt.label;
152+
}
153+
}
154+
155+
renderOption(opt) {
136156
return (
137157
<div
138158
onClick={this.optionSelected}
139-
key={`_select_opts${idx}`}
140-
index={idx}
159+
key={`_select_opts${opt._idx}`}
160+
index={opt._idx}
161+
className={`select_option`}
141162
>
142-
{opt.label}
163+
{this.renderOptionLabel(opt)}
143164
</div>
144165
);
145166
}
146167

147168
render() {
148169
if (!this.state.validOptions)
149170
return <div>Invalid Options: {this.state.invalidReason}</div>;
150-
const opts = this.state.options.map((x, idx) => {
151-
return this.renderOption(x, idx);
171+
const opts = this.state.options.map(x => {
172+
return this.renderOption(x);
152173
});
153174

154175
const style = {
@@ -181,7 +202,7 @@ class SimpleSelect extends React.Component {
181202
<div className="legend">{this.state.legendLabel}</div>
182203
<div className="mainSectionWrapper" onClick={this.openSelect} ref={this.dropdownButton}>
183204
<div className="selectedDisplay">
184-
{this.state.currentOptionSelected.label}
205+
{this.renderOptionLabel(this.state.currentOptionSelected)}
185206
</div>
186207
{cancelSection}
187208
<div className="downArrowContainer">

src/flags.css

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/flags.png

78.7 KB
Loading

src/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
44
import SimpleSelect from "./SimpleSelect";
55

66
import "./styles.css";
7+
import "./flags.css"
78

89
function App() {
910
const specialVal = { test: "valueObject" };
@@ -19,12 +20,20 @@ function App() {
1920
{ label: "Something Else", value: "Something Else" },
2021
{ label: "New Option", value: specialVal }
2122
];
23+
const flagOptions = [
24+
{label: "United States", value: "US", flag: "flag-us"},
25+
{label: "United Kingdom", value: "UK", flag: "flag-gb"}
26+
];
2227
const lotsOfOpts = [...Array(1000).keys()].map(x => {
2328
return { value: x, label: "Label" + x };
2429
});
2530
const onChange = (e, value, fullOption) => {
2631
console.log("Component val and fullOpt", value, fullOption);
2732
};
33+
const flagOptionLabelRender = (opt) => {
34+
const flagClass = `flag ${opt.flag}`;
35+
return <React.Fragment><span className={flagClass}></span><span>{opt.label}</span></React.Fragment>;
36+
}
2837
return (
2938
<div className="App">
3039
<h1>Simple, Stylable, React Select Component</h1>
@@ -67,6 +76,9 @@ function App() {
6776
nonCancelable defaultValue={2} legendLabel="Select nonCancelable"
6877
width="200px"//Non-cancelable
6978
/>
79+
<SimpleSelect id="5" options={flagOptions} onChange={onChange} width="300px" legendLabel="Custom Option Label Render"
80+
renderOptionLabel={flagOptionLabelRender}
81+
/>
7082
</div>
7183
);
7284
}

0 commit comments

Comments
 (0)