@sanity/ui 5.0.0-alpha.15 → 5.0.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +96 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +340 -83
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/avatar/Avatar.tsx +50 -0
- package/src/components/avatar/avatar.css +51 -0
- package/src/components/avatar/avatar.props.ts +57 -0
- package/src/components/avatar-counter/AvatarCounter.tsx +38 -0
- package/src/components/avatar-counter/avatar-counter.css +8 -0
- package/src/components/avatar-counter/avatarCounter.props.ts +44 -0
- package/src/components/avatar-stack/AvatarStack.tsx +60 -0
- package/src/components/avatar-stack/avatar-stack.css +16 -0
- package/src/components/avatar-stack/avatarStack.props.ts +20 -0
- package/src/components/button/button.css +2 -2
- package/src/components/checkbox/Checkbox.tsx +3 -3
- package/src/components/checkbox/checkbox.props.ts +2 -2
- package/src/components/hotkeys/Hotkeys.tsx +29 -0
- package/src/components/hotkeys/hotkeys.css +3 -0
- package/src/components/hotkeys/hotkeys.props.ts +15 -0
- package/src/components/index.css +6 -0
- package/src/components/kbd/KBD.tsx +33 -0
- package/src/components/kbd/kbd.css +6 -0
- package/src/components/kbd/kbd.props.ts +20 -0
- package/src/components/radio/Radio.tsx +3 -3
- package/src/components/radio/radio.props.ts +2 -2
- package/src/components/select/select.props.ts +1 -0
- package/src/components/switch/Switch.tsx +3 -3
- package/src/components/switch/switch.props.ts +2 -2
- package/src/components/text-area/TextArea.tsx +32 -0
- package/src/components/text-area/text-area.css +56 -0
- package/src/components/text-area/textArea.props.ts +35 -0
- package/src/components/text-input/TextInput.tsx +1 -1
- package/src/components/text-input/text-input.css +9 -3
- package/src/components/text-input/textInput.props.ts +1 -0
- package/src/css/classes/dynamic/height.css +2 -0
- package/src/css/classes/local/field-sizing.css +3 -0
- package/src/css/classes/local/index.css +1 -0
- package/src/index.ts +7 -1
- package/src/types/Avatar.ts +14 -0
- package/src/types/Eyebrow.ts +1 -1
- package/src/version.ts +2 -2
package/package.json
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import {useState} from 'react'
|
|
3
|
+
|
|
4
|
+
import {getProps} from '../../utils/getProps'
|
|
5
|
+
import {suffixClassName} from '../../utils/suffixClassName'
|
|
6
|
+
import {Text} from '../text/Text'
|
|
7
|
+
import {type AvatarProps, avatarProps} from './avatar.props'
|
|
8
|
+
|
|
9
|
+
const avatarClassName = suffixClassName('sui-Avatar')
|
|
10
|
+
|
|
11
|
+
/** @public */
|
|
12
|
+
export function Avatar({color = 'gray', size = 1, ...props}: AvatarProps) {
|
|
13
|
+
const {
|
|
14
|
+
className,
|
|
15
|
+
style,
|
|
16
|
+
'aria-label': ariaLabel,
|
|
17
|
+
name,
|
|
18
|
+
src,
|
|
19
|
+
...rest
|
|
20
|
+
} = getProps({color, size, ...props}, avatarProps)
|
|
21
|
+
const [error, setError] = useState(false)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<figure
|
|
25
|
+
className={clsx(
|
|
26
|
+
avatarClassName,
|
|
27
|
+
'sui-display-inline-flex sui-align-items-center sui-justify-content-center sui-radius-full sui-position-relative',
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
style={style}
|
|
31
|
+
data-ui="Avatar"
|
|
32
|
+
{...rest}
|
|
33
|
+
>
|
|
34
|
+
{src && !error ? (
|
|
35
|
+
<img
|
|
36
|
+
className="sui-radius-full sui-width-full sui-height-full"
|
|
37
|
+
src={src}
|
|
38
|
+
alt={ariaLabel || name}
|
|
39
|
+
onError={() => setError(true)}
|
|
40
|
+
/>
|
|
41
|
+
) : (
|
|
42
|
+
<Text role="img" weight="semibold" aria-label={ariaLabel || name}>
|
|
43
|
+
{name.slice(0, 1)}
|
|
44
|
+
</Text>
|
|
45
|
+
)}
|
|
46
|
+
</figure>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type {AvatarProps}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.sui-Avatar {
|
|
2
|
+
border: 1px solid transparent;
|
|
3
|
+
aspect-ratio: 1;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.sui-Avatar img {
|
|
7
|
+
border: 1px solid light-dark(var(--white), var(--black));
|
|
8
|
+
object-fit: cover;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sui-Avatar .sui-Text {
|
|
12
|
+
color: light-dark(var(--white), var(--black));
|
|
13
|
+
font-size: inherit;
|
|
14
|
+
text-transform: uppercase;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sui-Avatar.sui-avatar-gray {
|
|
18
|
+
background-color: light-dark(var(--gray-600), var(--gray-400));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.sui-Avatar.sui-avatar-blue {
|
|
22
|
+
background-color: light-dark(var(--blue-600), var(--blue-400));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.sui-Avatar.sui-avatar-purple {
|
|
26
|
+
background-color: light-dark(var(--purple-600), var(--purple-400));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sui-Avatar.sui-avatar-magenta {
|
|
30
|
+
background-color: light-dark(var(--magenta-600), var(--magenta-400));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.sui-Avatar.sui-avatar-red {
|
|
34
|
+
background-color: light-dark(var(--red-600), var(--red-400));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.sui-Avatar.sui-avatar-orange {
|
|
38
|
+
background-color: light-dark(var(--orange-600), var(--orange-400));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sui-Avatar.sui-avatar-yellow {
|
|
42
|
+
background-color: light-dark(var(--yellow-600), var(--yellow-400));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sui-Avatar.sui-avatar-green {
|
|
46
|
+
background-color: light-dark(var(--green-600), var(--green-400));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sui-Avatar.sui-avatar-cyan {
|
|
50
|
+
background-color: light-dark(var(--cyan-600), var(--cyan-400));
|
|
51
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {heightProps} from '../../props/height'
|
|
2
|
+
import {AVATAR_COLOR, AVATAR_SIZE, type AvatarColor, type AvatarSize} from '../../types/Avatar'
|
|
3
|
+
import {type PropDef} from '../../types/PropDef'
|
|
4
|
+
import type {Responsive} from '../../types/Responsive'
|
|
5
|
+
import {TEXT_SIZE} from '../../types/Text'
|
|
6
|
+
|
|
7
|
+
/** @public */
|
|
8
|
+
export interface AvatarProps extends React.ComponentProps<'figure'> {
|
|
9
|
+
/** Avatar color */
|
|
10
|
+
color?: AvatarColor
|
|
11
|
+
/** Avatar name, only first letter will be displayed */
|
|
12
|
+
name: string
|
|
13
|
+
/** Composite prop for setting height and font size */
|
|
14
|
+
size?: Responsive<AvatarSize>
|
|
15
|
+
/** Avatar image src */
|
|
16
|
+
src?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const avatarProps: Record<string, PropDef> = {
|
|
20
|
+
color: {
|
|
21
|
+
type: 'union',
|
|
22
|
+
className: 'avatar',
|
|
23
|
+
values: AVATAR_COLOR,
|
|
24
|
+
},
|
|
25
|
+
name: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
},
|
|
28
|
+
size: {
|
|
29
|
+
type: 'composite',
|
|
30
|
+
values: AVATAR_SIZE,
|
|
31
|
+
composition: {
|
|
32
|
+
size: {
|
|
33
|
+
propDef: {
|
|
34
|
+
type: 'union',
|
|
35
|
+
className: 'text-body',
|
|
36
|
+
values: TEXT_SIZE,
|
|
37
|
+
},
|
|
38
|
+
mapping: {
|
|
39
|
+
0: 0,
|
|
40
|
+
1: 1,
|
|
41
|
+
2: 2,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
height: {
|
|
45
|
+
propDef: heightProps['height'] as PropDef,
|
|
46
|
+
mapping: {
|
|
47
|
+
0: '19px',
|
|
48
|
+
1: '25px',
|
|
49
|
+
2: '33px',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
src: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
},
|
|
57
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
|
|
3
|
+
import {getProps} from '../../utils/getProps'
|
|
4
|
+
import {suffixClassName} from '../../utils/suffixClassName'
|
|
5
|
+
import {Text} from '../text/Text'
|
|
6
|
+
import {type AvatarCounterProps, avatarCounterProps} from './avatarCounter.props'
|
|
7
|
+
|
|
8
|
+
const avatarCounterClassName = suffixClassName('sui-AvatarCounter')
|
|
9
|
+
|
|
10
|
+
/** @public */
|
|
11
|
+
export function AvatarCounter({size = 1, ...props}: AvatarCounterProps) {
|
|
12
|
+
const {
|
|
13
|
+
className,
|
|
14
|
+
style,
|
|
15
|
+
'aria-label': ariaLabel,
|
|
16
|
+
count,
|
|
17
|
+
...rest
|
|
18
|
+
} = getProps({size, ...props}, avatarCounterProps)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<figure
|
|
22
|
+
className={clsx(
|
|
23
|
+
avatarCounterClassName,
|
|
24
|
+
'sui-display-inline-flex sui-align-items-center sui-justify-content-center sui-radius-full sui-border',
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
style={style}
|
|
28
|
+
data-ui="AvatarCounter"
|
|
29
|
+
{...rest}
|
|
30
|
+
>
|
|
31
|
+
<Text role="img" weight="semibold" aria-label={ariaLabel || `+ ${count} more`}>
|
|
32
|
+
{count}
|
|
33
|
+
</Text>
|
|
34
|
+
</figure>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type {AvatarCounterProps}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {heightProps} from '../../props/height'
|
|
2
|
+
import {AVATAR_SIZE, type AvatarSize} from '../../types/Avatar'
|
|
3
|
+
import {type PropDef} from '../../types/PropDef'
|
|
4
|
+
import {TEXT_SIZE} from '../../types/Text'
|
|
5
|
+
|
|
6
|
+
/** @public */
|
|
7
|
+
export interface AvatarCounterProps extends React.ComponentProps<'figure'> {
|
|
8
|
+
/** Count */
|
|
9
|
+
count: number
|
|
10
|
+
/** Composite prop for setting height and font size */
|
|
11
|
+
size?: AvatarSize
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const avatarCounterProps: Record<string, PropDef> = {
|
|
15
|
+
count: {
|
|
16
|
+
type: 'number',
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
type: 'composite',
|
|
20
|
+
values: AVATAR_SIZE,
|
|
21
|
+
composition: {
|
|
22
|
+
size: {
|
|
23
|
+
propDef: {
|
|
24
|
+
type: 'union',
|
|
25
|
+
className: 'text-body',
|
|
26
|
+
values: TEXT_SIZE,
|
|
27
|
+
},
|
|
28
|
+
mapping: {
|
|
29
|
+
0: 0,
|
|
30
|
+
1: 1,
|
|
31
|
+
2: 2,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
height: {
|
|
35
|
+
propDef: heightProps['height'] as PropDef,
|
|
36
|
+
mapping: {
|
|
37
|
+
0: '19px',
|
|
38
|
+
1: '25px',
|
|
39
|
+
2: '33px',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import {Children, cloneElement, isValidElement, type ReactElement} from 'react'
|
|
3
|
+
|
|
4
|
+
import {getProps} from '../../utils/getProps'
|
|
5
|
+
import {suffixClassName} from '../../utils/suffixClassName'
|
|
6
|
+
import {AvatarCounter} from '../avatar-counter/AvatarCounter'
|
|
7
|
+
import type {AvatarProps} from '../avatar/avatar.props'
|
|
8
|
+
import {type AvatarStackProps, avatarStackProps} from './avatarStack.props'
|
|
9
|
+
|
|
10
|
+
const avatarStackClassName = suffixClassName('sui-AvatarStack')
|
|
11
|
+
|
|
12
|
+
/** @public */
|
|
13
|
+
export function AvatarStack({size = 1, ...props}: AvatarStackProps) {
|
|
14
|
+
const {
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
style,
|
|
18
|
+
maxLength: maxLengthProp,
|
|
19
|
+
size: sizeProp,
|
|
20
|
+
...rest
|
|
21
|
+
} = getProps({size, ...props}, avatarStackProps)
|
|
22
|
+
const childArray = Children.toArray(children).filter(
|
|
23
|
+
isValidElement,
|
|
24
|
+
) as ReactElement<AvatarProps>[]
|
|
25
|
+
const length = childArray.length
|
|
26
|
+
const maxLength = maxLengthProp ?? length
|
|
27
|
+
const visibleAvatars =
|
|
28
|
+
length > maxLength ? childArray.slice(length - maxLength, length) : childArray
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<ul
|
|
32
|
+
className={clsx(avatarStackClassName, 'sui-display-flex', className)}
|
|
33
|
+
style={style}
|
|
34
|
+
data-ui="AvatarStack"
|
|
35
|
+
{...rest}
|
|
36
|
+
>
|
|
37
|
+
{(!length || length > maxLength) && (
|
|
38
|
+
<li className="sui-display-flex">
|
|
39
|
+
<AvatarCounter
|
|
40
|
+
count={!length ? 0 : length - maxLength}
|
|
41
|
+
size={sizeProp}
|
|
42
|
+
aria-label={
|
|
43
|
+
!length
|
|
44
|
+
? 'No users'
|
|
45
|
+
: `+ ${length - maxLength} more user${length - maxLength > 1 ? 's' : ''}`
|
|
46
|
+
}
|
|
47
|
+
/>
|
|
48
|
+
</li>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{visibleAvatars.map((child) => (
|
|
52
|
+
<li key={child.key} className="sui-display-flex">
|
|
53
|
+
{cloneElement(child, {size: sizeProp})}
|
|
54
|
+
</li>
|
|
55
|
+
))}
|
|
56
|
+
</ul>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type {AvatarStackProps}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.sui-AvatarStack {
|
|
2
|
+
list-style: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.sui-AvatarStack li:has([class*='sui-Avatar']) + li:has([class*='sui-Avatar']) {
|
|
6
|
+
margin-left: calc(-1 * var(--space-1));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.sui-AvatarStack li:has(+ li [class*='sui-Avatar']) {
|
|
10
|
+
mask-image: radial-gradient(
|
|
11
|
+
50% 50% at calc(150% - var(--space-1) - 1.25px) 50%,
|
|
12
|
+
transparent 97.5%,
|
|
13
|
+
#000 100%
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {AVATAR_SIZE, type AvatarSize} from '../../types/Avatar'
|
|
2
|
+
import {type PropDef} from '../../types/PropDef'
|
|
3
|
+
|
|
4
|
+
/** @public */
|
|
5
|
+
export interface AvatarStackProps extends React.ComponentProps<'ul'> {
|
|
6
|
+
/** Max number of avatars to display */
|
|
7
|
+
maxLength?: number
|
|
8
|
+
/** Avatar and AvatarCounter size */
|
|
9
|
+
size?: AvatarSize
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const avatarStackProps: Record<string, PropDef> = {
|
|
13
|
+
maxLength: {
|
|
14
|
+
type: 'number',
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
type: 'union',
|
|
18
|
+
values: AVATAR_SIZE,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
.sui-Button:hover {
|
|
11
11
|
background: color-mix(
|
|
12
12
|
in srgb,
|
|
13
|
-
var(--tone-500)
|
|
13
|
+
var(--tone-500) 12%,
|
|
14
14
|
var(--background-color)
|
|
15
15
|
);
|
|
16
16
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
.sui-Button.sui-button-level-primary:hover {
|
|
19
19
|
background: color-mix(
|
|
20
20
|
in srgb,
|
|
21
|
-
var(--tone-950)
|
|
21
|
+
var(--tone-950) 12%,
|
|
22
22
|
var(--background-color)
|
|
23
23
|
);
|
|
24
24
|
}
|
|
@@ -17,7 +17,7 @@ const checkboxMarkClassName = suffixClassName('sui-CheckboxMark')
|
|
|
17
17
|
/** @beta */
|
|
18
18
|
export function Checkbox(props: CheckboxProps) {
|
|
19
19
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
20
|
-
const {className, style, disabled,
|
|
20
|
+
const {className, style, disabled, hasError, indeterminate, label, ...rest} = getProps(
|
|
21
21
|
props,
|
|
22
22
|
checkboxProps,
|
|
23
23
|
)
|
|
@@ -34,7 +34,7 @@ export function Checkbox(props: CheckboxProps) {
|
|
|
34
34
|
style={style}
|
|
35
35
|
data-ui="Checkbox"
|
|
36
36
|
disabled={disabled}
|
|
37
|
-
error={
|
|
37
|
+
error={hasError}
|
|
38
38
|
>
|
|
39
39
|
<VisuallyHidden
|
|
40
40
|
as="input"
|
|
@@ -47,7 +47,7 @@ export function Checkbox(props: CheckboxProps) {
|
|
|
47
47
|
|
|
48
48
|
<Box
|
|
49
49
|
as="span"
|
|
50
|
-
className={clsx(checkboxMarkClassName,
|
|
50
|
+
className={clsx(checkboxMarkClassName, hasError && 'sui-error')}
|
|
51
51
|
position="relative"
|
|
52
52
|
radius={2}
|
|
53
53
|
width="17px"
|
|
@@ -7,7 +7,7 @@ export interface CheckboxProps extends React.ComponentProps<'input'>, MarginProp
|
|
|
7
7
|
* Applies error styling to the checkbox mark.
|
|
8
8
|
* @remarks Use when form validation fails. Pair with a visible error message. The prop is visual only.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
hasError?: boolean
|
|
11
11
|
/**
|
|
12
12
|
* Renders the "mixed state" mark (a horizontal line).
|
|
13
13
|
* @remarks Use when the checkbox controls a group with some-but-not-all children checked.
|
|
@@ -20,7 +20,7 @@ export interface CheckboxProps extends React.ComponentProps<'input'>, MarginProp
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const checkboxProps: Record<string, PropDef> = {
|
|
23
|
-
|
|
23
|
+
hasError: {
|
|
24
24
|
type: 'boolean',
|
|
25
25
|
},
|
|
26
26
|
indeterminate: {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
|
|
3
|
+
import {getProps} from '../../utils/getProps'
|
|
4
|
+
import {suffixClassName} from '../../utils/suffixClassName'
|
|
5
|
+
import {KBD} from '../kbd/KBD'
|
|
6
|
+
import {type HotkeysProps, hotkeysProps} from './hotkeys.props'
|
|
7
|
+
|
|
8
|
+
const hotkeysClassName = suffixClassName('sui-Hotkeys')
|
|
9
|
+
|
|
10
|
+
/** @alpha */
|
|
11
|
+
export function Hotkeys({keys, ...props}: HotkeysProps) {
|
|
12
|
+
const {className, style, ...rest} = getProps(props, hotkeysProps)
|
|
13
|
+
|
|
14
|
+
if (!keys || keys.length === 0) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const hotkeysClasses = clsx(hotkeysClassName, 'sui-display-inline-flex')
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<kbd className={clsx(hotkeysClasses, className)} style={style} data-ui="Hotkeys" {...rest}>
|
|
22
|
+
{keys.map((key) => (
|
|
23
|
+
<KBD key={key} text={key} />
|
|
24
|
+
))}
|
|
25
|
+
</kbd>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type {HotkeysProps}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {type PropDef} from '../../types/PropDef'
|
|
2
|
+
|
|
3
|
+
/** @public */
|
|
4
|
+
export interface HotkeysProps extends React.ComponentProps<'kbd'> {
|
|
5
|
+
/**
|
|
6
|
+
* The keys in the shortcut, in order. Each one renders as a KBD.
|
|
7
|
+
*/
|
|
8
|
+
keys?: string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const hotkeysProps: Record<string, PropDef> = {
|
|
12
|
+
keys: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
},
|
|
15
|
+
}
|
package/src/components/index.css
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
@import './avatar/avatar.css';
|
|
2
|
+
@import './avatar-counter/avatar-counter.css';
|
|
3
|
+
@import './avatar-stack/avatar-stack.css';
|
|
1
4
|
@import './badge/badge.css';
|
|
2
5
|
@import './box/box.css';
|
|
3
6
|
@import './button/button.css';
|
|
@@ -10,10 +13,12 @@
|
|
|
10
13
|
@import './flex/flex.css';
|
|
11
14
|
@import './grid/grid.css';
|
|
12
15
|
@import './heading/heading.css';
|
|
16
|
+
@import './hotkeys/hotkeys.css';
|
|
13
17
|
@import './icon/icon.css';
|
|
14
18
|
@import './indicator/indicator.css';
|
|
15
19
|
@import './indicator-stack/indicator-stack.css';
|
|
16
20
|
@import './inline/inline.css';
|
|
21
|
+
@import './kbd/kbd.css';
|
|
17
22
|
@import './label/label.css';
|
|
18
23
|
@import './link/link.css';
|
|
19
24
|
@import './list/list.css';
|
|
@@ -25,6 +30,7 @@
|
|
|
25
30
|
@import './skip-to-content/skip-to-content.css';
|
|
26
31
|
@import './switch/switch.css';
|
|
27
32
|
@import './text/text.css';
|
|
33
|
+
@import './text-area/text-area.css';
|
|
28
34
|
@import './text-input/text-input.css';
|
|
29
35
|
@import './tooltip/tooltip.css';
|
|
30
36
|
@import './visually-hidden/visually-hidden.css';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
|
|
3
|
+
import {getProps} from '../../utils/getProps'
|
|
4
|
+
import {suffixClassName} from '../../utils/suffixClassName'
|
|
5
|
+
import {Text} from '../text/Text'
|
|
6
|
+
import {type KBDProps, kbdProps} from './kbd.props'
|
|
7
|
+
|
|
8
|
+
const kbdClassName = suffixClassName('sui-KBD')
|
|
9
|
+
|
|
10
|
+
/** @alpha */
|
|
11
|
+
export function KBD(props: KBDProps) {
|
|
12
|
+
const {className, style, text, ...rest} = getProps(props, kbdProps)
|
|
13
|
+
|
|
14
|
+
const kbdClasses = clsx(kbdClassName, 'sui-p1 sui-radius2 sui-display-inline-block')
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Text
|
|
18
|
+
as="kbd"
|
|
19
|
+
className={clsx(kbdClasses, className)}
|
|
20
|
+
style={style}
|
|
21
|
+
data-ui="KBD"
|
|
22
|
+
size={0}
|
|
23
|
+
weight="semibold"
|
|
24
|
+
muted
|
|
25
|
+
trim
|
|
26
|
+
{...rest}
|
|
27
|
+
>
|
|
28
|
+
{text}
|
|
29
|
+
</Text>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type {KBDProps}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {type PropDef} from '../../types/PropDef'
|
|
2
|
+
|
|
3
|
+
/** @public */
|
|
4
|
+
export interface KBDProps extends React.ComponentProps<'kbd'> {
|
|
5
|
+
/**
|
|
6
|
+
* Sets KBD's key label.
|
|
7
|
+
*/
|
|
8
|
+
text?: string
|
|
9
|
+
/**
|
|
10
|
+
* Not accepted.
|
|
11
|
+
* @remarks Pass the key label via `text`.
|
|
12
|
+
*/
|
|
13
|
+
children?: never
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const kbdProps: Record<string, PropDef> = {
|
|
17
|
+
text: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
@@ -13,7 +13,7 @@ const radioMarkClassName = suffixClassName('sui-RadioMark')
|
|
|
13
13
|
|
|
14
14
|
/** @beta */
|
|
15
15
|
export function Radio(props: RadioProps) {
|
|
16
|
-
const {className, style, label, disabled,
|
|
16
|
+
const {className, style, label, disabled, hasError, ...rest} = getProps(props, radioProps)
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<Label
|
|
@@ -21,7 +21,7 @@ export function Radio(props: RadioProps) {
|
|
|
21
21
|
style={style}
|
|
22
22
|
data-ui="Radio"
|
|
23
23
|
disabled={disabled}
|
|
24
|
-
error={
|
|
24
|
+
error={hasError}
|
|
25
25
|
>
|
|
26
26
|
<VisuallyHidden
|
|
27
27
|
as="input"
|
|
@@ -33,7 +33,7 @@ export function Radio(props: RadioProps) {
|
|
|
33
33
|
|
|
34
34
|
<Box
|
|
35
35
|
as="span"
|
|
36
|
-
className={clsx(radioMarkClassName,
|
|
36
|
+
className={clsx(radioMarkClassName, hasError && 'sui-error')}
|
|
37
37
|
position="relative"
|
|
38
38
|
radius="full"
|
|
39
39
|
width="17px"
|
|
@@ -7,7 +7,7 @@ export interface RadioProps extends React.ComponentProps<'input'>, MarginProps {
|
|
|
7
7
|
* Applies error styling to the radio mark.
|
|
8
8
|
* @remarks Use when form validation fails. Pair with a visible error message. The prop is visual only.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
hasError?: boolean
|
|
11
11
|
/**
|
|
12
12
|
* Visible label next to the radio.
|
|
13
13
|
*/
|
|
@@ -15,7 +15,7 @@ export interface RadioProps extends React.ComponentProps<'input'>, MarginProps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const radioProps: Record<string, PropDef> = {
|
|
18
|
-
|
|
18
|
+
hasError: {
|
|
19
19
|
type: 'boolean',
|
|
20
20
|
},
|
|
21
21
|
label: {
|
|
@@ -5,6 +5,7 @@ import {type PropDef} from '../../types/PropDef'
|
|
|
5
5
|
export interface SelectProps extends React.ComponentProps<'select'>, FormElementDensityProps {
|
|
6
6
|
/**
|
|
7
7
|
* Used to trigger styling for invalid states.
|
|
8
|
+
* @remarks Use when form validation fails. Pair with a visible error message.
|
|
8
9
|
*/
|
|
9
10
|
hasError?: boolean
|
|
10
11
|
}
|
|
@@ -13,7 +13,7 @@ const switchMarkClassName = suffixClassName('sui-SwitchMark')
|
|
|
13
13
|
|
|
14
14
|
/** @beta */
|
|
15
15
|
export function Switch(props: SwitchProps) {
|
|
16
|
-
const {className, style, disabled,
|
|
16
|
+
const {className, style, disabled, hasError, label, ...rest} = getProps(props, switchProps)
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<Label
|
|
@@ -21,7 +21,7 @@ export function Switch(props: SwitchProps) {
|
|
|
21
21
|
style={style}
|
|
22
22
|
data-ui="Switch"
|
|
23
23
|
disabled={props.disabled}
|
|
24
|
-
error={
|
|
24
|
+
error={hasError}
|
|
25
25
|
>
|
|
26
26
|
<VisuallyHidden
|
|
27
27
|
as="input"
|
|
@@ -34,7 +34,7 @@ export function Switch(props: SwitchProps) {
|
|
|
34
34
|
|
|
35
35
|
<Flex
|
|
36
36
|
as="span"
|
|
37
|
-
className={clsx(switchMarkClassName,
|
|
37
|
+
className={clsx(switchMarkClassName, hasError && 'sui-error')}
|
|
38
38
|
alignItems="center"
|
|
39
39
|
radius={6}
|
|
40
40
|
aria-hidden="true"
|
|
@@ -7,7 +7,7 @@ export interface SwitchProps extends React.ComponentProps<'input'>, MarginProps
|
|
|
7
7
|
* Applies error styling to the switch track.
|
|
8
8
|
* @remarks Use when form validation fails. Pair with a visible error message. The prop is visual only.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
hasError?: boolean
|
|
11
11
|
/**
|
|
12
12
|
* Visible label next to the switch.
|
|
13
13
|
*/
|
|
@@ -15,7 +15,7 @@ export interface SwitchProps extends React.ComponentProps<'input'>, MarginProps
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const switchProps: Record<string, PropDef> = {
|
|
18
|
-
|
|
18
|
+
hasError: {
|
|
19
19
|
type: 'boolean',
|
|
20
20
|
},
|
|
21
21
|
label: {
|