@sanity/sanity-id 0.0.0
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/.turbo/turbo-build.log +32 -0
- package/README.md +272 -0
- package/css/all.css +3 -0
- package/css/colors.css +498 -0
- package/css/tailwind.css +3 -0
- package/css/variables.css +326 -0
- package/dist/Breadcrumbs.js +53 -0
- package/dist/Button.js +50 -0
- package/dist/Card.js +16 -0
- package/dist/Checkbox.js +31 -0
- package/dist/Eyebrow.js +30 -0
- package/dist/IconButton.js +38 -0
- package/dist/Input.js +16 -0
- package/dist/Input.module-P--gA8sq.js +6 -0
- package/dist/Label.js +22 -0
- package/dist/Link-BWIwmuYV.js +4068 -0
- package/dist/LinkCTA.js +53 -0
- package/dist/Radio.js +29 -0
- package/dist/RadioSwitch.js +87 -0
- package/dist/SanityIcon-Bl5or1b8.js +13 -0
- package/dist/Select.js +22 -0
- package/dist/Switch.js +29 -0
- package/dist/TextArea.js +21 -0
- package/dist/_commonjsHelpers-C6fGbg64.js +6 -0
- package/dist/clsx-OuTLNxxd.js +16 -0
- package/dist/colors.js +935 -0
- package/dist/styles.css +1 -0
- package/dist/tailwind.js +577 -0
- package/dist/useLinkWithRef-D9NOX6Bd.js +21 -0
- package/dist/utils.js +23 -0
- package/package.json +56 -0
- package/postcss.config.js +6 -0
- package/src/colors.ts +3 -0
- package/src/components/Breadcrumbs.module.css +21 -0
- package/src/components/Breadcrumbs.tsx +38 -0
- package/src/components/Button.module.css +407 -0
- package/src/components/Button.tsx +110 -0
- package/src/components/Card.module.css +19 -0
- package/src/components/Card.tsx +18 -0
- package/src/components/Checkbox.module.css +82 -0
- package/src/components/Checkbox.tsx +38 -0
- package/src/components/Eyebrow.module.css +28 -0
- package/src/components/Eyebrow.tsx +37 -0
- package/src/components/IconButton.module.css +196 -0
- package/src/components/IconButton.tsx +62 -0
- package/src/components/Input.module.css +55 -0
- package/src/components/Input.tsx +23 -0
- package/src/components/Label.module.css +53 -0
- package/src/components/Label.tsx +33 -0
- package/src/components/LinkCTA.module.css +122 -0
- package/src/components/LinkCTA.tsx +77 -0
- package/src/components/Radio.module.css +91 -0
- package/src/components/Radio.tsx +33 -0
- package/src/components/RadioSwitch.module.css +125 -0
- package/src/components/RadioSwitch.tsx +122 -0
- package/src/components/Select.module.css +35 -0
- package/src/components/Select.tsx +28 -0
- package/src/components/Switch.module.css +112 -0
- package/src/components/Switch.tsx +33 -0
- package/src/components/TextArea.module.css +17 -0
- package/src/components/TextArea.tsx +30 -0
- package/src/components/helpers/AddRefParam.tsx +29 -0
- package/src/components/helpers/Link.tsx +56 -0
- package/src/components/helpers/NavLink.tsx +20 -0
- package/src/components/helpers/SanityIcon.tsx +25 -0
- package/src/components/helpers/useIsCurrentPage.ts +39 -0
- package/src/components/helpers/useLinkWithRef.ts +27 -0
- package/src/components/helpers/useSafePathname.ts +17 -0
- package/src/css.d.ts +4 -0
- package/src/tailwind.ts +408 -0
- package/src/tokens/dynamic-colors.ts +154 -0
- package/src/tokens/primitive-colors.ts +237 -0
- package/src/tokens/semantic-colors.ts +574 -0
- package/src/utils.ts +58 -0
- package/tailwind.config.ts +7 -0
- package/tsconfig.json +17 -0
- package/vite.config.ts +29 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import cn from "clsx"
|
|
4
|
+
import { isInternalHref } from "../utils"
|
|
5
|
+
import { AddRefParam } from "./helpers/AddRefParam"
|
|
6
|
+
import { Link } from "./helpers/Link"
|
|
7
|
+
import css from "./LinkCTA.module.css"
|
|
8
|
+
|
|
9
|
+
type AnchorOrDivAttributes =
|
|
10
|
+
| ({ href: string } & React.AnchorHTMLAttributes<HTMLAnchorElement>)
|
|
11
|
+
| ({ href?: undefined } & React.HTMLAttributes<HTMLDivElement>)
|
|
12
|
+
|
|
13
|
+
export namespace LinkCTA {
|
|
14
|
+
export type Props = AnchorOrDivAttributes & {
|
|
15
|
+
children?: React.ReactNode
|
|
16
|
+
arrowDirection?: "right" | "top-right"
|
|
17
|
+
arrowPosition?: "left" | "right"
|
|
18
|
+
arrowColor?: "accent" | "base"
|
|
19
|
+
size?: "md" | "lg"
|
|
20
|
+
state?: "default" | "hover" | "focus" | "active"
|
|
21
|
+
external?: boolean
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function LinkCTA({
|
|
26
|
+
arrowColor,
|
|
27
|
+
arrowDirection,
|
|
28
|
+
arrowPosition,
|
|
29
|
+
children,
|
|
30
|
+
size,
|
|
31
|
+
state,
|
|
32
|
+
href,
|
|
33
|
+
external,
|
|
34
|
+
...props
|
|
35
|
+
}: LinkCTA.Props) {
|
|
36
|
+
arrowColor ??= "base"
|
|
37
|
+
arrowDirection ??= "right"
|
|
38
|
+
arrowPosition ??= "left"
|
|
39
|
+
size ??= "md"
|
|
40
|
+
|
|
41
|
+
let Element: React.ElementType = "div"
|
|
42
|
+
const internal = isInternalHref(href ?? "")
|
|
43
|
+
|
|
44
|
+
if (href && !internal) {
|
|
45
|
+
arrowDirection = "top-right"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (href) Element = external ? "a" : Link
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<AddRefParam>
|
|
52
|
+
<Element
|
|
53
|
+
{...props}
|
|
54
|
+
className={cn(props.className, css.linkCta)}
|
|
55
|
+
data-arrow-direction={arrowDirection}
|
|
56
|
+
data-arrow-color={arrowColor}
|
|
57
|
+
data-arrow-position={arrowPosition}
|
|
58
|
+
data-size={size}
|
|
59
|
+
data-state={state}
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
href={href}
|
|
62
|
+
>
|
|
63
|
+
{arrowPosition === "left" && (
|
|
64
|
+
<span className={css.arrow}>
|
|
65
|
+
<span className={css.icon}>→</span>
|
|
66
|
+
</span>
|
|
67
|
+
)}
|
|
68
|
+
{children}
|
|
69
|
+
{arrowPosition === "right" && (
|
|
70
|
+
<span className={css.arrow}>
|
|
71
|
+
<span className={css.icon}>→</span>
|
|
72
|
+
</span>
|
|
73
|
+
)}
|
|
74
|
+
</Element>
|
|
75
|
+
</AddRefParam>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
@layer shared {
|
|
2
|
+
.wrapper {
|
|
3
|
+
--theme-colors-radio-fg: theme("colors.fg-strong");
|
|
4
|
+
--theme-colors-radio-bg: theme("colors.bg-strong");
|
|
5
|
+
--theme-colors-radio-bg-checked: theme("colors.fg-strong");
|
|
6
|
+
--theme-colors-radio-border: theme("colors.border-subtle");
|
|
7
|
+
--theme-colors-radio-border-hover: theme("colors.border-subtle");
|
|
8
|
+
--theme-colors-radio-icon: theme("colors.bg-strong");
|
|
9
|
+
--theme-colors-radio-outline: transparent;
|
|
10
|
+
--theme-colors-radio-outline-hover: theme("colors.bg-dim");
|
|
11
|
+
--theme-colors-radio-outline-focus: theme("colors.fg-strong");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.wrapper {
|
|
16
|
+
@apply flex items-start gap-x-8;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.input {
|
|
20
|
+
@apply sr-only;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.box {
|
|
24
|
+
@apply flex flex-shrink-0 items-center justify-center rounded-full border outline outline-0 outline-transparent transition-all;
|
|
25
|
+
|
|
26
|
+
background: var(--theme-colors-radio-bg);
|
|
27
|
+
border-color: var(--theme-colors-radio-border);
|
|
28
|
+
|
|
29
|
+
.wrapper[data-size="md"] & {
|
|
30
|
+
@apply h-[19px] w-[19px];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.wrapper[data-size="lg"] & {
|
|
34
|
+
@apply h-[21px] w-[21px];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.wrapper:hover &,
|
|
38
|
+
.wrapper[data-state="hover"] & {
|
|
39
|
+
@apply outline-4;
|
|
40
|
+
|
|
41
|
+
border-color: var(--theme-colors-radio-border-hover);
|
|
42
|
+
outline-color: var(--theme-colors-radio-outline-hover);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.input:focus + &,
|
|
46
|
+
.wrapper[data-state="focus"] & {
|
|
47
|
+
@apply outline-2 outline-offset-2;
|
|
48
|
+
|
|
49
|
+
border-color: var(--theme-colors-radio-border-hover);
|
|
50
|
+
outline-color: var(--theme-colors-radio-outline-focus);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.input:checked + &,
|
|
54
|
+
.wrapper[data-state="checked"] & {
|
|
55
|
+
background: var(--theme-colors-radio-bg-checked);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.label {
|
|
60
|
+
@apply -mt-[1px];
|
|
61
|
+
|
|
62
|
+
color: var(--theme-colors-radio-fg);
|
|
63
|
+
|
|
64
|
+
.wrapper[data-size="md"] {
|
|
65
|
+
@apply text-interactive-md;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.wrapper[data-size="lg"] {
|
|
69
|
+
@apply text-interactive-lg;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.box::after {
|
|
74
|
+
@apply rounded-full opacity-0 transition-opacity;
|
|
75
|
+
|
|
76
|
+
background: var(--theme-colors-radio-icon);
|
|
77
|
+
content: "";
|
|
78
|
+
|
|
79
|
+
.input:checked + &,
|
|
80
|
+
.wrapper[data-state="checked"] & {
|
|
81
|
+
@apply opacity-100;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.wrapper[data-size="md"] & {
|
|
85
|
+
@apply h-[7px] w-[7px];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.wrapper[data-size="lg"] & {
|
|
89
|
+
@apply h-[9px] w-[9px];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import cn from "clsx"
|
|
2
|
+
import css from "./Radio.module.css"
|
|
3
|
+
|
|
4
|
+
export type RadioProps = Omit<
|
|
5
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
6
|
+
"type" | "size"
|
|
7
|
+
> & {
|
|
8
|
+
size?: "md" | "lg"
|
|
9
|
+
state?: "hover" | "focus" | "checked"
|
|
10
|
+
children?: React.ReactNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Radio({
|
|
14
|
+
size,
|
|
15
|
+
state,
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
...props
|
|
19
|
+
}: RadioProps) {
|
|
20
|
+
size ??= "md"
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<label
|
|
24
|
+
className={cn(className, css.wrapper)}
|
|
25
|
+
data-state={state}
|
|
26
|
+
data-size={size}
|
|
27
|
+
>
|
|
28
|
+
<input {...props} className={css.input} type="radio" />
|
|
29
|
+
<div className={css.box} />
|
|
30
|
+
<p className={css.label}>{children}</p>
|
|
31
|
+
</label>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
@layer shared {
|
|
2
|
+
.fieldset {
|
|
3
|
+
--theme-colors-radio-switch-fg: theme("colors.fg-strong");
|
|
4
|
+
--theme-colors-radio-switch-fg-icon: theme("colors.fg-strong");
|
|
5
|
+
--theme-colors-radio-switch-fg-icon-hover: theme("colors.fg-strong");
|
|
6
|
+
--theme-colors-radio-switch-bg-icon: transparent;
|
|
7
|
+
--theme-colors-radio-switch-bg-icon-hover: theme("colors.border-subtle");
|
|
8
|
+
--theme-colors-radio-switch-bg-icon-active: theme("colors.bg-strong");
|
|
9
|
+
--theme-colors-radio-switch-border: theme("colors.border-subtle");
|
|
10
|
+
--theme-colors-radio-switch-outline: theme("colors.fg-strong");
|
|
11
|
+
--theme-colors-radio-switch-border-tooltip: theme("colors.border-subtle");
|
|
12
|
+
--theme-colors-radio-switch-bg-tooltip: theme("colors.bg-strong");
|
|
13
|
+
--theme-colors-radio-switch-fg-tooltip: theme("colors.fg-base");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.fieldset {
|
|
18
|
+
@apply relative isolate box-border flex w-fit rounded-full border;
|
|
19
|
+
|
|
20
|
+
border-color: var(--theme-colors-radio-switch-border);
|
|
21
|
+
|
|
22
|
+
&[data-size="md"] {
|
|
23
|
+
@apply gap-x-6 p-4;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&[data-size="sm"] {
|
|
27
|
+
@apply gap-x-4 p-2;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:disabled {
|
|
31
|
+
@apply pointer-events-none cursor-default opacity-50;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.indicator {
|
|
36
|
+
@apply absolute -z-10 box-border origin-center rounded-full opacity-0;
|
|
37
|
+
|
|
38
|
+
background: var(--theme-colors-radio-switch-bg-icon-active);
|
|
39
|
+
transition:
|
|
40
|
+
translate 350ms ease,
|
|
41
|
+
opacity 200ms 350ms ease;
|
|
42
|
+
|
|
43
|
+
.fieldset[data-size="md"] & {
|
|
44
|
+
@apply left-4 top-4 size-[30px];
|
|
45
|
+
|
|
46
|
+
translate: calc(36px * var(--index)) 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.fieldset[data-size="sm"] & {
|
|
50
|
+
@apply left-0 top-0 size-25;
|
|
51
|
+
|
|
52
|
+
translate: calc(25px * var(--index)) 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.radio:checked ~ & {
|
|
56
|
+
@apply opacity-100;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.radio:focus-visible ~ & {
|
|
60
|
+
@apply outline outline-2 outline-offset-2;
|
|
61
|
+
|
|
62
|
+
outline-color: var(--theme-colors-radio-switch-outline);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.radio {
|
|
67
|
+
@apply sr-only;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.icon {
|
|
71
|
+
@apply relative box-border cursor-pointer rounded-full transition-colors text-fg-dim;
|
|
72
|
+
|
|
73
|
+
.fieldset[data-size="md"] & {
|
|
74
|
+
@apply size-[30px] p-6;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.fieldset[data-size="sm"] & {
|
|
78
|
+
@apply size-21 p-0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&:hover {
|
|
82
|
+
@apply text-fg-base;
|
|
83
|
+
|
|
84
|
+
background: var(--theme-colors-radio-switch-bg-icon-hover);
|
|
85
|
+
color: var(--theme-colors-radio-switch-fg-icon-hover);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&:active,
|
|
89
|
+
.radio:checked + & {
|
|
90
|
+
@apply transition-colors duration-75;
|
|
91
|
+
|
|
92
|
+
color: var(--theme-colors-radio-switch-fg-icon-active);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.tooltip {
|
|
97
|
+
@apply label-sm py-4 px-6 absolute left-1/2 top-0 whitespace-nowrap rounded-full border opacity-0 transition-opacity;
|
|
98
|
+
|
|
99
|
+
background: var(--theme-colors-radio-switch-bg-tooltip);
|
|
100
|
+
border-color: var(--theme-colors-radio-switch-border-tooltip);
|
|
101
|
+
color: var(--theme-colors-radio-switch-fg-tooltip);
|
|
102
|
+
translate: -50% calc(-100% - 2px);
|
|
103
|
+
|
|
104
|
+
.radio:focus-visible + .icon &,
|
|
105
|
+
.icon:hover & {
|
|
106
|
+
@apply opacity-100;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:global([data-rebrand="true"]) {
|
|
111
|
+
.fieldset {
|
|
112
|
+
--theme-colors-radio-switch-fg: theme("colors.fg-strong");
|
|
113
|
+
--theme-colors-radio-switch-fg-icon: theme("colors.gray-500");
|
|
114
|
+
--theme-colors-radio-switch-fg-icon-hover: theme("colors.fg-strong");
|
|
115
|
+
--theme-colors-radio-switch-fg-icon-active: theme("colors.bg-strong");
|
|
116
|
+
--theme-colors-radio-switch-bg-icon: transparent;
|
|
117
|
+
--theme-colors-radio-switch-bg-icon-hover: transparent;
|
|
118
|
+
--theme-colors-radio-switch-bg-icon-active: theme("colors.gray-500");
|
|
119
|
+
--theme-colors-radio-switch-border: transparent;
|
|
120
|
+
--theme-colors-radio-switch-outline: theme("colors.fg-strong");
|
|
121
|
+
--theme-colors-radio-switch-border-tooltip: theme("colors.border-subtle");
|
|
122
|
+
--theme-colors-radio-switch-bg-tooltip: theme("colors.bg-strong");
|
|
123
|
+
--theme-colors-radio-switch-fg-tooltip: theme("colors.fg-base");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Fragment, useEffect, useRef, useState } from "react"
|
|
4
|
+
import cn from "clsx"
|
|
5
|
+
import { slugify } from "../utils"
|
|
6
|
+
import { SanityIcon, SanityIconName } from "./helpers/SanityIcon"
|
|
7
|
+
import css from "./RadioSwitch.module.css"
|
|
8
|
+
|
|
9
|
+
export type RadioSwitchValue = {
|
|
10
|
+
label: string
|
|
11
|
+
id?: string
|
|
12
|
+
value: string
|
|
13
|
+
icon: SanityIconName
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type RadioSwitchProps = Omit<
|
|
17
|
+
React.FieldsetHTMLAttributes<HTMLFieldSetElement>,
|
|
18
|
+
"size" | "onChange"
|
|
19
|
+
> & {
|
|
20
|
+
name: string
|
|
21
|
+
options: RadioSwitchValue[]
|
|
22
|
+
value?: string
|
|
23
|
+
defaultValue?: string
|
|
24
|
+
legend: string
|
|
25
|
+
state?: "hover" | "focus" | "checked"
|
|
26
|
+
size?: "sm" | "md"
|
|
27
|
+
forceTooltip?: boolean
|
|
28
|
+
onChange?: React.ChangeEventHandler<HTMLInputElement>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function RadioSwitch({
|
|
32
|
+
className,
|
|
33
|
+
defaultValue,
|
|
34
|
+
onChange: onChangeProp,
|
|
35
|
+
name,
|
|
36
|
+
size,
|
|
37
|
+
state,
|
|
38
|
+
legend,
|
|
39
|
+
value,
|
|
40
|
+
options,
|
|
41
|
+
...props
|
|
42
|
+
}: RadioSwitchProps) {
|
|
43
|
+
const indicatorRef = useRef<HTMLDivElement>(null)
|
|
44
|
+
const [active, setIndex] = useState<string>(() =>
|
|
45
|
+
findActive(options, value, defaultValue)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
setIndex(findActive(options, value, defaultValue))
|
|
50
|
+
}, [value, options, defaultValue])
|
|
51
|
+
|
|
52
|
+
size ??= "md"
|
|
53
|
+
|
|
54
|
+
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
55
|
+
indicatorRef.current?.animate(
|
|
56
|
+
[{ scale: "1" }, { scale: "1.5 0.5" }, { scale: "1" }],
|
|
57
|
+
{
|
|
58
|
+
duration: 350,
|
|
59
|
+
easing: "ease-out",
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
setIndex(event.target.dataset.index ?? "")
|
|
63
|
+
onChangeProp?.(event)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<fieldset
|
|
68
|
+
{...props}
|
|
69
|
+
className={cn(className, css.fieldset)}
|
|
70
|
+
style={{ "--index": active }}
|
|
71
|
+
data-state={state}
|
|
72
|
+
data-size={size}
|
|
73
|
+
suppressHydrationWarning
|
|
74
|
+
>
|
|
75
|
+
{legend && <legend className="sr-only">{legend}</legend>}
|
|
76
|
+
{options.map((option, index) => (
|
|
77
|
+
<Fragment key={option.value}>
|
|
78
|
+
<input
|
|
79
|
+
type="radio"
|
|
80
|
+
id={createId(name, option)}
|
|
81
|
+
name={name}
|
|
82
|
+
className={css.radio}
|
|
83
|
+
value={option.value ?? ""}
|
|
84
|
+
data-index={index}
|
|
85
|
+
onChange={onChange}
|
|
86
|
+
checked={value ? option.value === value : false}
|
|
87
|
+
defaultChecked={
|
|
88
|
+
!value && defaultValue ? option.value === defaultValue : undefined
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
<label
|
|
92
|
+
className={css.icon}
|
|
93
|
+
htmlFor={createId(name, option)}
|
|
94
|
+
data-force-tooltip={String(index) === active}
|
|
95
|
+
suppressHydrationWarning
|
|
96
|
+
>
|
|
97
|
+
<SanityIcon icon={option.icon} className={css.icona} />
|
|
98
|
+
<div className={css.tooltip}>{option.label}</div>
|
|
99
|
+
</label>
|
|
100
|
+
</Fragment>
|
|
101
|
+
))}
|
|
102
|
+
<div className={css.indicator} ref={indicatorRef} />
|
|
103
|
+
</fieldset>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createId(name: string, value: RadioSwitchValue) {
|
|
108
|
+
if (value.id) return value.id
|
|
109
|
+
return slugify(`${name}-${value.value}`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function findActive(
|
|
113
|
+
options: RadioSwitchValue[],
|
|
114
|
+
value?: string,
|
|
115
|
+
defaultValue?: string
|
|
116
|
+
): string {
|
|
117
|
+
let index = options.findIndex((o) => !!value && o.value === value)
|
|
118
|
+
if (index >= 0) return index.toString()
|
|
119
|
+
index = options.findIndex((o) => !!defaultValue && o.value === defaultValue)
|
|
120
|
+
if (index >= 0) return index.toString()
|
|
121
|
+
return ""
|
|
122
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@layer shared {
|
|
2
|
+
.wrapper {
|
|
3
|
+
--theme-colors-select-icon: theme("colors.fg-dim");
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.select {
|
|
8
|
+
@apply min-w-full appearance-none;
|
|
9
|
+
|
|
10
|
+
&[data-size="md"] {
|
|
11
|
+
@apply pr-24;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
&[data-size="lg"] {
|
|
15
|
+
@apply pr-32;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.wrapper {
|
|
20
|
+
@apply relative flex items-center;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.icon {
|
|
24
|
+
@apply pointer-events-none absolute;
|
|
25
|
+
|
|
26
|
+
color: var(--theme-colors-select-icon);
|
|
27
|
+
|
|
28
|
+
.select[data-size="md"] + & {
|
|
29
|
+
@apply right-2 h-[21px];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.select[data-size="lg"] + & {
|
|
33
|
+
@apply right-4 h-[25px];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import cn from "clsx"
|
|
2
|
+
import { SanityIcon } from "./helpers/SanityIcon"
|
|
3
|
+
import inputCss from "./Input.module.css"
|
|
4
|
+
import css from "./Select.module.css"
|
|
5
|
+
|
|
6
|
+
export type SelectProps = Omit<
|
|
7
|
+
React.SelectHTMLAttributes<HTMLSelectElement>,
|
|
8
|
+
"size"
|
|
9
|
+
> & {
|
|
10
|
+
size?: "md" | "lg"
|
|
11
|
+
state?: "hover" | "focus"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Select({ size, state, ...props }: SelectProps) {
|
|
15
|
+
size ??= "md"
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className={cn(css.wrapper, props.className)}>
|
|
19
|
+
<select
|
|
20
|
+
{...props}
|
|
21
|
+
className={cn(inputCss.input, css.select)}
|
|
22
|
+
data-size={size}
|
|
23
|
+
data-state={state}
|
|
24
|
+
/>
|
|
25
|
+
<SanityIcon icon="select" className={css.icon} />
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
@layer shared {
|
|
2
|
+
.wrapper {
|
|
3
|
+
--theme-colors-switch-fg: theme("colors.fg-strong");
|
|
4
|
+
--theme-colors-switch-thumb-track-off: theme("colors.border-base");
|
|
5
|
+
--theme-colors-switch-thumb-track-off-hover: theme("colors.border-strong");
|
|
6
|
+
--theme-colors-switch-thumb-track-on: theme("colors.fg-strong");
|
|
7
|
+
--theme-colors-switch-thumb-indicator: theme("colors.bg-strong");
|
|
8
|
+
--theme-colors-switch-outline: transparent;
|
|
9
|
+
--theme-colors-switch-outline-hover: theme("colors.bg-dim");
|
|
10
|
+
--theme-colors-switch-outline-focus: theme("colors.fg-strong");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.wrapper {
|
|
15
|
+
@apply flex items-center gap-x-8;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.input {
|
|
19
|
+
@apply sr-only;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.thumb {
|
|
23
|
+
@apply relative isolate cursor-pointer rounded-full outline-0 transition-all;
|
|
24
|
+
|
|
25
|
+
background: var(--theme-colors-switch-thumb-track-off);
|
|
26
|
+
outline-color: var(--theme-colors-switch-outline);
|
|
27
|
+
|
|
28
|
+
.wrapper:hover &,
|
|
29
|
+
.wrapper[data-state="hover"] & {
|
|
30
|
+
background: var(--theme-colors-switch-thumb-track-off-hover);
|
|
31
|
+
outline: 4px solid var(--theme-colors-switch-outline-hover);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.input:checked + & {
|
|
35
|
+
background: var(--theme-colors-switch-thumb-track-on);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.input:focus &,
|
|
39
|
+
.wrapper[data-state="focus"] & {
|
|
40
|
+
outline: 2px solid var(--theme-colors-switch-outline-focus);
|
|
41
|
+
outline-offset: 2px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.wrapper[data-size="md"] & {
|
|
45
|
+
@apply h-[19px] w-[35px];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.wrapper[data-size="lg"] & {
|
|
49
|
+
@apply h-21 w-[37px];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.thumb::before {
|
|
54
|
+
@apply absolute left-[3px] top-[3px] box-border aspect-square h-[calc(100%-6px)] rounded-full border-[3px] border-solid border-transparent transition-all;
|
|
55
|
+
|
|
56
|
+
animation: toggle-off 0ms ease 0ms forwards;
|
|
57
|
+
background: var(--theme-colors-switch-thumb-indicator);
|
|
58
|
+
content: "";
|
|
59
|
+
|
|
60
|
+
.input:checked + & {
|
|
61
|
+
animation: toggle-on 0ms ease 0ms forwards;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.input:focus + &,
|
|
65
|
+
.wrapper:hover & {
|
|
66
|
+
animation-duration: 200ms;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.label {
|
|
71
|
+
color: var(--theme-colors-switch-fg);
|
|
72
|
+
|
|
73
|
+
.wrapper[data-size="md"] & {
|
|
74
|
+
@apply text-interactive-md;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.wrapper[data-size="lg"] & {
|
|
78
|
+
@apply text-interactive-lg;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@keyframes toggle-on {
|
|
83
|
+
0% {
|
|
84
|
+
scale: 1;
|
|
85
|
+
translate: 0 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
50% {
|
|
89
|
+
scale: 1.4 0.6;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
100% {
|
|
93
|
+
scale: 1;
|
|
94
|
+
translate: 16px 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@keyframes toggle-off {
|
|
99
|
+
0% {
|
|
100
|
+
scale: 1;
|
|
101
|
+
translate: 16px 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
50% {
|
|
105
|
+
scale: 1.4 0.6;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
100% {
|
|
109
|
+
scale: 1;
|
|
110
|
+
translate: 0 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import cn from "clsx"
|
|
2
|
+
import css from "./Switch.module.css"
|
|
3
|
+
|
|
4
|
+
export type SwitchProps = Omit<
|
|
5
|
+
Raect.InputHTMLAttributes<HTMLInputElement>,
|
|
6
|
+
"size"
|
|
7
|
+
> & {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
state?: "hover" | "focus" | "checked"
|
|
10
|
+
size?: "md" | "lg"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Switch({
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
size,
|
|
17
|
+
state,
|
|
18
|
+
...props
|
|
19
|
+
}: SwitchProps) {
|
|
20
|
+
size ??= "md"
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<label
|
|
24
|
+
className={cn(className, css.wrapper)}
|
|
25
|
+
data-size={size}
|
|
26
|
+
data-state={state}
|
|
27
|
+
>
|
|
28
|
+
<input {...props} className={css.input} type="checkbox" />
|
|
29
|
+
<div className={css.thumb} />
|
|
30
|
+
<div className={css.label}>{children}</div>
|
|
31
|
+
</label>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
@layer shared {
|
|
2
|
+
.wrapper {
|
|
3
|
+
--theme-colors-select-icon: theme("colors.fg-dim");
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.textarea {
|
|
8
|
+
@apply min-w-full appearance-none;
|
|
9
|
+
|
|
10
|
+
&[data-size="md"] {
|
|
11
|
+
@apply text-interactive-md min-h-64 p-8 leading-normal;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
&[data-size="lg"] {
|
|
15
|
+
@apply text-interactive-lg min-h-96 p-12 leading-normal;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import cn from "clsx"
|
|
2
|
+
import { forwardRef } from "react"
|
|
3
|
+
import inputCss from "./Input.module.css"
|
|
4
|
+
import css from "./TextArea.module.css"
|
|
5
|
+
|
|
6
|
+
export type InputProps = Omit<
|
|
7
|
+
React.InputHTMLAttributes<HTMLTextAreaElement>,
|
|
8
|
+
"size"
|
|
9
|
+
> & {
|
|
10
|
+
size?: "md" | "lg"
|
|
11
|
+
state?: "hover" | "focus"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const TextArea = forwardRef<HTMLTextAreaElement, InputProps>(
|
|
15
|
+
({ size, state, ...props }, ref) => {
|
|
16
|
+
size ??= "md"
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<textarea
|
|
20
|
+
{...props}
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cn(inputCss.input, css.textarea, props.className)}
|
|
23
|
+
data-size={size}
|
|
24
|
+
data-state={state}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
TextArea.displayName = "TextArea"
|