<div class="search-field search-field--big">
<div class="search-field__field">
<input type="search" class="search-field__input" />
<button class="search-field__submit" type="submit"></button>
</div>
</div>
<div class="search-field {{ getmodifiers modifiers "search-field" }}">
<div class="search-field__field">
<input type="search" class="search-field__input" {{{getattributes attributes}}} />
<button class="search-field__submit" type="submit"></button>
</div>
</div>
{
"theme": "consumer",
"modifiers": [
"big"
]
}
import React from 'react';
const Geolocation = (props) => {
const requestIpLocation = async (cb) => {
try {
const res = await fetch('https://ipinfo.io/geo');
const data = await res.json();
cb(data.loc.split(','));
} catch (e) {
console.warn(e);
}
};
const requestGeolocation = async (e) => {
e.preventDefault();
if (!navigator.geolocation) {
return requestIpLocation();
}
const cords = await new Promise((resolve) => {
navigator.geolocation.getCurrentPosition((position) => {
resolve([position.coords.latitude, position.coords.longitude]);
}, () => requestIpLocation(resolve));
});
props.onGeolocation(cords);
};
return (
<button
className="search-field__geolocation"
onClick={requestGeolocation}
/>
);
};
export default Geolocation;
import React from 'react';
import Downshift from 'downshift';
import PropTypes from 'prop-types';
import Radio from '../form/radio/Radio';
const SearchField = (props) => {
const {
preSelectedFirstItem,
onInputChange,
onSubmit,
onChange,
itemStyle,
items,
itemComponent,
className,
inputPlaceholder,
categories,
suggestions,
inputValue: initialInputValue,
...downshiftProps
} = props;
const [selected, setSelected] = React.useState(null);
const hasCategories =
categories &&
Array.isArray(categories.items) &&
categories.items.length > 0;
const hasSuggestions =
suggestions &&
Array.isArray(suggestions.items) &&
suggestions.items.length > 0;
function stateReducer(state, changes) {
// this prevents the menu from being closed when the user
// selects an item with a keyboard or mouse
switch (changes.type) {
case Downshift.stateChangeTypes.mouseUp:
// Preserve input value on mouseup (when clicking outside)
return {
...changes,
inputValue: state.inputValue
};
case Downshift.stateChangeTypes.blurInput:
return {
...changes,
...(items[state.highlightedIndex] && {
inputValue: items[state.highlightedIndex].value
}),
...(typeof onSubmit !== 'undefined' && {
inputValue: state.inputValue
}),
...(onSubmit === 'undefined' && {
highlightedIndex: state.highlightedIndex
})
};
default:
return changes;
}
}
return (
<form onSubmit={(e) => onSubmit && onSubmit(e)}>
<Downshift
itemToString={(item) => (item ? item.value : ``)}
defaultHighlightedIndex={preSelectedFirstItem ? 0 : null}
initialInputValue={initialInputValue}
stateReducer={stateReducer}
{...downshiftProps}
>
{({
getInputProps,
getItemProps,
getMenuProps,
isOpen,
inputValue,
highlightedIndex,
reset,
clearSelection
}) => (
<div className={`search-field ${className}`}>
{/* Search field */}
<div className="search-field__field">
<input
name="q"
className="search-field__input"
placeholder={inputPlaceholder}
type="search"
{...getInputProps({
onChange: (e) => onInputChange(e),
// Ensure value is always a string to prevent controlled/uncontrolled warning
value: inputValue || ''
})}
/>
{/* Clear button */}
{inputValue && inputValue.length > 0 && (
<button
type="button"
className="search-field__clear"
aria-label="Rensa sökfältet"
onClick={(e) => {
onInputChange({
target: { value: '' }
});
clearSelection();
reset();
e.preventDefault();
}}
/>
)}
<button
className="search-field__submit"
type="submit"
onClick={(e) => {
if (
typeof items[highlightedIndex] !==
'undefined'
) {
onChange(items[highlightedIndex]);
e.preventDefault();
} else if (
typeof onSubmit === 'undefined'
) {
e.preventDefault();
}
}}
aria-label={inputPlaceholder}
/>
{props.children}
</div>
{/* Autocomplete */}
{isOpen && inputValue.length > 0 && items.length > 0 ? (
<React.Fragment>
<ul
className={`search-field__autocomplete ${
isOpen &&
`search-field__autocomplete--is-open`
}`}
{...getMenuProps()}
>
{items.map((item, index) => (
<li
className={`search-field__item ${
highlightedIndex === index &&
`search-field__item--selected`
} ${itemStyle}`}
{...getItemProps({
key: index,
index,
item
})}
>
{itemComponent(item)}
</li>
))}
</ul>
</React.Fragment>
) : null}
{(hasCategories || hasSuggestions) && (
<div className="row">
{/* Categories */}
{hasCategories && (
<div className="column-block col col--span-12 col--span-s-8">
<div className="search-field__categories">
<Radio
id="search-category"
name="category"
label={categories.label}
selectedValue={selected || categories.items[0].key}
onChange={(val) =>
setSelected(val)
}
modifier="radio-pill"
options={categories.items}
/>
</div>
</div>
)}
<div className="column-block col col--span-12 col--span-s-4">
{/* Popular links */}
{hasSuggestions && (
<div className="search-field__suggestions">
{suggestions.label && (
<div className="search-field__suggestions-label">
{suggestions.label}
</div>
)}
<ul>
{suggestions &&
suggestions.items &&
suggestions.items.map(
(
popularLink,
index
) => {
return (
<li key={index}>
<a
href={
popularLink.url
}
>
{
popularLink.label
}
</a>
</li>
);
}
)}
</ul>
</div>
)}
</div>
</div>
)}
</div>
)}
</Downshift>
</form>
);
};
SearchField.propTypes = {
onInputChange: PropTypes.func,
onSubmit: PropTypes.func,
itemStyle: PropTypes.string,
items: PropTypes.array.isRequired,
itemComponent: PropTypes.func,
className: PropTypes.string,
inputPlaceholder: PropTypes.string,
preSelectedFirstItem: PropTypes.bool,
seeMoreLink: PropTypes.string,
inputValue: PropTypes.string,
categories: PropTypes.shape({
label: PropTypes.string,
items: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.string,
label: PropTypes.string
})
)
}),
suggestions: PropTypes.shape({
label: PropTypes.string,
items: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
url: PropTypes.string
})
)
})
};
SearchField.defaultProps = {
onInputChange: () => {},
itemStyle: '',
className: '',
preSelectedFirstItem: true,
seeMoreLink: ''
};
export default SearchField;
import SearchField from './SearchField';
export { default as Geolocation } from './Geolocation';
export default SearchField;
$height-big: size(8);
$height-small: size(6);
.search-field {
width: 100%;
position: relative;
margin-bottom: size(4);
&--big {
@include breakpoint-classes {
@include breakpoint($s) {
.search-field__input,
.search-field__submit,
.search-field__geolocation {
height: $height-big;
}
.search-field__submit,
.search-field__geolocation {
width: $height-big;
background-size: calc(100% - #{size(3)});
}
}
}
}
}
.search-field__field {
display: flex;
}
.search-field__input {
border: $color-gray-2 1px solid;
padding: size(2) size(3);
line-height: size(3);
height: $height-small;
display: flex;
flex: 1;
font-size: size(2.5);
border-radius: size(0.5) 0 0 size(0.5);
border-right: 0;
background: $color-white;
width: 100%;
@include placeholder {
color: $color-gray-3;
font-size: size(2.5);
opacity: 1;
}
&::-webkit-input-placeholder {
transform: translateY(0);
-webkit-transform: translateY(2px);
}
&::-webkit-search-decoration,
&::-webkit-search-cancel-button,
&::-webkit-search-results-button,
&::-webkit-search-results-decoration {
display: none;
}
}
// buttons
.search-field__geolocation,
.search-field__submit {
width: $height-small;
height: $height-small;
min-width: auto;
}
.search-field__geolocation {
background: url(./img/icon-crosshair.svg) $color-gray-1 no-repeat center;
border-radius: size(0.5);
background-size: calc(100% - #{size(2)});
margin-left: size(1);
@include breakpoint($m) {
margin-left: size(2);
}
}
.search-field__clear {
position: absolute;
background: url(./img/icon-close.svg) no-repeat center;
right: 48px;
top: 24px;
transform: translateY(-50%);
width: size(5);
height: size(5);
z-index: 1;
@include breakpoint($s) {
right: 64px;
top: 32px;
}
}
.search-field__submit {
background: url(./img/icon-search.svg) no-repeat center;
border-radius: 0 size(0.5) size(0.5) 0;
background-size: calc(100% - #{size(2)});
background-color: $color-green-dark;
&:hover {
cursor: pointer;
}
@include color-theme {
background-color: $arg-theme-color;
}
}
.search-field__autocomplete {
position: absolute;
width: 100%;
border-radius: size(0.5);
border: $color-gray-2 1px solid;
visibility: hidden;
list-style: none;
padding: size(2);
margin: 0;
text-align: left;
z-index: 1;
background-color: $color-white;
&--is-open {
visibility: visible;
}
}
.search-field__item {
display: flex;
align-items: center;
margin-top: size(1);
&:first-child {
margin-top: 0;
}
&--selected {
a {
background-color: $color-gray-1;
}
}
&--link-holder {
justify-content: center;
padding: size(2) size(3);
}
&--link {
color: $color-green;
display: inline-block;
vertical-align: top;
position: relative;
padding-right: size(2);
}
&--icon {
position: absolute;
top: 50%;
right: 0;
width: 16px;
height: 16px;
vertical-align: middle;
fill: currentColor;
transform: translateY(-50%);
}
}
.search-widget__spinner {
position: absolute;
top: 32px;
transform: translateY(-50%);
right: 70px;
.search-field--with-clear-button & {
top: 24px;
right: 80px;
@include breakpoint($s) {
top: 32px;
right: 100px;
}
}
}
.search-field {
.header__search & {
display: none;
}
&.is-open {
.header__search & {
display: block;
}
}
}
.search-field__categories,
.search-field__suggestions {
margin-top: size(4);
}
.search-field__categories {
.form-item__legend {
margin-bottom: size(3);
}
}
.search-field__suggestions {
ul {
list-style: none;
padding: 0;
}
li {
padding-top: size(1);
&:first-child {
padding-top: 0;
}
}
a {
display: block;
color: $color-black;
font-size: size(2);
line-height: size(3);
font-weight: 700;
padding: 0 size(0.5);
&:hover {
background-color: $color-gray-1;
text-decoration: none;
}
}
&-label {
margin-bottom: size(2);
font-size: size(2);
color: $color-gray-5;
}
}
No notes defined.