@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.
Files changed (77) hide show
  1. package/.turbo/turbo-build.log +32 -0
  2. package/README.md +272 -0
  3. package/css/all.css +3 -0
  4. package/css/colors.css +498 -0
  5. package/css/tailwind.css +3 -0
  6. package/css/variables.css +326 -0
  7. package/dist/Breadcrumbs.js +53 -0
  8. package/dist/Button.js +50 -0
  9. package/dist/Card.js +16 -0
  10. package/dist/Checkbox.js +31 -0
  11. package/dist/Eyebrow.js +30 -0
  12. package/dist/IconButton.js +38 -0
  13. package/dist/Input.js +16 -0
  14. package/dist/Input.module-P--gA8sq.js +6 -0
  15. package/dist/Label.js +22 -0
  16. package/dist/Link-BWIwmuYV.js +4068 -0
  17. package/dist/LinkCTA.js +53 -0
  18. package/dist/Radio.js +29 -0
  19. package/dist/RadioSwitch.js +87 -0
  20. package/dist/SanityIcon-Bl5or1b8.js +13 -0
  21. package/dist/Select.js +22 -0
  22. package/dist/Switch.js +29 -0
  23. package/dist/TextArea.js +21 -0
  24. package/dist/_commonjsHelpers-C6fGbg64.js +6 -0
  25. package/dist/clsx-OuTLNxxd.js +16 -0
  26. package/dist/colors.js +935 -0
  27. package/dist/styles.css +1 -0
  28. package/dist/tailwind.js +577 -0
  29. package/dist/useLinkWithRef-D9NOX6Bd.js +21 -0
  30. package/dist/utils.js +23 -0
  31. package/package.json +56 -0
  32. package/postcss.config.js +6 -0
  33. package/src/colors.ts +3 -0
  34. package/src/components/Breadcrumbs.module.css +21 -0
  35. package/src/components/Breadcrumbs.tsx +38 -0
  36. package/src/components/Button.module.css +407 -0
  37. package/src/components/Button.tsx +110 -0
  38. package/src/components/Card.module.css +19 -0
  39. package/src/components/Card.tsx +18 -0
  40. package/src/components/Checkbox.module.css +82 -0
  41. package/src/components/Checkbox.tsx +38 -0
  42. package/src/components/Eyebrow.module.css +28 -0
  43. package/src/components/Eyebrow.tsx +37 -0
  44. package/src/components/IconButton.module.css +196 -0
  45. package/src/components/IconButton.tsx +62 -0
  46. package/src/components/Input.module.css +55 -0
  47. package/src/components/Input.tsx +23 -0
  48. package/src/components/Label.module.css +53 -0
  49. package/src/components/Label.tsx +33 -0
  50. package/src/components/LinkCTA.module.css +122 -0
  51. package/src/components/LinkCTA.tsx +77 -0
  52. package/src/components/Radio.module.css +91 -0
  53. package/src/components/Radio.tsx +33 -0
  54. package/src/components/RadioSwitch.module.css +125 -0
  55. package/src/components/RadioSwitch.tsx +122 -0
  56. package/src/components/Select.module.css +35 -0
  57. package/src/components/Select.tsx +28 -0
  58. package/src/components/Switch.module.css +112 -0
  59. package/src/components/Switch.tsx +33 -0
  60. package/src/components/TextArea.module.css +17 -0
  61. package/src/components/TextArea.tsx +30 -0
  62. package/src/components/helpers/AddRefParam.tsx +29 -0
  63. package/src/components/helpers/Link.tsx +56 -0
  64. package/src/components/helpers/NavLink.tsx +20 -0
  65. package/src/components/helpers/SanityIcon.tsx +25 -0
  66. package/src/components/helpers/useIsCurrentPage.ts +39 -0
  67. package/src/components/helpers/useLinkWithRef.ts +27 -0
  68. package/src/components/helpers/useSafePathname.ts +17 -0
  69. package/src/css.d.ts +4 -0
  70. package/src/tailwind.ts +408 -0
  71. package/src/tokens/dynamic-colors.ts +154 -0
  72. package/src/tokens/primitive-colors.ts +237 -0
  73. package/src/tokens/semantic-colors.ts +574 -0
  74. package/src/utils.ts +58 -0
  75. package/tailwind.config.ts +7 -0
  76. package/tsconfig.json +17 -0
  77. 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"