<fieldset class="field-group m-form-control m-form-control--large m-radiobutton-advanced js-radiobutton-advanced">
    <legend class="label-legend" id="radiobutton-advanced-legend">Vad vill du söka efter?</legend>
    <p class="u-visuallyhidden" id="radiobutton-advanced-help">På mindre skärmar visas bara valt alternativ. Aktivera det synliga alternativet för att visa alla alternativ. Tryck Enter eller Escape för att stänga listan.</p>
    <p class="u-visuallyhidden js-radiobutton-advanced__status" id="radiobutton-advanced-status" aria-live="polite" aria-atomic="true"></p>
    <div class="m-radiobutton-advanced__options js-radiobutton-advanced__options" id="radiobutton-advanced-options" aria-labelledby="radiobutton-advanced-legend" aria-describedby="radiobutton-advanced-help radiobutton-advanced-status" aria-expanded="false">
        <div class="radio m-radiobutton-advanced__option js-radiobutton-advanced__option">
            <input type="radio" id="radiobutton-advanced-1" name="radiobutton-advanced" value="first" class="a-radio u-visuallyhidden js-radiobutton-advanced__input" data-placeholder="t.ex. volvo.se" aria-describedby="radiobutton-advanced-1-description" disabled>
            <label for="radiobutton-advanced-1">
                <span class="m-radiobutton-advanced__content">
                    <span class="m-radiobutton-advanced__label">Domän</span>
                    <span class="m-radiobutton-advanced__description" id="radiobutton-advanced-1-description">Sök efter .se eller .nu-domän.</span>
                </span>
                <span class="m-radiobutton-advanced__icon" aria-hidden="true">
                    <span></span>
                    <span></span>
                    <span></span>
                </span>
            </label>
        </div>
        <div class="radio m-radiobutton-advanced__option js-radiobutton-advanced__option">
            <input type="radio" id="radiobutton-advanced-2" name="radiobutton-advanced" value="second" class="a-radio u-visuallyhidden js-radiobutton-advanced__input" data-placeholder="t.ex. volvo" aria-describedby="radiobutton-advanced-2-description" disabled>
            <label for="radiobutton-advanced-2">
                <span class="m-radiobutton-advanced__content">
                    <span class="m-radiobutton-advanced__label">Nyckelord</span>
                    <span class="m-radiobutton-advanced__description" id="radiobutton-advanced-2-description">Sök efter alla domäner med detta nyckelord.</span>
                </span>
                <span class="m-radiobutton-advanced__icon" aria-hidden="true">
                    <span></span>
                    <span></span>
                    <span></span>
                </span>
            </label>
        </div>
        <div class="radio m-radiobutton-advanced__option js-radiobutton-advanced__option">
            <input type="radio" id="radiobutton-advanced-3" name="radiobutton-advanced" value="third" class="a-radio u-visuallyhidden js-radiobutton-advanced__input" data-placeholder="t.ex. 556810-8988" aria-describedby="radiobutton-advanced-3-description" disabled>
            <label for="radiobutton-advanced-3">
                <span class="m-radiobutton-advanced__content">
                    <span class="m-radiobutton-advanced__label">Organisationsnummer</span>
                    <span class="m-radiobutton-advanced__description" id="radiobutton-advanced-3-description">Sök alla domäner registrerade med detta organisationsnummer.</span>
                </span>
                <span class="m-radiobutton-advanced__icon" aria-hidden="true">
                    <span></span>
                    <span></span>
                    <span></span>
                </span>
            </label>
        </div>
    </div>
