Accessibility fixes

- Aria landmarks
 - Title attributes to all icon only buttons
 - <Multibutton/> now internally a radio group
 - Replaced 1 'skip navigation link' with UI group links
 - Added map specific shortcuts to the shortcut menu
 - Hidden layer list actions from tab index
This commit is contained in:
orangemug 2020-05-18 19:37:49 +01:00
parent e3e6647e03
commit b28407a4a0
25 changed files with 260 additions and 74 deletions

View file

@ -12,10 +12,13 @@ class Button extends React.Component {
children: PropTypes.node, children: PropTypes.node,
disabled: PropTypes.bool, disabled: PropTypes.bool,
type: PropTypes.string, type: PropTypes.string,
id: PropTypes.string,
} }
render() { render() {
return <button return <button
id={this.props.id}
title={this.props.title}
type={this.props.type} type={this.props.type}
onClick={this.props.onClick} onClick={this.props.onClick}
disabled={this.props.disabled} disabled={this.props.disabled}

View file

@ -131,6 +131,16 @@ export default class Toolbar extends React.Component {
this.props.onSetMapState(val); this.props.onSetMapState(val);
} }
onSkip = (target) => {
if (target === "map") {
document.querySelector(".mapboxgl-canvas").focus();
}
else {
const el = document.querySelector("#skip-target-"+target);
el.focus();
}
}
render() { render() {
const views = [ const views = [
{ {
@ -144,22 +154,22 @@ export default class Toolbar extends React.Component {
}, },
{ {
id: "filter-deuteranopia", id: "filter-deuteranopia",
title: "Map (deuteranopia)", title: "Deuteranopia color filter",
disabled: !colorAccessibilityFiltersEnabled, disabled: !colorAccessibilityFiltersEnabled,
}, },
{ {
id: "filter-protanopia", id: "filter-protanopia",
title: "Map (protanopia)", title: "Protanopia color filter",
disabled: !colorAccessibilityFiltersEnabled, disabled: !colorAccessibilityFiltersEnabled,
}, },
{ {
id: "filter-tritanopia", id: "filter-tritanopia",
title: "Map (tritanopia)", title: "Tritanopia color filter",
disabled: !colorAccessibilityFiltersEnabled, disabled: !colorAccessibilityFiltersEnabled,
}, },
{ {
id: "filter-achromatopsia", id: "filter-achromatopsia",
title: "Map (achromatopsia)", title: "Achromatopsia color filter",
disabled: !colorAccessibilityFiltersEnabled, disabled: !colorAccessibilityFiltersEnabled,
}, },
]; ];
@ -173,23 +183,37 @@ export default class Toolbar extends React.Component {
<div <div
className="maputnik-toolbar-logo-container" className="maputnik-toolbar-logo-container"
> >
<a className="maputnik-toolbar-skip" href="#skip-menu"> {/* Keyboard accessible quick links */}
Skip navigation <button
</a> className="maputnik-toolbar-skip"
<a onClick={e => this.onSkip("layer-list")}
href="https://github.com/maputnik/editor" >
rel="noopener noreferrer" Layers list
target="_blank" </button>
<button
className="maputnik-toolbar-skip"
onClick={e => this.onSkip("layer-editor")}
>
Layer editor
</button>
<button
className="maputnik-toolbar-skip"
onClick={e => this.onSkip("map")}
>
Map view
</button>
<div
className="maputnik-toolbar-logo" className="maputnik-toolbar-logo"
tabIndex="-1"
> >
<span dangerouslySetInnerHTML={{__html: logoImage}} /> <span dangerouslySetInnerHTML={{__html: logoImage}} />
<h1> <h1>
<span className="maputnik-toolbar-name">{pkgJson.name}</span> <span className="maputnik-toolbar-name">{pkgJson.name}</span>
<span className="maputnik-toolbar-version">v{pkgJson.version}</span> <span className="maputnik-toolbar-version">v{pkgJson.version}</span>
</h1> </h1>
</a> </div>
</div> </div>
<div className="maputnik-toolbar__actions"> <div className="maputnik-toolbar__actions" role="navigation" aria-label="Toolbar">
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}> <ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
<MdOpenInBrowser /> <MdOpenInBrowser />
<IconText>Open</IconText> <IconText>Open</IconText>
@ -209,20 +233,21 @@ export default class Toolbar extends React.Component {
<ToolbarSelect wdKey="nav:inspect"> <ToolbarSelect wdKey="nav:inspect">
<MdFindInPage /> <MdFindInPage />
<IconText>View </IconText> <label>View
<select <select
className="maputnik-select" className="maputnik-select"
onChange={(e) => this.handleSelection(e.target.value)} onChange={(e) => this.handleSelection(e.target.value)}
value={currentView.id} value={currentView.id}
> >
{views.map((item) => { {views.map((item) => {
return ( return (
<option key={item.id} value={item.id} disabled={item.disabled}> <option key={item.id} value={item.id} disabled={item.disabled}>
{item.title} {item.title}
</option> </option>
); );
})} })}
</select> </select>
</label>
</ToolbarSelect> </ToolbarSelect>
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}> <ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>

View file

@ -14,7 +14,7 @@ export default class DeleteStopButton extends React.Component {
return <Button return <Button
className="maputnik-delete-stop" className="maputnik-delete-stop"
onClick={this.props.onClick} onClick={this.props.onClick}
title={"Remove zoom level stop."} title={"Remove zoom level from stop"}
> >
<MdDelete /> <MdDelete />
</Button> </Button>

View file

@ -64,6 +64,7 @@ export default class ExpressionProperty extends React.Component {
onClick={this.props.onUndo} onClick={this.props.onUndo}
disabled={undoDisabled} disabled={undoDisabled}
className="maputnik-delete-stop" className="maputnik-delete-stop"
title="Revert from expression"
> >
<MdUndo /> <MdUndo />
</Button> </Button>
@ -72,6 +73,7 @@ export default class ExpressionProperty extends React.Component {
key="delete_action" key="delete_action"
onClick={this.props.onDelete} onClick={this.props.onDelete}
className="maputnik-delete-stop" className="maputnik-delete-stop"
title="Delete expression"
> >
<MdDelete /> <MdDelete />
</Button> </Button>

View file

@ -40,6 +40,7 @@ export default class FunctionButtons extends React.Component {
<Button <Button
className="maputnik-make-zoom-function" className="maputnik-make-zoom-function"
onClick={this.props.onExpressionClick} onClick={this.props.onExpressionClick}
title="Convert to expression"
> >
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24"> <svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiFunctionVariant} /> <path fill="currentColor" d={mdiFunctionVariant} />
@ -50,7 +51,7 @@ export default class FunctionButtons extends React.Component {
makeZoomButton = <Button makeZoomButton = <Button
className="maputnik-make-zoom-function" className="maputnik-make-zoom-function"
onClick={this.props.onZoomClick} onClick={this.props.onZoomClick}
title={"Turn property into a zoom function to enable a map feature to change with map's zoom level."} title="Convert property into a zoom function"
> >
<MdFunctions /> <MdFunctions />
</Button> </Button>
@ -59,7 +60,7 @@ export default class FunctionButtons extends React.Component {
makeDataButton = <Button makeDataButton = <Button
className="maputnik-make-data-function" className="maputnik-make-data-function"
onClick={this.props.onDataClick} onClick={this.props.onDataClick}
title={"Turn property into a data function to enable a map feature to change according to data properties and the map's zoom level."} title="Convert property to data function"
> >
<MdInsertChart /> <MdInsertChart />
</Button> </Button>

View file

@ -194,6 +194,7 @@ export default class CombiningFilterEditor extends React.Component {
</p> </p>
<Button <Button
onClick={this.makeExpression} onClick={this.makeExpression}
title="Convert to expression"
> >
<svg style={{marginRight: "0.2em", width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24"> <svg style={{marginRight: "0.2em", width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiFunctionVariant} /> <path fill="currentColor" d={mdiFunctionVariant} />
@ -211,6 +212,7 @@ export default class CombiningFilterEditor extends React.Component {
<div> <div>
<Button <Button
onClick={this.makeExpression} onClick={this.makeExpression}
title="Convert to expression"
className="maputnik-make-zoom-function" className="maputnik-make-zoom-function"
> >
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24"> <svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">

View file

@ -15,6 +15,7 @@ class FilterEditorBlock extends React.Component {
<Button <Button
className="maputnik-delete-filter" className="maputnik-delete-filter"
onClick={this.props.onDelete} onClick={this.props.onDelete}
title="Delete filter block"
> >
<MdDelete /> <MdDelete />
</Button> </Button>

View file

@ -124,10 +124,11 @@ class DeleteValueButton extends React.Component {
return <Button return <Button
className="maputnik-delete-stop" className="maputnik-delete-stop"
onClick={this.props.onClick} onClick={this.props.onClick}
title="Remove array item"
> >
<DocLabel <DocLabel
label={<MdDelete />} label={<MdDelete />}
doc={"Remove array entry."} doc={"Remove array item."}
/> />
</Button> </Button>
} }

View file

@ -19,15 +19,17 @@ class EnumInput extends React.Component {
value: PropTypes.string, value: PropTypes.string,
style: PropTypes.object, style: PropTypes.object,
default: PropTypes.string, default: PropTypes.string,
name: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
options: PropTypes.array, options: PropTypes.array,
} }
render() { render() {
const {options, value, onChange} = this.props; const {options, value, onChange, name} = this.props;
if(options.length <= 3 && optionsLabelLength(options) <= 20) { if(options.length <= 3 && optionsLabelLength(options) <= 20) {
return <MultiButtonInput return <MultiButtonInput
name={name}
options={options} options={options}
value={value || this.props.default} value={value || this.props.default}
onChange={onChange} onChange={onChange}

View file

@ -5,6 +5,7 @@ import Button from '../Button'
class MultiButtonInput extends React.Component { class MultiButtonInput extends React.Component {
static propTypes = { static propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
options: PropTypes.array.isRequired, options: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
@ -17,19 +18,24 @@ class MultiButtonInput extends React.Component {
} }
const selectedValue = this.props.value || options[0][0] const selectedValue = this.props.value || options[0][0]
const buttons = options.map(([val, label])=> { const radios = options.map(([val, label])=> {
return <Button return <label
key={val} key={val}
onClick={e => this.props.onChange(val)} className={classnames("maputnik-radio-as-button", {"maputnik-button-selected": val === selectedValue})}
className={classnames({"maputnik-button-selected": val === selectedValue})}
> >
<input type="radio"
name={this.props.name}
onChange={e => this.props.onChange(val)}
value={val}
checked={val === selectedValue}
/>
{label} {label}
</Button> </label>
}) })
return <div className="maputnik-multibutton"> return <fieldset className="maputnik-multibutton">
{buttons} {radios}
</div> </fieldset>
} }
} }

View file

@ -289,7 +289,9 @@ export default class LayerEditor extends React.Component {
} }
return <div className="maputnik-layer-editor" return <div className="maputnik-layer-editor"
> role="main"
aria-label="Layer editor"
>
<header> <header>
<div className="layer-header"> <div className="layer-header">
<h2 className="layer-header__title"> <h2 className="layer-header__title">
@ -301,7 +303,7 @@ export default class LayerEditor extends React.Component {
onSelection={handleSelection} onSelection={handleSelection}
closeOnSelection={false} closeOnSelection={false}
> >
<Button className='more-menu__button'> <Button id="skip-target-layer-editor" className='more-menu__button' title="Layer options">
<MdMoreVert className="more-menu__button__svg" /> <MdMoreVert className="more-menu__button__svg" />
</Button> </Button>
<Menu> <Menu>

View file

@ -91,9 +91,18 @@ class LayerListContainer extends React.Component {
groupedLayers() { groupedLayers() {
const groups = [] const groups = []
const layerIdCount = new Map();
for (let i = 0; i < this.props.layers.length; i++) { for (let i = 0; i < this.props.layers.length; i++) {
const origLayer = this.props.layers[i];
const previousLayer = this.props.layers[i-1] const previousLayer = this.props.layers[i-1]
const layer = this.props.layers[i] layerIdCount.set(origLayer.id,
layerIdCount.has(origLayer.id) ? layerIdCount.get(origLayer.id) + 1 : 0
);
const layer = {
...origLayer,
key: `layers-list-${origLayer.id}-${layerIdCount.get(origLayer.id)}`,
}
if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) { if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) {
const lastGroup = groups[groups.length - 1] const lastGroup = groups[groups.length - 1]
lastGroup.push(layer) lastGroup.push(layer)
@ -191,14 +200,13 @@ class LayerListContainer extends React.Component {
const listItems = [] const listItems = []
let idx = 0 let idx = 0
const layerIdCount = new Map();
const layersByGroup = this.groupedLayers(); const layersByGroup = this.groupedLayers();
layersByGroup.forEach(layers => { layersByGroup.forEach(layers => {
const groupPrefix = layerPrefix(layers[0].id) const groupPrefix = layerPrefix(layers[0].id)
if(layers.length > 1) { if(layers.length > 1) {
const grp = <LayerListGroup const grp = <LayerListGroup
data-wd-key={[groupPrefix, idx].join('-')} data-wd-key={[groupPrefix, idx].join('-')}
aria-controls={layers.map(l => l.key).join(" ")}
key={`group-${groupPrefix}-${idx}`} key={`group-${groupPrefix}-${idx}`}
title={groupPrefix} title={groupPrefix}
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex} isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
@ -223,10 +231,6 @@ class LayerListContainer extends React.Component {
additionalProps.ref = this.selectedItemRef; additionalProps.ref = this.selectedItemRef;
} }
layerIdCount.set(layer.id,
layerIdCount.has(layer.id) ? layerIdCount.get(layer.id) + 1 : 0
);
const key = `${layer.id}-${layerIdCount.get(layer.id)}`;
const listItem = <LayerListItem const listItem = <LayerListItem
className={classnames({ className={classnames({
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex, 'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
@ -234,7 +238,7 @@ class LayerListContainer extends React.Component {
'maputnik-layer-list-item--error': !!layerError 'maputnik-layer-list-item--error': !!layerError
})} })}
index={idx} index={idx}
key={key} key={layer.key}
layerId={layer.id} layerId={layer.id}
layerIndex={idx} layerIndex={idx}
layerType={layer.type} layerType={layer.type}
@ -251,7 +255,12 @@ class LayerListContainer extends React.Component {
}) })
}) })
return <div className="maputnik-layer-list" ref={this.scrollContainerRef}> return <div
className="maputnik-layer-list"
role="complementary"
aria-label="Layers list"
ref={this.scrollContainerRef}
>
<AddModal <AddModal
layers={this.props.layers} layers={this.props.layers}
sources={this.props.sources} sources={this.props.sources}
@ -265,7 +274,7 @@ class LayerListContainer extends React.Component {
<div className="maputnik-default-property"> <div className="maputnik-default-property">
<div className="maputnik-multibutton"> <div className="maputnik-multibutton">
<button <button
id="skip-menu" id="skip-target-layer-list"
onClick={this.toggleLayers} onClick={this.toggleLayers}
className="maputnik-button"> className="maputnik-button">
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"} {this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
@ -283,7 +292,10 @@ class LayerListContainer extends React.Component {
</div> </div>
</div> </div>
</header> </header>
<ul className="maputnik-layer-list-container"> <ul className="maputnik-layer-list-container"
role="navigation"
aria-label="Layers list"
>
{listItems} {listItems}
</ul> </ul>
</div> </div>

View file

@ -16,7 +16,13 @@ export default class LayerListGroup extends React.Component {
data-wd-key={"layer-list-group:"+this.props["data-wd-key"]} data-wd-key={"layer-list-group:"+this.props["data-wd-key"]}
onClick={e => this.props.onActiveToggle(!this.props.isActive)} onClick={e => this.props.onActiveToggle(!this.props.isActive)}
> >
<span className="maputnik-layer-list-group-title">{this.props.title}</span> <button
className="maputnik-layer-list-group-title"
aria-controls={this.props['aria-controls']}
aria-expanded={this.props.isActive}
>
{this.props.title}
</button>
<span className="maputnik-space" /> <span className="maputnik-space" />
<Collapser <Collapser
style={{ height: 14, width: 14 }} style={{ height: 14, width: 14 }}

View file

@ -14,7 +14,9 @@ const DraggableLabel = SortableHandle((props) => {
className="layer-handle__icon" className="layer-handle__icon"
type={props.layerType} type={props.layerType}
/> />
<span className="maputnik-layer-list-item-id">{props.layerId}</span> <button className="maputnik-layer-list-item-id">
{props.layerId}
</button>
</div> </div>
}); });
@ -54,6 +56,7 @@ class IconAction extends React.Component {
className={`maputnik-layer-list-icon-action ${classAdditions}`} className={`maputnik-layer-list-icon-action ${classAdditions}`}
data-wd-key={this.props.wdKey} data-wd-key={this.props.wdKey}
onClick={this.props.onClick} onClick={this.props.onClick}
aria-hidden="true"
> >
{this.renderIcon()} {this.renderIcon()}
</button> </button>

View file

@ -221,6 +221,8 @@ export default class MapboxGlMap extends React.Component {
if(IS_SUPPORTED) { if(IS_SUPPORTED) {
return <div return <div
className="maputnik-map__map" className="maputnik-map__map"
role="region"
aria-label="Map view"
ref={x => this.container = x} ref={x => this.container = x}
></div> ></div>
} }

View file

@ -179,6 +179,8 @@ export default class OpenLayersMap extends React.Component {
<div <div
className="maputnik-ol" className="maputnik-ol"
ref={x => this.container = x} ref={x => this.container = x}
role="region"
aria-label="Map view"
style={{ style={{
...this.props.style, ...this.props.style,
}}> }}>

View file

@ -104,7 +104,10 @@ class ExportModal extends React.Component {
</InputBlock> </InputBlock>
</div> </div>
<Button onClick={this.downloadStyle.bind(this)}> <Button
onClick={this.downloadStyle.bind(this)}
title="Download style"
>
<MdFileDownload /> <MdFileDownload />
Download Download
</Button> </Button>

View file

@ -54,6 +54,7 @@ class Modal extends React.Component {
<h1 className="maputnik-modal-header-title">{this.props.title}</h1> <h1 className="maputnik-modal-header-title">{this.props.title}</h1>
<span className="maputnik-modal-header-space"></span> <span className="maputnik-modal-header-space"></span>
<button className="maputnik-modal-header-toggle" <button className="maputnik-modal-header-toggle"
title="Close modal"
onClick={this.onClose} onClick={this.onClose}
data-wd-key={this.props["data-wd-key"]+".close-modal"} data-wd-key={this.props["data-wd-key"]+".close-modal"}
> >

View file

@ -226,7 +226,7 @@ class OpenModal extends React.Component {
type="submit" type="submit"
className="maputnik-big-button" className="maputnik-big-button"
disabled={this.state.styleUrl.length < 1} disabled={this.state.styleUrl.length < 1}
>Open URL</Button> >Load from URL</Button>
</div> </div>
</form> </form>
</section> </section>

View file

@ -190,6 +190,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"Light anchor"} fieldSpec={latest.light.anchor}> <InputBlock label={"Light anchor"} fieldSpec={latest.light.anchor}>
<EnumInput <EnumInput
{...inputProps} {...inputProps}
name="light-anchor"
value={light.anchor} value={light.anchor}
options={Object.keys(latest.light.anchor.values)} options={Object.keys(latest.light.anchor.values)}
default={latest.light.anchor.default} default={latest.light.anchor.default}

View file

@ -13,40 +13,92 @@ class ShortcutsModal extends React.Component {
render() { render() {
const help = [ const help = [
{ {
key: "?", key: <kbd>?</kbd>,
text: "Shortcuts menu" text: "Shortcuts menu"
}, },
{ {
key: "o", key: <kbd>o</kbd>,
text: "Open modal" text: "Open modal"
}, },
{ {
key: "e", key: <kbd>e</kbd>,
text: "Export modal" text: "Export modal"
}, },
{ {
key: "d", key: <kbd>d</kbd>,
text: "Data Sources modal" text: "Data Sources modal"
}, },
{ {
key: "s", key: <kbd>s</kbd>,
text: "Style Settings modal" text: "Style Settings modal"
}, },
{ {
key: "i", key: <kbd>i</kbd>,
text: "Toggle inspect" text: "Toggle inspect"
}, },
{ {
key: "m", key: <kbd>m</kbd>,
text: "Focus map" text: "Focus map"
}, },
{ {
key: "!", key: <kbd>!</kbd>,
text: "Debug modal" text: "Debug modal"
}, },
] ]
const mapShortcuts = [
{
key: <kbd>+</kbd>,
text: "Increase the zoom level by 1.",
},
{
key: <><kbd>Shift</kbd> + <kbd>+</kbd></>,
text: "Increase the zoom level by 2.",
},
{
key: <kbd>-</kbd>,
text: "Decrease the zoom level by 1.",
},
{
key: <><kbd>Shift</kbd> + <kbd>-</kbd></>,
text: "Decrease the zoom level by 2.",
},
{
key: <kbd>Up</kbd>,
text: "Pan up by 100 pixels.",
},
{
key: <kbd>Down</kbd>,
text: "Pan down by 100 pixels.",
},
{
key: <kbd>Left</kbd>,
text: "Pan left by 100 pixels.",
},
{
key: <kbd>Right</kbd>,
text: "Pan right by 100 pixels.",
},
{
key: <><kbd>Shift</kbd> + <kbd>Right</kbd></>,
text: "Increase the rotation by 15 degrees.",
},
{
key: <><kbd>Shift</kbd> + <kbd>Left</kbd></>,
text: "Decrease the rotation by 15 degrees."
},
{
key: <><kbd>Shift</kbd> + <kbd>Up</kbd></>,
text: "Increase the pitch by 10 degrees."
},
{
key: <><kbd>Shift</kbd> + <kbd>Down</kbd></>,
text: "Decrease the pitch by 10 degrees."
},
]
return <Modal return <Modal
data-wd-key="shortcuts-modal" data-wd-key="shortcuts-modal"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
@ -57,10 +109,19 @@ class ShortcutsModal extends React.Component {
<p> <p>
Press <code>ESC</code> to lose focus of any active elements, then press one of: Press <code>ESC</code> to lose focus of any active elements, then press one of:
</p> </p>
<dl>
{help.map((item, idx) => {
return <div key={idx} className="maputnik-modal-shortcuts__shortcut">
<dt key={"dt"+idx}>{item.key}</dt>
<dd key={"dd"+idx}>{item.text}</dd>
</div>
})}
</dl>
<p>If the Map is in focused you can use the following shortcuts</p>
<ul> <ul>
{help.map((item) => { {mapShortcuts.map((item, idx) => {
return <li key={item.key}> return <li key={idx}>
<code>{item.key}</code> {item.text} <span>{item.key}</span> {item.text}
</li> </li>
})} })}
</ul> </ul>

View file

@ -78,3 +78,19 @@
width: 92.5%; width: 92.5%;
} }
.maputnik-radio-as-button {
@extend .maputnik-button;
border: solid 1px transparent;
&:focus-within {
border: solid 1px $color-white;
}
input {
width: 0;
overflow: hidden;
opacity: 0;
margin: 0;
}
}

View file

@ -36,6 +36,7 @@
&-item-handle { &-item-handle {
flex: 1; flex: 1;
display: flex; display: flex;
cursor: grab;
svg { svg {
margin-right: 4px; margin-right: 4px;
@ -43,12 +44,11 @@
} }
&-item { &-item {
border: solid 1px transparent;
font-weight: 400; font-weight: 400;
color: $color-lowgray; color: $color-lowgray;
font-size: $font-size-6; font-size: $font-size-6;
border-width: 0 0 1px; border-bottom-color: lighten($color-black, 0.1);
border-style: solid;
border-color: lighten($color-black, 0.1);
user-select: none; user-select: none;
list-style: none; list-style: none;
z-index: 2000; z-index: 2000;
@ -61,6 +61,10 @@
-webkit-transition: opacity 600ms, visibility 600ms; -webkit-transition: opacity 600ms, visibility 600ms;
transition: opacity 600ms, visibility 600ms; transition: opacity 600ms, visibility 600ms;
&:focus-within {
border: solid 1px $color-lowgray;
}
@media screen and (prefers-reduced-motion: reduce) { @media screen and (prefers-reduced-motion: reduce) {
transition-duration: 0; transition-duration: 0;
} }
@ -131,22 +135,33 @@
} }
&-item-id { &-item-id {
all: inherit;
width: 115px; width: 115px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
cursor: pointer;
} }
&-group-header { &-group-header {
border: solid 1px transparent;
font-size: $font-size-6; font-size: $font-size-6;
color: $color-lowgray; color: $color-lowgray;
background-color: lighten($color-black, 2); background-color: lighten($color-black, 2);
cursor: pointer;
user-select: none; user-select: none;
padding: $margin-2; padding: $margin-2;
&:focus-within {
border: solid 1px $color-lowgray;
}
button {
all: unset;
cursor: pointer;
}
@include flex-row; @include flex-row;
svg { svg {

View file

@ -256,17 +256,33 @@
} }
.maputnik-modal-shortcuts { .maputnik-modal-shortcuts {
code { position: relative;
overflow: hidden;
max-width: 30em;
kbd {
color: white; color: white;
background: #3c3c3c; background: #3c3c3c;
padding: 2px 6px; padding: 2px 6px;
display: inline-block; display: inline-block;
text-align: center; text-align: center;
border-radius: 2px; border-radius: 2px;
margin-right: 4px;
font-family: monospace; font-family: monospace;
} }
&__shortcut {
margin-bottom: $margin-2;
}
dt {
display: inline;
margin-right: $margin-2;
}
dd {
display: inline;
}
li { li {
margin-bottom: 4px; margin-bottom: 4px;
} }

View file

@ -132,6 +132,8 @@
} }
.maputnik-toolbar-skip { .maputnik-toolbar-skip {
all: unset;
border: solid 1px transparent;
position: absolute; position: absolute;
overflow: hidden; overflow: hidden;
width: 0px; width: 0px;
@ -147,6 +149,7 @@
&:active, &:active,
&:focus { &:focus {
width: 100%; width: 100%;
border-color: $color-lowgray;
} }
} }