mirror of
https://github.com/primer/css.git
synced 2024-09-11 16:36:07 +03:00
FormControl refactor/cleanup (#2114)
* checkbox animation adjustment * radio styles * disabled state * pull styles from app header * cleanup spacing/alignment * add checkbox focus styles * stories + radio focus styles * convert to css check * refactor to pure *CSS* :) * configure multi radio story * lint * Create neat-sloths-march.md * snappier animations * adjust mask size for better scailing * checkbox mask size * lint * Stylelint auto-fixes * rename classes * how many times will she refactor? * slight refactor based on original api * delete component file test * Stylelint auto-fixes * add comments around padding * Stylelint auto-fixes * undo changes to old forms * increase touch target :) * remove unused grid + cursor pointer * Stylelint auto-fixes * adjust disabled checked styles * bump primitives * Stylelint auto-fixes * Update neat-sloths-march.md * temp add prefix for testing * test windows high contrast * cleanup prefix for final build Co-authored-by: Actions Auto Build <actions@github.com>
This commit is contained in:
parent
8984684470
commit
facb96823a
7
.changeset/neat-sloths-march.md
Normal file
7
.changeset/neat-sloths-march.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"@primer/css": patch
|
||||
---
|
||||
|
||||
- Updates stories to reflect markup for Rails
|
||||
- Clean up FormControl classes
|
||||
- Add Radio and Checkbox custom styles
|
@ -7,7 +7,8 @@ module.exports = {
|
||||
'@storybook/preset-scss',
|
||||
'@whitespace/storybook-addon-html',
|
||||
'storybook-addon-designs',
|
||||
'storybook-color-picker'
|
||||
'storybook-color-picker',
|
||||
'storybook-addon-variants/preset.js'
|
||||
],
|
||||
framework: '@storybook/react',
|
||||
core: {
|
||||
|
@ -59,6 +59,7 @@
|
||||
"@whitespace/storybook-addon-html": "^5.0.0",
|
||||
"babel-loader": "^8.2.5",
|
||||
"storybook-addon-designs": "6.2.1",
|
||||
"storybook-addon-variants": "^0.0.5",
|
||||
"storybook-color-picker": "2.3.1"
|
||||
}
|
||||
}
|
||||
|
117
docs/src/stories/rails-form-framework/Checkbox.stories.jsx
Normal file
117
docs/src/stories/rails-form-framework/Checkbox.stories.jsx
Normal file
@ -0,0 +1,117 @@
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default {
|
||||
title: 'Rails Forms/Checkbox',
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
decorators: [
|
||||
Story => (
|
||||
<form>
|
||||
<Story />
|
||||
</form>
|
||||
)
|
||||
],
|
||||
excludeStories: ['InputTemplate'],
|
||||
argTypes: {
|
||||
disabled: {
|
||||
description: 'disabled field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'CSS'
|
||||
}
|
||||
},
|
||||
visuallyHidden: {
|
||||
description: 'visually hide label',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'CSS'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
caption: {
|
||||
name: 'caption',
|
||||
type: 'string',
|
||||
description: 'caption',
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
focusElement: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'set focus on element',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
},
|
||||
checked: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'checked',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
},
|
||||
indeterminate: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'indeterminate',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const focusMethod = function getFocus() {
|
||||
// find the focusable element
|
||||
var input = document.getElementsByTagName('input')[0]
|
||||
// set focus on element
|
||||
input.focus()
|
||||
}
|
||||
|
||||
export const InputTemplate = ({label, disabled, visuallyHidden, focusElement, caption, checked, indeterminate}) => (
|
||||
<>
|
||||
<div data-view-component="true" class="FormControl-checkbox-wrap">
|
||||
<input
|
||||
id="input-id"
|
||||
type="checkbox"
|
||||
disabled={disabled}
|
||||
class="FormControl-checkbox"
|
||||
checked={checked ? 'true' : undefined}
|
||||
indeterminate={indeterminate ? 'true' : undefined}
|
||||
ariaDescribedBy={caption ? 'caption-ebb67985' : undefined}
|
||||
/>
|
||||
<span class="FormControl-checkbox-labelWrap">
|
||||
<label htmlFor="input-id" className={clsx('FormControl-label', visuallyHidden && 'sr-only')}>
|
||||
{label}
|
||||
</label>
|
||||
{caption && (
|
||||
<p className="FormControl-caption" id="caption-ebb67985">
|
||||
{caption}
|
||||
</p>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{focusElement && focusMethod()}
|
||||
</>
|
||||
)
|
||||
|
||||
export const Playground = InputTemplate.bind({})
|
||||
Playground.args = {
|
||||
label: 'Select an option',
|
||||
disabled: false,
|
||||
focusElement: false,
|
||||
caption: 'Caption',
|
||||
invalid: false,
|
||||
visuallyHidden: false,
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
}
|
127
docs/src/stories/rails-form-framework/Radio.stories.jsx
Normal file
127
docs/src/stories/rails-form-framework/Radio.stories.jsx
Normal file
@ -0,0 +1,127 @@
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default {
|
||||
title: 'Rails Forms/Radio',
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
decorators: [
|
||||
Story => (
|
||||
<form>
|
||||
<Story />
|
||||
</form>
|
||||
)
|
||||
],
|
||||
excludeStories: ['RadioTemplate'],
|
||||
argTypes: {
|
||||
disabled: {
|
||||
description: 'disabled field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'CSS'
|
||||
}
|
||||
},
|
||||
visuallyHidden: {
|
||||
description: 'visually hide label',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'CSS'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
caption: {
|
||||
name: 'caption',
|
||||
type: 'string',
|
||||
description: 'caption',
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
id: {
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
description: 'id',
|
||||
table: {
|
||||
category: 'Radio'
|
||||
}
|
||||
},
|
||||
focusElement: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'set focus on element',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
},
|
||||
checked: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'checked',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
},
|
||||
indeterminate: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'indeterminate',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const focusMethod = function getFocus() {
|
||||
// find the focusable element
|
||||
var input = document.getElementsByTagName('input')[0]
|
||||
// set focus on element
|
||||
input.focus()
|
||||
}
|
||||
|
||||
export const RadioTemplate = ({label, disabled, visuallyHidden, focusElement, caption, checked, indeterminate, id}) => (
|
||||
<>
|
||||
<div class="FormControl-radio-wrap">
|
||||
<input
|
||||
id={id}
|
||||
name="radio"
|
||||
type="radio"
|
||||
disabled={disabled}
|
||||
class="FormControl-radio"
|
||||
checked={checked ? 'true' : undefined}
|
||||
indeterminate={indeterminate ? 'true' : undefined}
|
||||
ariaDescribedBy={caption ? 'caption-ebb67985' : undefined}
|
||||
/>
|
||||
<span class="FormControl-radio-labelWrap">
|
||||
<label htmlFor={id} className={clsx('FormControl-label', visuallyHidden && 'sr-only')}>
|
||||
{label}
|
||||
</label>
|
||||
{caption && (
|
||||
<p className="FormControl-caption" id="caption-ebb67985">
|
||||
{caption}
|
||||
</p>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{focusElement && focusMethod()}
|
||||
</>
|
||||
)
|
||||
|
||||
export const Playground = RadioTemplate.bind({})
|
||||
Playground.args = {
|
||||
id: 'some-id',
|
||||
label: 'Select an option',
|
||||
disabled: false,
|
||||
focusElement: false,
|
||||
caption: 'Caption',
|
||||
invalid: false,
|
||||
visuallyHidden: false,
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import {RadioTemplate} from './Radio.stories.jsx'
|
||||
|
||||
export default {
|
||||
title: 'Rails Forms/Radio/Features'
|
||||
}
|
||||
|
||||
export const MultiRadios = ({}) => (
|
||||
<form style={{display: 'grid', gap: '.5rem'}}>
|
||||
<RadioTemplate label="First radio item" caption="This one has a caption!" id="first" />
|
||||
<RadioTemplate label="Second radio item" id="second" />
|
||||
<RadioTemplate label="Third radio item" id="third" />
|
||||
</form>
|
||||
)
|
210
docs/src/stories/rails-form-framework/Select.stories.jsx
Normal file
210
docs/src/stories/rails-form-framework/Select.stories.jsx
Normal file
@ -0,0 +1,210 @@
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default {
|
||||
title: 'Rails Forms/Select',
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
decorators: [
|
||||
Story => (
|
||||
<form>
|
||||
<Story />
|
||||
</form>
|
||||
)
|
||||
],
|
||||
excludeStories: ['InputTemplate'],
|
||||
argTypes: {
|
||||
size: {
|
||||
options: [0, 1, 2], // iterator
|
||||
mapping: ['FormControl-small', 'FormControl-medium', 'FormControl-large'], // values
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: ['small', 'medium', 'large']
|
||||
},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
validationStatus: {
|
||||
options: [0, 1, 2, 3], // iterator
|
||||
mapping: ['', 'FormControl-error', 'FormControl-success', 'FormControl-warning'], // values
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: ['undefined', 'error', 'success', 'warning']
|
||||
},
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
fullWidth: {
|
||||
description: 'formerly called Block',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
description: 'disabled field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
required: {
|
||||
description: 'required field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
invalid: {
|
||||
description: 'invalid field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
visuallyHidden: {
|
||||
description: 'visually hide label',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Label'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'Label'
|
||||
}
|
||||
},
|
||||
caption: {
|
||||
name: 'caption',
|
||||
type: 'string',
|
||||
description: 'caption',
|
||||
table: {
|
||||
category: 'Caption'
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
focusElement: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'set focus on element',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
},
|
||||
monospace: {
|
||||
description: 'monospace text',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
inset: {
|
||||
description: 'formerly called Contrast',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const focusMethod = function getFocus() {
|
||||
// find the focusable element
|
||||
var input = document.getElementsByTagName('input')[0]
|
||||
// set focus on element
|
||||
input.focus()
|
||||
}
|
||||
|
||||
export const InputTemplate = ({
|
||||
label,
|
||||
size,
|
||||
fullWidth,
|
||||
placeholder,
|
||||
inset,
|
||||
disabled,
|
||||
visuallyHidden,
|
||||
monospace,
|
||||
focusElement,
|
||||
invalid,
|
||||
caption,
|
||||
validationStatus
|
||||
}) => (
|
||||
<>
|
||||
<div className={clsx('FormControl', fullWidth && 'FormControl--fullWidth')}>
|
||||
<label htmlFor="input-id" className={clsx('FormControl-label', visuallyHidden && 'sr-only')}>
|
||||
{label}
|
||||
</label>
|
||||
<div className={clsx('FormControl-select-wrap', size && `${size}`)}>
|
||||
<select
|
||||
placeholder={placeholder}
|
||||
id="input-id"
|
||||
name="input-id"
|
||||
className={clsx(
|
||||
'FormControl-select',
|
||||
size && `${size}`,
|
||||
validationStatus && `${validationStatus}`,
|
||||
inset && 'FormControl--inset',
|
||||
monospace && 'FormControl--monospace'
|
||||
)}
|
||||
disabled={disabled ? 'true' : undefined}
|
||||
invalid={invalid ? 'true' : undefined}
|
||||
>
|
||||
<option label="First item" data-view-component="true"></option>
|
||||
</select>
|
||||
</div>
|
||||
{invalid && (
|
||||
<span className="FormControl-inlineValidation">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
version="1.1"
|
||||
width="12"
|
||||
className="octicon octicon-alert-fill"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.855.708c.5-.896 1.79-.896 2.29 0l4.675 8.351a1.312 1.312 0 01-1.146 1.954H1.33A1.312 1.312 0 01.183 9.058L4.855.708zM7 7V3H5v4h2zm-1 3a1 1 0 100-2 1 1 0 000 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<p id="validation-5e6e1c8a">{validation}</p>
|
||||
</span>
|
||||
)}
|
||||
{caption && (
|
||||
<p className="FormControl-caption" id="caption-ebb67985">
|
||||
{caption}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{focusElement && focusMethod()}
|
||||
</>
|
||||
)
|
||||
|
||||
export const Playground = InputTemplate.bind({})
|
||||
Playground.args = {
|
||||
type: 'email',
|
||||
placeholder: 'Email address',
|
||||
label: 'Enter email address',
|
||||
fullWidth: false,
|
||||
monospace: false,
|
||||
inset: false,
|
||||
disabled: false,
|
||||
focusElement: false,
|
||||
size: 1,
|
||||
caption: 'Caption',
|
||||
invalid: false,
|
||||
visuallyHidden: false,
|
||||
validationStatus: 0
|
||||
}
|
311
docs/src/stories/rails-form-framework/TextInput.stories.jsx
Normal file
311
docs/src/stories/rails-form-framework/TextInput.stories.jsx
Normal file
@ -0,0 +1,311 @@
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default {
|
||||
title: 'Rails Forms/TextInput',
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
decorators: [
|
||||
Story => (
|
||||
<form>
|
||||
<Story />
|
||||
</form>
|
||||
)
|
||||
],
|
||||
excludeStories: ['InputTemplate'],
|
||||
argTypes: {
|
||||
size: {
|
||||
options: [0, 1, 2], // iterator
|
||||
mapping: ['FormControl-small', 'FormControl-medium', 'FormControl-large'], // values
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: ['small', 'medium', 'large']
|
||||
},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
validationStatus: {
|
||||
options: [0, 1, 2, 3], // iterator
|
||||
mapping: ['', 'FormControl-error', 'FormControl-success', 'FormControl-warning'], // values
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: ['undefined', 'error', 'success', 'warning']
|
||||
},
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
fullWidth: {
|
||||
description: 'formerly called Block',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
showClearButton: {
|
||||
description: 'show clear button',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
trailingActionDivider: {
|
||||
description: 'divider between input and trailing action',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
monospace: {
|
||||
description: 'monospace text',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
inset: {
|
||||
description: 'formerly called Contrast',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
description: 'disabled field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
required: {
|
||||
description: 'required field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
invalid: {
|
||||
description: 'invalid field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
visuallyHidden: {
|
||||
description: 'visually hide label',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Label'
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
name: 'placeholder',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'Label'
|
||||
}
|
||||
},
|
||||
caption: {
|
||||
name: 'caption',
|
||||
type: 'string',
|
||||
description: 'caption',
|
||||
table: {
|
||||
category: 'Caption'
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
focusElement: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'set focus on element',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
},
|
||||
leadingVisual: {
|
||||
name: 'leadingVisual',
|
||||
type: 'boolean',
|
||||
description: 'octicon',
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const focusMethod = function getFocus() {
|
||||
// find the focusable element
|
||||
var input = document.getElementsByTagName('input')[0]
|
||||
// set focus on element
|
||||
input.focus()
|
||||
}
|
||||
|
||||
export const InputTemplate = ({
|
||||
label,
|
||||
size,
|
||||
fullWidth,
|
||||
placeholder,
|
||||
inset,
|
||||
disabled,
|
||||
visuallyHidden,
|
||||
monospace,
|
||||
focusElement,
|
||||
showClearButton,
|
||||
leadingVisual,
|
||||
invalid,
|
||||
caption,
|
||||
validation,
|
||||
trailingActionDivider,
|
||||
validationStatus
|
||||
}) => (
|
||||
<>
|
||||
<div className={clsx('FormControl', fullWidth && 'FormControl--fullWidth')}>
|
||||
<label htmlFor="input-id" className={clsx('FormControl-label', visuallyHidden && 'sr-only')}>
|
||||
{label}
|
||||
</label>
|
||||
{showClearButton || leadingVisual ? (
|
||||
<div
|
||||
className={clsx(
|
||||
'FormControl-input-wrap',
|
||||
showClearButton && 'FormControl-input-wrap--trailingAction',
|
||||
trailingActionDivider && 'FormControl-input-wrap-trailingAction--divider',
|
||||
size && `${size}`,
|
||||
leadingVisual && 'FormControl-input-wrap--leadingVisual'
|
||||
)}
|
||||
>
|
||||
{leadingVisual && (
|
||||
<span class="FormControl-input-leadingVisualWrap">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
data-view-component="true"
|
||||
class="octicon octicon-search FormControl-input-leadingVisual"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M11.5 7a4.499 4.499 0 11-8.998 0A4.499 4.499 0 0111.5 7zm-.82 4.74a6 6 0 111.06-1.06l3.04 3.04a.75.75 0 11-1.06 1.06l-3.04-3.04z"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
placeholder={placeholder}
|
||||
id="input-id"
|
||||
type="text"
|
||||
className={clsx(
|
||||
'FormControl-input',
|
||||
size && `${size}`,
|
||||
inset && 'FormControl--inset',
|
||||
monospace && 'FormControl--monospace'
|
||||
)}
|
||||
disabled={disabled ? 'true' : undefined}
|
||||
invalid={invalid ? 'true' : undefined}
|
||||
/>
|
||||
{showClearButton && (
|
||||
<button
|
||||
id="input-id-clear"
|
||||
className={clsx(
|
||||
'FormControl-input-trailingAction',
|
||||
trailingActionDivider && 'FormControl-input-trailingAction--divider'
|
||||
)}
|
||||
aria-label="Clear"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
data-view-component="true"
|
||||
class="octicon octicon-x"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<input
|
||||
placeholder={placeholder}
|
||||
id="input-id"
|
||||
type="text"
|
||||
disabled={disabled ? 'true' : undefined}
|
||||
className={clsx(
|
||||
'FormControl-input',
|
||||
size && `${size}`,
|
||||
validationStatus && `${validationStatus}`,
|
||||
inset && 'FormControl-inset',
|
||||
monospace && 'FormControl-monospace'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{invalid && (
|
||||
<span className="FormControl-inlineValidation">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
version="1.1"
|
||||
width="12"
|
||||
className="octicon octicon-alert-fill"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.855.708c.5-.896 1.79-.896 2.29 0l4.675 8.351a1.312 1.312 0 01-1.146 1.954H1.33A1.312 1.312 0 01.183 9.058L4.855.708zM7 7V3H5v4h2zm-1 3a1 1 0 100-2 1 1 0 000 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<p id="validation-5e6e1c8a">{validation}</p>
|
||||
</span>
|
||||
)}
|
||||
{caption && (
|
||||
<p className="FormControl-caption" id="caption-ebb67985">
|
||||
{caption}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{focusElement && focusMethod()}
|
||||
</>
|
||||
)
|
||||
|
||||
export const Playground = InputTemplate.bind({})
|
||||
Playground.args = {
|
||||
placeholder: 'Email address',
|
||||
label: 'Enter email address',
|
||||
fullWidth: false,
|
||||
monospace: false,
|
||||
inset: false,
|
||||
disabled: false,
|
||||
focusElement: false,
|
||||
leadingVisual: false,
|
||||
size: 1,
|
||||
caption: 'Caption',
|
||||
showClearButton: false,
|
||||
invalid: false,
|
||||
visuallyHidden: false,
|
||||
validation: '',
|
||||
trailingActionDivider: false,
|
||||
validationStatus: 0
|
||||
}
|
199
docs/src/stories/rails-form-framework/Textarea.stories.jsx
Normal file
199
docs/src/stories/rails-form-framework/Textarea.stories.jsx
Normal file
@ -0,0 +1,199 @@
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default {
|
||||
title: 'Rails Forms/Textarea',
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
decorators: [
|
||||
Story => (
|
||||
<form>
|
||||
<Story />
|
||||
</form>
|
||||
)
|
||||
],
|
||||
excludeStories: ['InputTemplate'],
|
||||
argTypes: {
|
||||
validationStatus: {
|
||||
options: [0, 1, 2, 3], // iterator
|
||||
mapping: ['', 'FormControl-error', 'FormControl-success', 'FormControl-warning'], // values
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: ['undefined', 'error', 'success', 'warning']
|
||||
},
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
fullWidth: {
|
||||
description: 'formerly called Block',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
monospace: {
|
||||
description: 'monospace text',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
inset: {
|
||||
description: 'formerly called Contrast',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
description: 'disabled field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
required: {
|
||||
description: 'required field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
invalid: {
|
||||
description: 'invalid field',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
visuallyHidden: {
|
||||
description: 'visually hide label',
|
||||
control: {type: 'boolean'},
|
||||
table: {
|
||||
category: 'Label'
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
name: 'placeholder',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'Input'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'Label'
|
||||
}
|
||||
},
|
||||
caption: {
|
||||
name: 'caption',
|
||||
type: 'string',
|
||||
description: 'caption',
|
||||
table: {
|
||||
category: 'Caption'
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
description: 'string',
|
||||
table: {
|
||||
category: 'Validation'
|
||||
}
|
||||
},
|
||||
focusElement: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'set focus on element',
|
||||
table: {
|
||||
category: 'Interactive'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const focusMethod = function getFocus() {
|
||||
// find the focusable element
|
||||
var input = document.getElementsByTagName('input')[0]
|
||||
// set focus on element
|
||||
input.focus()
|
||||
}
|
||||
|
||||
export const InputTemplate = ({
|
||||
label,
|
||||
fullWidth,
|
||||
placeholder,
|
||||
inset,
|
||||
disabled,
|
||||
visuallyHidden,
|
||||
monospace,
|
||||
focusElement,
|
||||
invalid,
|
||||
caption,
|
||||
validation,
|
||||
validationStatus
|
||||
}) => (
|
||||
<>
|
||||
<div className={clsx('FormControl', fullWidth && 'FormControl--fullWidth')}>
|
||||
<label htmlFor="input-id" className={clsx('FormControl-label', visuallyHidden && 'sr-only')}>
|
||||
{label}
|
||||
</label>
|
||||
<textarea
|
||||
placeholder={placeholder}
|
||||
id="input-id"
|
||||
disabled={disabled ? 'true' : undefined}
|
||||
className={clsx(
|
||||
'FormControl-textarea',
|
||||
inset && 'FormControl-inset',
|
||||
monospace && 'FormControl-monospace',
|
||||
validationStatus && `${validationStatus}`
|
||||
)}
|
||||
/>
|
||||
{invalid && (
|
||||
<span className="FormControl-inlineValidation">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
version="1.1"
|
||||
width="12"
|
||||
className="octicon octicon-alert-fill"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.855.708c.5-.896 1.79-.896 2.29 0l4.675 8.351a1.312 1.312 0 01-1.146 1.954H1.33A1.312 1.312 0 01.183 9.058L4.855.708zM7 7V3H5v4h2zm-1 3a1 1 0 100-2 1 1 0 000 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<p id="validation-5e6e1c8a">{validation}</p>
|
||||
</span>
|
||||
)}
|
||||
{caption && (
|
||||
<p className="FormControl-caption" id="caption-ebb67985">
|
||||
{caption}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{focusElement && focusMethod()}
|
||||
</>
|
||||
)
|
||||
|
||||
export const Playground = InputTemplate.bind({})
|
||||
Playground.args = {
|
||||
placeholder: 'Email address',
|
||||
label: 'Enter email address',
|
||||
fullWidth: false,
|
||||
monospace: false,
|
||||
inset: false,
|
||||
disabled: false,
|
||||
focusElement: false,
|
||||
caption: 'Caption',
|
||||
invalid: false,
|
||||
visuallyHidden: false,
|
||||
validation: '',
|
||||
validationStatus: 0
|
||||
}
|
@ -6304,6 +6304,13 @@ capture-exit@^2.0.0:
|
||||
dependencies:
|
||||
rsvp "^4.8.4"
|
||||
|
||||
cartesian@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cartesian/-/cartesian-1.0.1.tgz#ae3fc8a63e2ba7e2c4989ce696207457bcae65af"
|
||||
integrity sha512-tR3qKRYpRJ6FXEGuoBwpuCYcwydrk1N2rduy7eWg1Msepi3i5fCxheryw4VBlCqjCbk3Vhjh3eg+IGHtl5H74A==
|
||||
dependencies:
|
||||
xtend "^4.0.1"
|
||||
|
||||
case-sensitive-paths-webpack-plugin@^2.3.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
|
||||
@ -18592,6 +18599,13 @@ storybook-addon-designs@6.2.1:
|
||||
dependencies:
|
||||
"@figspec/react" "^1.0.0"
|
||||
|
||||
storybook-addon-variants@^0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/storybook-addon-variants/-/storybook-addon-variants-0.0.5.tgz#cd2b221999d0044b3a39a150180f1e738bb504ad"
|
||||
integrity sha512-MSQeVcliKCx/w5MiA7mcW5d7GqeL6kN4oSYKbULo4UldXkNUt3AjmjjuMMfVVcF7TdwWjAgmsHAXPo/vBIXyiQ==
|
||||
dependencies:
|
||||
cartesian "^1.0.1"
|
||||
|
||||
storybook-color-picker@2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/storybook-color-picker/-/storybook-color-picker-2.3.1.tgz#d1b6e577708747d2599d8af99e125bea0d96982e"
|
||||
|
@ -41,7 +41,7 @@
|
||||
"storybook": "cd docs && yarn && yarn storybook"
|
||||
},
|
||||
"dependencies": {
|
||||
"@primer/primitives": "^7.8.3"
|
||||
"@primer/primitives": "^7.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "0.4.4",
|
||||
|
@ -96,8 +96,6 @@ button,
|
||||
[role='button'],
|
||||
input[type='radio'],
|
||||
input[type='checkbox'] {
|
||||
transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
|
||||
transition-property: color, background-color, box-shadow, border-color;
|
||||
// fallback :focus state
|
||||
&:focus {
|
||||
@include focusOutline;
|
||||
|
@ -1,27 +1,23 @@
|
||||
// stylelint-disable primer/typography, primer/borders, primer/spacing, primer/box-shadow, max-nesting-depth
|
||||
// stylelint-disable primer/typography, primer/borders, primer/spacing, selector-max-type, selector-max-specificity, selector-no-qualifying-type, max-nesting-depth
|
||||
|
||||
// group label, field, caption and error message
|
||||
.FormGroup {
|
||||
// groups label, field, caption and inline error message
|
||||
.FormControl {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base-size-4, 4px);
|
||||
}
|
||||
|
||||
// fill container
|
||||
.FormGroup--fullWidth {
|
||||
.FormControl--fullWidth {
|
||||
display: flex;
|
||||
|
||||
// stretch field to fill container
|
||||
.FormControl-fieldWrap {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// <label>
|
||||
.FormControl-label {
|
||||
font-size: var(--primer-text-body-size-medium, 14px);
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
line-height: var(--primer-text-body-lineHeight-medium, 20px);
|
||||
line-height: var(--primer-text-body-lineHeight-medium, calc(20 / 14));
|
||||
color: var(--color-fg-default);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@ -30,11 +26,12 @@
|
||||
margin-bottom: 0;
|
||||
font-size: var(--primer-text-caption-size, 12px);
|
||||
font-weight: var(--primer-text-caption-weight, 400);
|
||||
line-height: var(--primer-text-caption-lineHeight, 16px);
|
||||
line-height: var(--primer-text-caption-lineHeight, calc(16 / 12));
|
||||
color: var(--color-fg-muted);
|
||||
}
|
||||
|
||||
.FormControl-validation {
|
||||
// inline validation message
|
||||
.FormControl-inlineValidation {
|
||||
display: flex;
|
||||
font-size: var(--primer-text-caption-size, 12px);
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
@ -44,339 +41,636 @@
|
||||
align-items: center;
|
||||
gap: var(--base-size-4, 4px);
|
||||
|
||||
// stylelint-disable-next-line selector-max-type
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// shared among all form control components (input, select, textarea, checkbox, radio)
|
||||
.FormControl {
|
||||
@mixin Field {
|
||||
background-color: var(--color-canvas-default);
|
||||
border: solid var(--primer-borderWidth-thin, 1px) var(--color-border-default);
|
||||
|
||||
&[disabled] {
|
||||
color: var(--color-primer-fg-disabled);
|
||||
cursor: not-allowed;
|
||||
background-color: var(--color-input-disabled-bg);
|
||||
border-color: var(--color-border-default);
|
||||
-webkit-text-fill-color: var(--color-primer-fg-disabled);
|
||||
opacity: 1;
|
||||
-webkit-text-fill-color: var(--color-primer-fg-disabled);
|
||||
}
|
||||
|
||||
&:not(:focus)[invalid] {
|
||||
border-color: var(--color-danger-emphasis);
|
||||
}
|
||||
|
||||
// <select> and <input>
|
||||
&.FormControl--input,
|
||||
&.FormControl--select {
|
||||
height: var(--primer-control-medium-size, 32px);
|
||||
font-size: var(--primer-text-body-size-medium, 14px);
|
||||
line-height: var(--primer-text-body-lineHeight-medium, 20px);
|
||||
border-radius: var(--primer-borderRadius-medium, 6px);
|
||||
padding-inline: var(--primer-control-medium-paddingInline-condensed, 8px);
|
||||
transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
|
||||
transition-property: color, background-color, box-shadow, border-color;
|
||||
&:not([type='checkbox']):not([type='radio']):focus {
|
||||
@include focusBoxShadowInset;
|
||||
|
||||
&[disabled] {
|
||||
&::placeholder {
|
||||
color: var(--color-primer-fg-disabled);
|
||||
}
|
||||
}
|
||||
// remove fallback :focus if :focus-visible is supported
|
||||
&:not(:focus-visible) {
|
||||
border-color: transparent;
|
||||
|
||||
::placeholder {
|
||||
color: var(--color-fg-subtle);
|
||||
opacity: 1;
|
||||
@include focusBoxShadowInset(1px, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
// default focus state
|
||||
&:focus-visible {
|
||||
@include focusBoxShadowInset;
|
||||
}
|
||||
}
|
||||
|
||||
// TextInput structure
|
||||
// ===================
|
||||
//
|
||||
// .FormControl
|
||||
// ├─ .FormControl-label
|
||||
// │ ├─ .FormControl-input-wrap
|
||||
// │ │ ├─ .FormControl-input-leadingVisualWrap
|
||||
// │ │ │ ├─ .FormControl-input-leadingVisual
|
||||
// │ │ ├─ .FormControl-input
|
||||
// │ │ ├─ .FormControl-input-trailingAction
|
||||
// ├─ .FormControl-inlineValidation
|
||||
// ├─ .FormControl-caption
|
||||
|
||||
// Select structure
|
||||
// ===================
|
||||
//
|
||||
// .FormControl
|
||||
// ├─ .FormControl-label
|
||||
// │ ├─ .FormControl-select-wrap
|
||||
// │ │ ├─ .FormControl-select
|
||||
// ├─ .FormControl-inlineValidation
|
||||
// ├─ .FormControl-caption
|
||||
|
||||
// Textarea structure
|
||||
// ===================
|
||||
//
|
||||
// .FormControl
|
||||
// ├─ .FormControl-label
|
||||
// ├─ .FormControl-textarea
|
||||
// ├─ .FormControl-inlineValidation
|
||||
// ├─ .FormControl-caption
|
||||
|
||||
.FormControl-input,
|
||||
.FormControl-select,
|
||||
.FormControl-textarea {
|
||||
@include Field;
|
||||
|
||||
width: 100%;
|
||||
font-size: var(--primer-text-body-size-medium, 14px);
|
||||
line-height: var(--primer-text-body-lineHeight-medium, calc(20 / 14));
|
||||
border-radius: var(--primer-borderRadius-medium, 6px);
|
||||
transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
|
||||
transition-property: color, background-color, box-shadow, border-color;
|
||||
padding-inline: var(--primer-control-medium-paddingInline-condensed, 8px);
|
||||
padding-block: calc(var(--primer-control-medium-paddingBlock, 6px) - var(--primer-borderWidth-thin, 1px));
|
||||
|
||||
&[disabled] {
|
||||
&::placeholder {
|
||||
color: var(--color-primer-fg-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: var(--color-fg-subtle);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// sizes
|
||||
|
||||
&.FormControl--small {
|
||||
&.FormControl-small {
|
||||
height: var(--primer-control-small-size, 28px);
|
||||
padding-inline: var(--primer-control-small-paddingInline-normal, 8px);
|
||||
padding-block: var(--primer-control-small-paddingBlock, 4px);
|
||||
font-size: var(--primer-text-body-size-small, 12px);
|
||||
}
|
||||
|
||||
&.FormControl--medium {
|
||||
&.FormControl-medium {
|
||||
height: var(--primer-control-medium-size, 32px);
|
||||
}
|
||||
|
||||
&.FormControl--large {
|
||||
&.FormControl-large {
|
||||
height: var(--primer-control-large-size, 40px);
|
||||
padding-inline: var(--primer-control-large-paddingInline-normal, 12px);
|
||||
padding-block: var(--primer-control-large-paddingBlock, 10px);
|
||||
}
|
||||
|
||||
// variants
|
||||
|
||||
&.FormControl-inset {
|
||||
background-color: var(--color-canvas-inset);
|
||||
}
|
||||
|
||||
&.FormControl-monospace {
|
||||
font-family:
|
||||
var(
|
||||
--primer-fontStack-monospace,
|
||||
'ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace'
|
||||
);
|
||||
}
|
||||
|
||||
// validation states
|
||||
|
||||
&.FormControl-error {
|
||||
border-color: var(--color-danger-emphasis);
|
||||
}
|
||||
|
||||
&.FormControl-success {
|
||||
border-color: var(--color-success-emphasis);
|
||||
}
|
||||
|
||||
&.FormControl-warning {
|
||||
border-color: var(--color-attention-emphasis);
|
||||
}
|
||||
}
|
||||
|
||||
// pseudo input styles to allow for visual and action slots
|
||||
// set input styles on -fieldWrap, remove styles from <input>
|
||||
.FormControl-fieldWrap {
|
||||
display: inline-grid;
|
||||
height: var(--primer-control-medium-size, 32px);
|
||||
background-color: var(--color-canvas-default);
|
||||
border-radius: var(--primer-borderRadius-medium, 6px);
|
||||
box-shadow: var(--primer-borderInset-thin, inset 0 0 0 max(1px, 0.0625rem)) var(--color-border-default);
|
||||
grid-template-rows: auto;
|
||||
gap: var(--primer-controlStack-medium-gap-condensed, 8px);
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
padding-inline: var(--primer-control-medium-paddingInline-condensed, 8px);
|
||||
// positioning for leading/trailing items for TextInput
|
||||
.FormControl-input-wrap {
|
||||
position: relative;
|
||||
display: grid;
|
||||
|
||||
.FormControl {
|
||||
padding: unset;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
.FormControl-input-leadingVisualWrap {
|
||||
position: absolute;
|
||||
top: var(--base-size-8, 8px);
|
||||
left: var(--base-size-8, 8px);
|
||||
display: block;
|
||||
width: var(--base-size-16, 16px);
|
||||
height: var(--base-size-16, 16px);
|
||||
color: var(--color-fg-muted);
|
||||
pointer-events: none;
|
||||
|
||||
&:focus-visible,
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
// octicon
|
||||
.FormControl-input-leadingVisual {
|
||||
display: block;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
@include focusBoxShadowInset(2px, var(--color-accent-fg));
|
||||
}
|
||||
// TODO: replace with new Button component
|
||||
.FormControl-input-trailingAction {
|
||||
position: absolute;
|
||||
top: var(--base-size-4, 4px);
|
||||
right: var(--base-size-4, 4px);
|
||||
z-index: 4;
|
||||
display: grid;
|
||||
width: var(--primer-control-xsmall-size, 24px);
|
||||
height: var(--primer-control-xsmall-size, 24px);
|
||||
padding: 0;
|
||||
color: var(--color-fg-muted);
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: var(--primer-borderRadius-small);
|
||||
transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1);
|
||||
transition-property: color, background-color, border-color;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.FormControl-fieldWrap--disabled {
|
||||
color: var(--color-primer-fg-disabled);
|
||||
background-color: var(--color-input-disabled-bg);
|
||||
}
|
||||
svg {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&.FormControl-fieldWrap--invalid:not(:focus-within) {
|
||||
box-shadow: var(--primer-borderInset-thin, inset 0 0 0 max(1px, 0.0625rem)) var(--color-danger-emphasis);
|
||||
&[disabled] {
|
||||
color: var(--color-primer-fg-disabled);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-action-list-item-default-hover-bg);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--color-action-list-item-default-active-bg);
|
||||
}
|
||||
|
||||
// show vertical divider line between field and button
|
||||
&.FormControl-input-trailingAction--divider {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: calc((var(--primer-control-xsmall-size) - var(--base-size-16)) / 2);
|
||||
left: calc(var(--base-size-4, 4px) * -1);
|
||||
display: block;
|
||||
width: var(--primer-borderWidth-thin);
|
||||
height: var(--base-size-16);
|
||||
content: '';
|
||||
background: var(--color-border-default);
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
@include minTouchTarget(var(--primer-control-medium-size, 32px), var(--primer-control-medium-size, 32px));
|
||||
|
||||
@media (pointer: coarse) {
|
||||
min-width: var(--primer-control-minTarget-coarse, 44px);
|
||||
min-height: var(--primer-control-minTarget-coarse, 44px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if leadingVisual is present
|
||||
&.FormControl-fieldWrap--input-leadingVisual {
|
||||
grid-template-columns: var(--base-size-16, 16px) minmax(0, auto);
|
||||
|
||||
.FormControl--input {
|
||||
padding-inline-start: calc(var(--base-size-16, 16px) + var(--primer-control-medium-paddingInline-condensed, 8px));
|
||||
}
|
||||
|
||||
// if leadingVisual and trailingAction are present
|
||||
&.FormControl-fieldWrap--input-trailingAction {
|
||||
grid-template-columns: var(--base-size-16, 16px) minmax(0, auto) min-content;
|
||||
|
||||
.FormControl--input-trailingAction {
|
||||
grid-column: 3 / 4;
|
||||
}
|
||||
/*
|
||||
┌──32px──┬────────────────────┐
|
||||
╎ ┌───┐ ┌────────────────┐ ╎
|
||||
╎ 16px 16px ╎
|
||||
╎ └───┘ └────────────────┘ ╎
|
||||
└───────8px───────────────────┘
|
||||
*/
|
||||
&.FormControl-input-wrap--leadingVisual {
|
||||
.FormControl-input {
|
||||
padding-inline-start:
|
||||
calc(
|
||||
var(--primer-control-medium-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
|
||||
var(--primer-control-medium-gap, 8px)
|
||||
); /* 32px */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌──────────────────┬──32px──┐
|
||||
╎ ┌──────────────┐ ┌────┐ ╎
|
||||
╎ 24px 24px ╎
|
||||
╎ └──────────────┘ └────┘ ╎
|
||||
└──────────────────┴────────┘
|
||||
*/
|
||||
// if trailingAction is present
|
||||
&.FormControl-fieldWrap--input-trailingAction {
|
||||
grid-template-columns: minmax(0, auto) min-content;
|
||||
&.FormControl-input-wrap--trailingAction {
|
||||
.FormControl-input {
|
||||
padding-inline-end:
|
||||
calc(
|
||||
var(--primer-control-medium-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
|
||||
var(--primer-control-medium-gap, 8px)
|
||||
); /* 32px */
|
||||
}
|
||||
|
||||
/*
|
||||
32px + 1px border
|
||||
┌──────────────────┬──33px──┐
|
||||
╎ ┌──────────────┐ ┌────┐ ╎
|
||||
╎ 24px 24px ╎
|
||||
╎ └──────────────┘ └────┘ ╎
|
||||
└──────────────────┴────────┘
|
||||
*/
|
||||
// if trailingAction divider is present, add 1px padding to accomodate input field text
|
||||
// can be refactored to has(.FormControl-input-trailingAction--divider)
|
||||
&.FormControl-input-wrap-trailingAction--divider {
|
||||
.FormControl-input {
|
||||
padding-inline-end:
|
||||
calc(
|
||||
var(--primer-control-medium-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
|
||||
var(--primer-control-medium-gap, 8px) + var(--primer-borderWidth-thin, 1px)
|
||||
); /* 33px */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.FormControl-fieldWrap--input {
|
||||
grid-template-rows: max-content;
|
||||
|
||||
.FormControl--input-leadingVisual {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 1;
|
||||
// size modifications can be refactored with :has() - FormControl-input-wrap:has(.FormControl-large)
|
||||
// sizes
|
||||
&.FormControl-small {
|
||||
.FormControl-input-leadingVisualWrap {
|
||||
top: calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem); /* 6px */
|
||||
left: calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem); /* 6px */
|
||||
}
|
||||
|
||||
// <input> spans entire grid
|
||||
.FormControl--input {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
// TODO: replace with new Button component
|
||||
// trailingAction will auto fill last grid slot
|
||||
.FormControl--input-trailingAction {
|
||||
display: grid;
|
||||
width: calc(var(--primer-control-medium-size, 32px) - var(--base-size-4, 4px));
|
||||
height: calc(var(--primer-control-medium-size, 32px) - var(--base-size-4, 4px));
|
||||
padding: 0;
|
||||
// optically align the icon with field padding
|
||||
margin-right: calc(calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem) * -1);
|
||||
color: var(--color-fg-muted);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
border: solid var(--primer-borderWidth-thin, 1px) transparent;
|
||||
border-radius: var(--primer-borderRadius-medium, 6px);
|
||||
transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1);
|
||||
transition-property: color, background-color, border-color;
|
||||
grid-row: 1;
|
||||
place-content: center;
|
||||
place-self: center end;
|
||||
grid-column: 2 / 3;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-btn-hover-bg);
|
||||
border: solid var(--primer-borderWidth-thin, 1px) var(--color-btn-hover-bg);
|
||||
/*
|
||||
┌──────────────────┬──28px──┐
|
||||
╎ ┌──────────────┐ ┌────┐ ╎
|
||||
╎ 20px 20px ╎
|
||||
╎ └──────────────┘ └────┘ ╎
|
||||
└──────────────────┴────────┘
|
||||
*/
|
||||
&.FormControl-input-wrap--trailingAction {
|
||||
.FormControl-input.FormControl-small {
|
||||
padding-inline-end:
|
||||
calc(
|
||||
var(--primer-control-small-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
|
||||
var(--primer-control-small-gap, 8px)
|
||||
); /* 28px */
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
color: var(--color-primer-fg-disabled);
|
||||
pointer-events: none;
|
||||
/*
|
||||
28px + 1px border
|
||||
┌──────────────────┬──29px──┐
|
||||
╎ ┌──────────────┐ ┌────┐ ╎
|
||||
╎ 20px 20px ╎
|
||||
╎ └──────────────┘ └────┘ ╎
|
||||
└──────────────────┴────────┘
|
||||
*/
|
||||
&.FormControl-input-wrap-trailingAction--divider {
|
||||
.FormControl-input.FormControl-small {
|
||||
padding-inline-end:
|
||||
calc(
|
||||
var(--primer-control-small-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
|
||||
var(--primer-control-small-gap, 8px) + var(--primer-borderWidth-thin, 1px)
|
||||
); /* 29px */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sizes
|
||||
.FormControl-input-trailingAction {
|
||||
width: calc(var(--primer-control-small-size, 28px) - var(--base-size-8, 8px));
|
||||
height: calc(var(--primer-control-small-size, 28px) - var(--base-size-8, 8px));
|
||||
|
||||
// these selectors can be refactored to use :has()
|
||||
&.FormControl--small {
|
||||
&::before {
|
||||
top: calc((var(--primer-control-xsmall-size) - var(--base-size-16)) / 4); /* 2px */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.FormControl-large {
|
||||
.FormControl-input-leadingVisualWrap {
|
||||
top: var(--primer-control-medium-paddingInline-normal, 12px);
|
||||
left: var(--primer-control-medium-paddingInline-normal, 12px);
|
||||
}
|
||||
|
||||
/*
|
||||
┌──36px──┬───12px padding──────┐
|
||||
╎ ┌───┐ ┌────────────────┐ ╎
|
||||
╎ 16px 16px ╎
|
||||
╎ └───┘ └────────────────┘ ╎
|
||||
└12px───8px───────────────────┘
|
||||
*/
|
||||
&.FormControl-input-wrap--leadingVisual {
|
||||
.FormControl-input.FormControl-large {
|
||||
padding-inline-start:
|
||||
calc(
|
||||
var(--primer-control-large-paddingInline-normal, 12px) + var(--base-size-16, 16px) +
|
||||
var(--primer-control-large-gap, 8px)
|
||||
); /* 36px */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌──────────────────┬──36px──┐
|
||||
╎ ┌──────────────┐ ┌────┐ ╎
|
||||
╎ 28px 28px ╎
|
||||
╎ └──────────────┘ └────┘ ╎
|
||||
└──────────────────┴────────┘
|
||||
*/
|
||||
&.FormControl-input-wrap--trailingAction {
|
||||
.FormControl-input.FormControl-large {
|
||||
padding-inline-end:
|
||||
calc(
|
||||
var(--primer-control-large-paddingInline-normal, 12px) + var(--base-size-16, 16px) +
|
||||
var(--primer-control-large-gap, 8px)
|
||||
); /* 36px */
|
||||
}
|
||||
|
||||
/*
|
||||
┌──────────────────┬──37px──┐
|
||||
╎ ┌──────────────┐ ┌────┐ ╎
|
||||
╎ 28px 28px ╎
|
||||
╎ └──────────────┘ └────┘ ╎
|
||||
└──────────────────┴────────┘
|
||||
*/
|
||||
&.FormControl-input-wrap-trailingAction--divider {
|
||||
.FormControl-input.FormControl-large {
|
||||
padding-inline-end:
|
||||
calc(
|
||||
var(--primer-control-large-paddingInline-normal, 12px) + var(--base-size-16, 16px) +
|
||||
var(--primer-control-large-gap, 8px) + var(--primer-borderWidth-thin, 1px)
|
||||
); /* 37px */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.FormControl-input-trailingAction {
|
||||
top: calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem); /* 6px */
|
||||
right: calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem); /* 6px */
|
||||
width: var(--primer-control-small-size, 28px);
|
||||
height: var(--primer-control-small-size, 28px);
|
||||
font-size: var(--primer-text-body-size-small, 12px);
|
||||
|
||||
.FormControl--input-trailingAction {
|
||||
width: calc(var(--primer-control-small-size, 28px) - var(--base-size-8, 8px));
|
||||
height: calc(var(--primer-control-small-size, 28px) - var(--base-size-8, 8px));
|
||||
}
|
||||
}
|
||||
|
||||
&.FormControl--medium {
|
||||
height: var(--primer-control-medium-size, 32px);
|
||||
|
||||
.FormControl--input-trailingAction {
|
||||
width: calc(var(--primer-control-medium-size, 32px) - var(--base-size-8, 8px));
|
||||
height: calc(var(--primer-control-medium-size, 32px) - var(--base-size-8, 8px));
|
||||
}
|
||||
}
|
||||
|
||||
&.FormControl--large {
|
||||
height: var(--primer-control-large-size, 40px);
|
||||
padding-inline: var(--primer-control-large-paddingInline-normal, 12px);
|
||||
|
||||
.FormControl--input-trailingAction {
|
||||
width: calc(var(--primer-control-large-size, 40px) - var(--base-size-8, 8px));
|
||||
height: calc(var(--primer-control-large-size, 40px) - var(--base-size-8, 8px));
|
||||
margin-right: calc(calc(var(--primer-control-medium-paddingInline-normal, 12px) - 0.125rem) * -1);
|
||||
&::before {
|
||||
top: unset;
|
||||
height: var(--base-size-20);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.FormControl-fieldWrap--select {
|
||||
.FormControl-select-wrap {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, auto) var(--base-size-16, 16px);
|
||||
padding-inline-end: var(--primer-control-medium-paddingInline-condensed, 8px);
|
||||
|
||||
// mask allows for background-color to respect themes
|
||||
&::after {
|
||||
width: var(--base-size-16, 16px);
|
||||
height: var(--base-size-16, 16px);
|
||||
padding-right: var(--base-size-4, 4px);
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
background-color: var(--color-fg-muted);
|
||||
mask: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzU4NjA2OSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNC40MjcgOS40MjdsMy4zOTYgMy4zOTZhLjI1MS4yNTEgMCAwMC4zNTQgMGwzLjM5Ni0zLjM5NkEuMjUuMjUgMCAwMDExLjM5NiA5SDQuNjA0YS4yNS4yNSAwIDAwLS4xNzcuNDI3ek00LjQyMyA2LjQ3TDcuODIgMy4wNzJhLjI1LjI1IDAgMDEuMzU0IDBMMTEuNTcgNi40N2EuMjUuMjUgMCAwMS0uMTc3LjQyN0g0LjZhLjI1LjI1IDAgMDEtLjE3Ny0uNDI3eiIgLz48L3N2Zz4=');
|
||||
mask-size: cover;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
place-self: center end;
|
||||
}
|
||||
|
||||
// spans entire grid below mask
|
||||
.FormControl {
|
||||
.FormControl-select {
|
||||
grid-column: 1/-1;
|
||||
grid-row: 1;
|
||||
appearance: none;
|
||||
padding-right: var(--base-size-16, 16px);
|
||||
}
|
||||
}
|
||||
|
||||
.FormGroup--checkbox {
|
||||
display: inline-grid;
|
||||
grid-template-areas: 'field label' '. caption';
|
||||
gap: var(--base-size-8, 8px);
|
||||
|
||||
.FormControl-label {
|
||||
grid-area: label;
|
||||
}
|
||||
|
||||
.FormControl-caption {
|
||||
grid-area: caption;
|
||||
padding-right: var(--base-size-20, 20px);
|
||||
}
|
||||
}
|
||||
|
||||
// checkbox + radio specific styles
|
||||
|
||||
.FormControl--checkbox,
|
||||
.FormControl--radio {
|
||||
position: absolute;
|
||||
// Checkbox + Radio structure
|
||||
// ===================
|
||||
//
|
||||
// .FormControl-radio-wrap
|
||||
// ├─ .FormControl-radio
|
||||
// ├─ .FormControl-radio-labelWrap
|
||||
// │ ├─ .FormControl-label
|
||||
// │ ├─ .FormControl-caption
|
||||
|
||||
.FormControl-checkbox-wrap,
|
||||
.FormControl-radio-wrap {
|
||||
display: inline-grid;
|
||||
grid-template-columns: min-content auto;
|
||||
gap: var(--base-size-8, 8px);
|
||||
|
||||
.FormControl-checkbox-labelWrap,
|
||||
.FormControl-radio-labelWrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base-size-4, 4px);
|
||||
}
|
||||
|
||||
.FormControl-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// these selectors are temporary to override base.scss
|
||||
// once Field styles are widely adopted, we can adjust this and the global base styles
|
||||
input[type='checkbox'].FormControl-checkbox,
|
||||
input[type='radio'].FormControl-radio {
|
||||
@include Field;
|
||||
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: var(--base-size-16, 16px);
|
||||
height: var(--base-size-16, 16px);
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
margin-top: 0.125rem; // 2px to center align with label (20px line-height)
|
||||
cursor: pointer;
|
||||
border: solid var(--primer-borderWidth-thin, 1px) var(--color-border-default);
|
||||
border-radius: var(--primer-borderRadius-small, 3px);
|
||||
transition: background-color, border-color 80ms cubic-bezier(0.33, 1, 0.68, 1); // checked -> unchecked - add 120ms delay to fully see animation-out
|
||||
appearance: none;
|
||||
place-content: center;
|
||||
|
||||
&::before {
|
||||
width: var(--base-size-16, 16px);
|
||||
height: var(--base-size-16, 16px);
|
||||
visibility: hidden;
|
||||
content: '';
|
||||
background-color: var(--color-fg-on-emphasis);
|
||||
transition: visibility 0s linear 230ms;
|
||||
clip-path: inset(var(--base-size-16, 16px) 0 0 0);
|
||||
mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOSIgdmlld0JveD0iMCAwIDEyIDkiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTEuNzgwMyAwLjIxOTYyNUMxMS45MjEgMC4zNjA0MjcgMTIgMC41NTEzMDUgMTIgMC43NTAzMTNDMTIgMC45NDkzMjEgMTEuOTIxIDEuMTQwMTkgMTEuNzgwMyAxLjI4MUw0LjUxODYgOC41NDA0MkM0LjM3Nzc1IDguNjgxIDQuMTg2ODIgOC43NiAzLjk4Nzc0IDguNzZDMy43ODg2NyA4Ljc2IDMuNTk3NzMgOC42ODEgMy40NTY4OSA4LjU0MDQyTDAuMjAxNjIyIDUuMjg2MkMwLjA2ODkyNzcgNS4xNDM4MyAtMC4wMDMzMDkwNSA0Ljk1NTU1IDAuMDAwMTE2NDkzIDQuNzYwOThDMC4wMDM1NTIwNSA0LjU2NjQzIDAuMDgyMzg5NCA0LjM4MDgxIDAuMjIwMDMyIDQuMjQzMjFDMC4zNTc2NjUgNC4xMDU2MiAwLjU0MzM1NSA0LjAyNjgxIDAuNzM3OTcgNC4wMjMzOEMwLjkzMjU4NCA0LjAxOTk0IDEuMTIwOTMgNC4wOTIxNyAxLjI2MzM0IDQuMjI0ODJMMy45ODc3NCA2Ljk0ODM1TDEwLjcxODYgMC4yMTk2MjVDMTAuODU5NSAwLjA3ODk5MjMgMTEuMDUwNCAwIDExLjI0OTUgMEMxMS40NDg1IDAgMTEuNjM5NSAwLjA3ODk5MjMgMTEuNzgwMyAwLjIxOTYyNVoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo='); // octicon checkmark image
|
||||
mask-size: 75%;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
|
||||
@media screen and (prefers-reduced-motion: no-preference) {
|
||||
animation: checkmarkOut 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards; // slightly snappier animation out
|
||||
}
|
||||
}
|
||||
|
||||
// extend touch target
|
||||
&::after {
|
||||
@include minTouchTarget(var(--primer-control-medium-size, 32px), var(--primer-control-medium-size, 32px));
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
~ .FormControl-checkbox-labelWrap,
|
||||
~ .FormControl-radio-labelWrap {
|
||||
.FormControl-label {
|
||||
color: var(--color-primer-fg-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:checked {
|
||||
+ .FormControl--checkbox-svg {
|
||||
.FormControl--checkbox-background {
|
||||
fill: var(--color-accent-fg);
|
||||
stroke: var(--color-accent-fg);
|
||||
background: var(--color-accent-fg);
|
||||
border-color: var(--color-accent-fg);
|
||||
transition: background-color, border-color 80ms cubic-bezier(0.32, 0, 0.67, 0) 0ms; // unchecked -> checked
|
||||
|
||||
&::before {
|
||||
visibility: visible;
|
||||
transition: visibility 0s linear 0s;
|
||||
|
||||
@media screen and (prefers-reduced-motion: no-preference) {
|
||||
animation: checkmarkIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;
|
||||
}
|
||||
}
|
||||
|
||||
.FormControl--checkbox-check {
|
||||
fill: var(--color-fg-on-emphasis);
|
||||
visibility: visible;
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
background-color: var(--color-primer-fg-disabled);
|
||||
border-color: var(--color-primer-fg-disabled);
|
||||
opacity: 1;
|
||||
|
||||
@media screen and (prefers-reduced-motion: no-preference) {
|
||||
animation: checkmarkIn 200ms cubic-bezier(0.11, 0, 0.5, 0) forwards;
|
||||
}
|
||||
&::before {
|
||||
background-color: var(--color-fg-on-emphasis);
|
||||
}
|
||||
}
|
||||
|
||||
// Windows High Contrast mode
|
||||
// stylelint-disable primer/colors
|
||||
@media (forced-colors: active) {
|
||||
background-color: CanvasText;
|
||||
border-color: CanvasText;
|
||||
}
|
||||
// stylelint-enable primer/colors
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:indeterminate {
|
||||
&::before {
|
||||
mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMiIgdmlld0JveD0iMCAwIDEwIDIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMCAxQzAgMC40NDc3MTUgMC40NDc3MTUgMCAxIDBIOUM5LjU1MjI5IDAgMTAgMC40NDc3MTUgMTAgMUMxMCAxLjU1MjI4IDkuNTUyMjkgMiA5IDJIMUMwLjQ0NzcxNSAyIDAgMS41NTIyOCAwIDFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio'].FormControl-radio {
|
||||
border-radius: var(--primer-borderRadius-full, 100vh);
|
||||
|
||||
&::before {
|
||||
clip-path: circle(0%);
|
||||
mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUgOS4zNzVDNy40MTYyMiA5LjM3NSA5LjM3NSA3LjQxNjIyIDkuMzc1IDVDOS4zNzUgMi41ODM3NiA3LjQxNjIyIDAuNjI1IDUgMC42MjVDMi41ODM3NiAwLjYyNSAwLjYyNSAyLjU4Mzc2IDAuNjI1IDVDMC42MjUgNy40MTYyMiAyLjU4Mzc2IDkuMzc1IDUgOS4zNzVaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'); // checked circle image
|
||||
mask-size: 65%;
|
||||
|
||||
@media screen and (prefers-reduced-motion: no-preference) {
|
||||
animation: radioOut 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards; // slightly snappier animation out
|
||||
}
|
||||
}
|
||||
|
||||
&:checked {
|
||||
&::before {
|
||||
@media screen and (prefers-reduced-motion: no-preference) {
|
||||
animation: radioIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:indeterminate {
|
||||
+ .FormControl--checkbox-svg {
|
||||
.FormControl--checkbox-background {
|
||||
fill: var(--color-accent-fg);
|
||||
stroke: var(--color-accent-fg);
|
||||
}
|
||||
|
||||
.FormControl--checkbox-indeterminate {
|
||||
fill: var(--color-fg-on-emphasis);
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes checkmarkIn {
|
||||
from {
|
||||
clip-path: inset(16px 0 0 0);
|
||||
}
|
||||
|
||||
to {
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes checkmarkOut {
|
||||
from {
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
|
||||
to {
|
||||
clip-path: inset(16px 0 0 0);
|
||||
&::before {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.FormControl--checkbox-svg {
|
||||
width: var(--base-size-16, 16px);
|
||||
height: var(--base-size-16, 16px);
|
||||
margin-top: 0.125rem;
|
||||
cursor: pointer;
|
||||
|
||||
.FormControl--checkbox-background {
|
||||
fill: var(--color-canvas-default);
|
||||
stroke: var(--color-border-default);
|
||||
@keyframes checkmarkIn {
|
||||
from {
|
||||
clip-path: inset(var(--base-size-16, 16px) 0 0 0);
|
||||
}
|
||||
|
||||
.FormControl--checkbox-check {
|
||||
visibility: hidden;
|
||||
transition: visibility 0s linear 200ms;
|
||||
clip-path: inset(16px 0 0 0);
|
||||
|
||||
@media screen and (prefers-reduced-motion: no-preference) {
|
||||
animation: checkmarkOut 200ms cubic-bezier(0.11, 0, 0.5, 0) forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.FormControl--checkbox-indeterminate {
|
||||
fill: transparent;
|
||||
visibility: hidden;
|
||||
to {
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes checkmarkOut {
|
||||
from {
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
|
||||
to {
|
||||
clip-path: inset(var(--base-size-16, 16px) 0 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes radioIn {
|
||||
from {
|
||||
clip-path: circle(0%);
|
||||
}
|
||||
|
||||
to {
|
||||
clip-path: circle(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes radioOut {
|
||||
from {
|
||||
clip-path: circle(100%);
|
||||
}
|
||||
|
||||
to {
|
||||
clip-path: circle(0%);
|
||||
}
|
||||
}
|
||||
|
@ -1076,10 +1076,10 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@primer/primitives@^7.8.3":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@primer/primitives/-/primitives-7.8.3.tgz#de7e03492cf977e99f2417490d76421db9715e9f"
|
||||
integrity sha512-04ZwfJhpZ0QFwDrJfCuLX6iOh0BflWDTvuoRA80lQH9xk0RtIg16INbruwwtnbSgnKKXXRSykRRJ5BbxnqufRA==
|
||||
"@primer/primitives@^7.8.4":
|
||||
version "7.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@primer/primitives/-/primitives-7.8.4.tgz#484486ee47050f18b2e82c33e9df247a5886c82a"
|
||||
integrity sha512-cXmnhKBvrwbP3FYR9oxNYx3s8y2svsQLbDNZuoGcsZJLQ6RD3HfQ9ZtXgbyTbTYTyfPvkyd0pkQLI7tRJSc5kg==
|
||||
|
||||
"@primer/stylelint-config@^12.4.0":
|
||||
version "12.6.1"
|
||||
|
Loading…
Reference in New Issue
Block a user