</fieldset>
<fieldset class="field-group m-form-control m-form-control--large m-radiobutton-advanced js-radiobutton-advanced{{#if is_invalid}} is-invalid{{/if}}">
	<legend class="label-legend" id="{{name}}-legend">{{legend}}</legend>
	<p class="u-visuallyhidden" id="{{name}}-help">På mindre skärmar visas bara valt alternativ. Aktivera det synliga alternativet för att visa alla alternativ. Tryck Enter eller Escape för att stänga listan.</p>
	<p class="u-visuallyhidden js-radiobutton-advanced__status" id="{{name}}-status" aria-live="polite" aria-atomic="true"></p>
	<div class="m-radiobutton-advanced__options js-radiobutton-advanced__options" id="{{name}}-options" aria-labelledby="{{name}}-legend" aria-describedby="{{name}}-help {{name}}-status" aria-expanded="false">
		{{#each options}}
			<div class="radio m-radiobutton-advanced__option js-radiobutton-advanced__option{{#if ../is_invalid}} is-invalid{{/if}}">
				<input type="radio" id="{{id}}" name="{{../name}}" value="{{value}}" class="a-radio u-visuallyhidden js-radiobutton-advanced__input"{{#if placeholder}} data-placeholder="{{placeholder}}"{{/if}}{{#if description}} aria-describedby="{{id}}-description"{{/if}}{{#if is_checked}} checked{{/if}}{{#ifequals value ../selected_value}} checked{{/ifequals}}{{#if ../is_disabled}} disabled{{else}}{{#if is_disabled}} disabled{{/if}}{{/if}}{{#if ../is_required}} required{{else}}{{#if is_required}} required{{/if}}{{/if}}{{#if ../is_invalid}} aria-invalid="true"{{else}}{{#if is_invalid}} aria-invalid="true"{{/if}}{{/if}}>
				<label for="{{id}}">
					<span class="m-radiobutton-advanced__content">
						<span class="m-radiobutton-advanced__label">{{label}}</span>
						{{#if description}}<span class="m-radiobutton-advanced__description" id="{{id}}-description">{{description}}</span>{{/if}}
					</span>
					<span class="m-radiobutton-advanced__icon" aria-hidden="true">
						<span></span>
						<span></span>
						<span></span>
					</span>
				</label>
			</div>
		{{/each}}
	</div>
</fieldset>
{
  "legend": "Vad vill du söka efter?",
  "name": "radiobutton-advanced",
  "options": [
    {
      "id": "radiobutton-advanced-1",
      "label": "Domän",
      "description": "Sök efter .se eller .nu-domän.",
      "value": "first",
      "placeholder": "t.ex. volvo.se"
    },
    {
      "id": "radiobutton-advanced-2",
      "label": "Nyckelord",
      "description": "Sök efter alla domäner med detta nyckelord.",
      "value": "second",
      "placeholder": "t.ex. volvo"
    },
    {
      "id": "radiobutton-advanced-3",
      "label": "Organisationsnummer",
      "description": "Sök alla domäner registrerade med detta organisationsnummer.",
      "value": "third",
      "placeholder": "t.ex. 556810-8988"
    }
  ],
  "is_disabled": true
}
  • Content:
    @charset "UTF-8";
    @use '../../configurations/mixins' as mixin;
    @use '../../configurations/bem' as bem;
    @use '../../configurations/variables' as var;
    @use '../../configurations/functions' as func;
    @use '../../configurations/colors/colors' as colors;
    @use '../../vendor/grid/breakpoints' as breakpoint;
    
    @keyframes radiobutton-advanced-icon-pulse {
    	0% {
    		transform: scale(1);
    		opacity: 0.8;
    	}
    
    	45% {
    		transform: scale(1.65);
    		opacity: 0;
    	}
    
    	100% {
    		transform: scale(1.65);
    		opacity: 0;
    	}
    }
    
    @include mixin.molecule(radiobutton-advanced) {
    	@include breakpoint.bp-down(sm) {
    		&.is-expanded {
    			padding-bottom: var(--radiobutton-advanced-collapsed-height, 0);
    
    			[class*='m-radiobutton-advanced__options'] {
    				position: absolute;
    				right: 0;
    				left: 0;
    				z-index: func.z_index(foreground);
    				padding: func.rhythm(1);
    				border: 1px solid colors.$color-concrete;
    				border-radius: var.$border-radius;
    				background-color: colors.$color-snow;
    			}
    		}
    
    		&.is-collapsed:not(.is-expanded) {
    			[class*='m-radiobutton-advanced__options'] > [class*='m-radiobutton-advanced__option'] {
    				@include mixin.visuallyhidden;
    
    				&.is-visible {
    					position: static;
    					width: auto;
    					height: auto;
    					margin-bottom: 0;
    					overflow: visible;
    					clip: auto;
    				}
    			}
    
    			[class*='m-radiobutton-advanced__options'] > [class*='m-radiobutton-advanced__option'].is-visible {
    				cursor: pointer;
    
    				[class*='m-radiobutton-advanced__icon'] {
    					display: inline-flex;
    				}
    			}
    		}
    
    		&.is-collapsed:not(.is-expanded):not(.has-selection) {
    			[class*='m-radiobutton-advanced__options'] > [class*='m-radiobutton-advanced__option'].is-visible {
    				[class*='m-radiobutton-advanced__icon'] {
    					background-color: colors.$color-ocean-lighter;
    
    					&::after {
    						animation: radiobutton-advanced-icon-pulse 2.8s ease-out infinite;
    					}
    				}
    			}
    		}
    	}
    
    	@include bem.e(options) {
    		width: 100%;
    
    		@include breakpoint.bp-up(md) {
    			display: flex;
    			flex-wrap: wrap;
    			gap: func.rhythm(1);
    		}
    	}
    
    	@include bem.e(option) {
    		margin-bottom: func.rhythm(1);
    		border: 1px solid colors.$color-concrete;
    		border-radius: var.$border-radius;
    		background-color: colors.$color-snow;
    		box-shadow: none;
    		transition: box-shadow 0.25s ease;
    
    		&:last-child {
    			margin-bottom: 0;
    		}
    
    		@include breakpoint.bp-up(md) {
    			flex: 1 1 0;
    			margin-bottom: 0;
    		}
    
    		&.is-checked {
    			border-color: colors.$color-ocean-light;
    			background-color: colors.$color-ocean-lighter;
    			box-shadow: 0 0 0 2px colors.$color-ocean-light, 0 func.rhythm(0.5) func.rhythm(0.5) rgba(colors.$color-cyberspace, 0.1);
    
    			> label {
    				&:hover,
    				&:focus {
    					background-color: colors.$color-ocean-lighter;
    				}
    			}
    		}
    
    		> label {
    			width: 100%;
    			height: 100%;
    			padding-left: func.rhythm(1);
    			padding-right: func.rhythm(1);
    			margin-bottom: 0;
    
    			&:hover,
    			&:focus {
    				background-color: colors.$color-ash;
    			}
    
    			@include breakpoint.bp-up(md) {
    				padding-left: func.rhythm(1.5);
    				padding-right: func.rhythm(1.5);
    				padding-top: func.rhythm(1) !important;
    				padding-bottom: func.rhythm(1) !important;
    			}
    		}
    	}
    
    	@include bem.e(content) {
    		display: block;
    		flex-grow: 1;
    	}
    
    	@include bem.e(label) {
    		display: block;
    		font-family: var.$font-family-medium;
    	}
    
    	@include bem.e(description) {
    		display: block;
    		margin-top: func.rhythm(0.25);
    		color: colors.$color-dark;
    		font-size: var.$size-medium;
    		line-height: 1.4;
    	}
    
    	@include bem.e(icon) {
    		display: none;
    		position: relative;
    		flex-direction: column;
    		justify-content: center;
    		align-items: center;
    		flex-shrink: 0;
    		gap: func.to_rem(3px);
    		align-self: center;
    		margin-left: func.rhythm(1);
    		padding: func.rhythm(0.5);
    		width: func.to_rem(28px);
    		height: func.to_rem(28px);
    		border-radius: 50%;
    		background-color: colors.$color-snow;
    
    		&::after {
    			position: absolute;
    			inset: 0;
    			border-radius: 50%;
    			box-shadow: 0 0 0 2px colors.$color-ocean;
    			content: '';
    			opacity: 0;
    			pointer-events: none;
    		}
    
    		> span {
    			width: func.to_rem(4px);
    			height: func.to_rem(4px);
    			border-radius: 50%;
    			background-color: colors.$color-cyberspace;
    		}
    	}
    }
    
  • URL: /components/raw/radiobutton-advanced/_radiobutton-advanced.scss
  • Filesystem Path: src/molecules/radiobutton-advanced/_radiobutton-advanced.scss
  • Size: 4.2 KB
  • Content:
    import className from '../../assets/js/className';
    
    const radiobuttonAdvancedGroups = document.querySelectorAll('.js-radiobutton-advanced');
    const optionsSelector = '.js-radiobutton-advanced__options';
    const optionSelector = '.js-radiobutton-advanced__option';
    const inputSelector = '.js-radiobutton-advanced__input';
    const statusSelector = '.js-radiobutton-advanced__status';
    const labelSelector = `.${className('m-radiobutton-advanced__label')}`;
    const collapsedLayout = window.matchMedia('(max-width: 768px)');
    const keyboardInteractionGroups = new WeakSet();
    const keyboardExpandKeys = ['Enter', ' ', 'Spacebar'];
    const keyboardSelectionKeys = ['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'End', 'Home', ' ', 'Spacebar'];
    
    function getOptions(group) {
    	return Array.from(group.querySelectorAll(optionSelector));
    }
    
    function getInput(option) {
    	return option.querySelector(inputSelector);
    }
    
    function getVisibleOption(options) {
    	return options.find((option) => {
    		const input = getInput(option);
    
    		return input && input.checked;
    	}) || options[0];
    }
    
    function getOptionLabel(option) {
    	const label = option.querySelector(labelSelector);
    
    	return label ? label.textContent.trim() : '';
    }
    
    function setCheckedState(group) {
    	const options = getOptions(group);
    	let hasCheckedOption = false;
    
    	options.forEach((option) => {
    		const input = getInput(option);
    
    		if (!input) {
    			return;
    		}
    
    		option.classList.toggle('is-checked', input.checked);
    		hasCheckedOption = hasCheckedOption || input.checked;
    	});
    
    	group.classList.toggle('has-selection', hasCheckedOption);
    }
    
    function setVisibleState(group) {
    	const options = getOptions(group);
    	const visibleOption = getVisibleOption(options);
    
    	options.forEach((option) => {
    		option.classList.toggle('is-visible', option === visibleOption);
    	});
    }
    
    function setOptionAccessibility(group) {
    	getOptions(group).forEach((option) => {
    		option.removeAttribute('aria-hidden');
    	});
    }
    
    function setAccessibilityState(group) {
    	const options = group.querySelector(optionsSelector);
    	const status = group.querySelector(statusSelector);
    	const isExpanded = !collapsedLayout.matches || group.classList.contains('is-expanded');
    
    	if (options) {
    		options.setAttribute('aria-expanded', isExpanded ? 'true' : 'false');
    	}
    
    	if (!status) {
    		return;
    	}
    
    	if (!collapsedLayout.matches) {
    		status.textContent = '';
    
    		return;
    	}
    
    	if (isExpanded) {
    		status.textContent = 'Alla alternativ visas.';
    
    		return;
    	}
    
    	const visibleOption = getVisibleOption(getOptions(group));
    	const label = visibleOption ? getOptionLabel(visibleOption) : '';
    
    	status.textContent = label ? `Valt alternativ: ${label}. Aktivera alternativet för att visa fler alternativ.` : 'Aktivera alternativet för att visa fler alternativ.';
    }
    
    function setCollapsedHeight(group) {
    	const options = group.querySelector(optionsSelector);
    
    	if (!options) {
    		return;
    	}
    
    	group.style.setProperty('--radiobutton-advanced-collapsed-height', `${options.offsetHeight}px`);
    }
    
    function collapseGroup(group) {
    	group.classList.add('is-collapsed');
    	group.classList.remove('is-expanded');
    	setVisibleState(group);
    	setOptionAccessibility(group);
    	setAccessibilityState(group);
    	setCollapsedHeight(group);
    }
    
    function expandGroup(group) {
    	group.classList.add('is-expanded');
    	setOptionAccessibility(group);
    	setAccessibilityState(group);
    }
    
    function syncGroup(group) {
    	setCheckedState(group);
    	collapseGroup(group);
    }
    
    function syncExpandedGroup(group) {
    	setCheckedState(group);
    	setVisibleState(group);
    	setOptionAccessibility(group);
    	setAccessibilityState(group);
    }
    
    radiobuttonAdvancedGroups.forEach((group) => {
    	group.addEventListener('pointerdown', () => {
    		keyboardInteractionGroups.delete(group);
    	});
    
    	group.addEventListener('click', (event) => {
    		if (!collapsedLayout.matches) {
    			return;
    		}
    
    		if (keyboardInteractionGroups.has(group)) {
    			return;
    		}
    
    		const option = event.target.closest(optionSelector);
    
    		if (!option || !group.contains(option)) {
    			return;
    		}
    
    		const input = getInput(option);
    
    		if (!input || input.disabled) {
    			return;
    		}
    
    		if (group.classList.contains('is-expanded')) {
    			if (option.classList.contains('is-checked')) {
    				collapseGroup(group);
    			}
    
    			return;
    		}
    
    		if (option.classList.contains('is-visible')) {
    			event.preventDefault();
    			expandGroup(group);
    		}
    	});
    
    	group.addEventListener('keydown', (event) => {
    		if (!collapsedLayout.matches) {
    			return;
    		}
    
    		const option = event.target.closest(optionSelector);
    
    		if (option && group.contains(option) && keyboardSelectionKeys.includes(event.key)) {
    			keyboardInteractionGroups.add(group);
    		}
    
    		if (event.key === 'Escape' && group.classList.contains('is-expanded')) {
    			event.preventDefault();
    			keyboardInteractionGroups.delete(group);
    			collapseGroup(group);
    
    			const visibleOption = getVisibleOption(getOptions(group));
    			const input = visibleOption ? getInput(visibleOption) : null;
    
    			if (input) {
    				input.focus();
    			}
    
    			return;
    		}
    
    		if (event.key === 'Enter' && group.classList.contains('is-expanded')) {
    			event.preventDefault();
    			keyboardInteractionGroups.delete(group);
    			collapseGroup(group);
    
    			const visibleOption = getVisibleOption(getOptions(group));
    			const input = visibleOption ? getInput(visibleOption) : null;
    
    			if (input) {
    				input.focus();
    			}
    
    			return;
    		}
    
    		if (!keyboardExpandKeys.includes(event.key) || !option || !group.contains(option) || group.classList.contains('is-expanded') || !option.classList.contains('is-visible')) {
    			return;
    		}
    
    		event.preventDefault();
    		keyboardInteractionGroups.add(group);
    		expandGroup(group);
    	});
    
    	group.addEventListener('change', (event) => {
    		if (!event.target.matches(inputSelector)) {
    			return;
    		}
    
    		if (collapsedLayout.matches && group.classList.contains('is-expanded') && keyboardInteractionGroups.has(group)) {
    			syncExpandedGroup(group);
    
    			return;
    		}
    
    		keyboardInteractionGroups.delete(group);
    		syncGroup(group);
    	});
    
    	syncGroup(group);
    });
    
    function syncResponsiveAccessibility() {
    	radiobuttonAdvancedGroups.forEach((group) => {
    		setOptionAccessibility(group);
    		setAccessibilityState(group);
    		setCollapsedHeight(group);
    	});
    }
    
    if (collapsedLayout.addEventListener) {
    	collapsedLayout.addEventListener('change', syncResponsiveAccessibility);
    } else {
    	collapsedLayout.addListener(syncResponsiveAccessibility);
    }
    
  • URL: /components/raw/radiobutton-advanced/radiobutton-advanced.js
  • Filesystem Path: src/molecules/radiobutton-advanced/radiobutton-advanced.js
  • Size: 6.4 KB

No notes defined.