@rebasepro/ui 0.17.3 → 0.18.1

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 (127) hide show
  1. package/README.md +4 -0
  2. package/dist/components/MultiSelect.d.ts +10 -0
  3. package/dist/index.es.js +2 -1
  4. package/dist/index.es.js.map +1 -1
  5. package/package.json +32 -23
  6. package/src/components/Alert.tsx +0 -118
  7. package/src/components/Autocomplete.tsx +0 -93
  8. package/src/components/Avatar.tsx +0 -73
  9. package/src/components/Badge.tsx +0 -44
  10. package/src/components/BooleanSwitch.tsx +0 -116
  11. package/src/components/BooleanSwitchWithLabel.tsx +0 -120
  12. package/src/components/Button.tsx +0 -110
  13. package/src/components/Card.tsx +0 -43
  14. package/src/components/CenteredView.tsx +0 -35
  15. package/src/components/Checkbox.tsx +0 -129
  16. package/src/components/Chip.tsx +0 -131
  17. package/src/components/CircularProgress.tsx +0 -53
  18. package/src/components/CircularProgressCenter.tsx +0 -27
  19. package/src/components/Collapse.tsx +0 -66
  20. package/src/components/ColorPicker.tsx +0 -140
  21. package/src/components/Container.tsx +0 -51
  22. package/src/components/DateTimeField.tsx +0 -363
  23. package/src/components/DebouncedTextField.tsx +0 -76
  24. package/src/components/Dialog.tsx +0 -159
  25. package/src/components/DialogActions.tsx +0 -29
  26. package/src/components/DialogContent.tsx +0 -27
  27. package/src/components/DialogTitle.tsx +0 -41
  28. package/src/components/ErrorBoundary.tsx +0 -236
  29. package/src/components/ExpandablePanel.tsx +0 -130
  30. package/src/components/FileUpload.tsx +0 -103
  31. package/src/components/FilterChip.tsx +0 -89
  32. package/src/components/IconButton.tsx +0 -101
  33. package/src/components/InfoLabel.tsx +0 -23
  34. package/src/components/InputLabel.tsx +0 -40
  35. package/src/components/Label.tsx +0 -32
  36. package/src/components/LoadingButton.tsx +0 -32
  37. package/src/components/Markdown.tsx +0 -55
  38. package/src/components/Menu.tsx +0 -114
  39. package/src/components/Menubar.tsx +0 -355
  40. package/src/components/MultiSelect.tsx +0 -433
  41. package/src/components/Paper.tsx +0 -23
  42. package/src/components/Popover.tsx +0 -162
  43. package/src/components/RadioGroup.tsx +0 -74
  44. package/src/components/ResizablePanels.tsx +0 -189
  45. package/src/components/SearchBar.tsx +0 -136
  46. package/src/components/Select.tsx +0 -326
  47. package/src/components/Separator.tsx +0 -28
  48. package/src/components/Sheet.tsx +0 -135
  49. package/src/components/Skeleton.tsx +0 -54
  50. package/src/components/Slider.tsx +0 -110
  51. package/src/components/Table.tsx +0 -178
  52. package/src/components/Tabs.tsx +0 -185
  53. package/src/components/TextField.tsx +0 -270
  54. package/src/components/TextareaAutosize.tsx +0 -368
  55. package/src/components/ToggleButtonGroup.tsx +0 -69
  56. package/src/components/Tooltip.tsx +0 -153
  57. package/src/components/Typography.tsx +0 -132
  58. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
  59. package/src/components/VirtualTable/VirtualTable.tsx +0 -700
  60. package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
  61. package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
  62. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
  63. package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
  64. package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
  65. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
  66. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
  67. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
  68. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
  69. package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
  70. package/src/components/VirtualTable/index.tsx +0 -10
  71. package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
  72. package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
  73. package/src/components/VirtualTable/types.tsx +0 -47
  74. package/src/components/common/SelectInputLabel.tsx +0 -23
  75. package/src/components/index.tsx +0 -62
  76. package/src/hooks/PortalContainerContext.tsx +0 -50
  77. package/src/hooks/index.ts +0 -7
  78. package/src/hooks/useDebounceCallback.tsx +0 -21
  79. package/src/hooks/useDebounceValue.tsx +0 -20
  80. package/src/hooks/useDebouncedCallback.ts +0 -39
  81. package/src/hooks/useInjectStyles.tsx +0 -31
  82. package/src/hooks/useOutsideAlerter.tsx +0 -46
  83. package/src/icons/GitHubIcon.tsx +0 -21
  84. package/src/icons/HandleIcon.tsx +0 -23
  85. package/src/icons/Icon.tsx +0 -33
  86. package/src/icons/LucideIconByName.tsx +0 -117
  87. package/src/icons/cool_icon_keys.ts +0 -12
  88. package/src/icons/icon_keys.ts +0 -1936
  89. package/src/icons/index.ts +0 -179
  90. package/src/index.css +0 -169
  91. package/src/index.ts +0 -47
  92. package/src/scripts/fixIconSizes.cjs +0 -92
  93. package/src/scripts/fixIconSizesToConst.cjs +0 -106
  94. package/src/scripts/generateIconKeys.ts +0 -41
  95. package/src/scripts/generateIcons.ts +0 -4
  96. package/src/scripts/saveIconFiles.ts +0 -89
  97. package/src/styles.ts +0 -141
  98. package/src/theme.css +0 -168
  99. package/src/util/chip_colors.ts +0 -242
  100. package/src/util/cls.ts +0 -8
  101. package/src/util/debounce.ts +0 -28
  102. package/src/util/hash.ts +0 -11
  103. package/src/util/index.ts +0 -4
  104. package/src/util/key_to_icon_component.ts +0 -11
  105. package/src/util/lazy_chunk.ts +0 -101
  106. package/src/views/CardView.tsx +0 -281
  107. package/src/views/CollectionView/CollectionCardView.tsx +0 -265
  108. package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
  109. package/src/views/CollectionView/CollectionListView.tsx +0 -240
  110. package/src/views/CollectionView/CollectionTableView.tsx +0 -229
  111. package/src/views/CollectionView/CollectionView.tsx +0 -450
  112. package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
  113. package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
  114. package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
  115. package/src/views/CollectionView/index.ts +0 -39
  116. package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
  117. package/src/views/CollectionView/utils.ts +0 -14
  118. package/src/views/Kanban/Board.tsx +0 -384
  119. package/src/views/Kanban/BoardColumn.tsx +0 -138
  120. package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
  121. package/src/views/Kanban/BoardSortableList.tsx +0 -182
  122. package/src/views/Kanban/board_types.ts +0 -80
  123. package/src/views/Kanban/index.tsx +0 -5
  124. package/src/views/Kanban/placement.ts +0 -62
  125. package/src/views/KanbanView.tsx +0 -32
  126. package/src/views/ListView.tsx +0 -296
  127. package/src/views/index.ts +0 -5
package/package.json CHANGED
@@ -1,25 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/ui",
3
- "type": "module",
4
- "version": "0.17.3",
3
+ "version": "0.18.1",
5
4
  "description": "Rebase design system — React components and design tokens built on Radix UI, Tailwind CSS and lucide-react",
6
- "funding": {
7
- "url": "https://github.com/sponsors/rebaseco"
8
- },
9
- "author": "Rebase",
10
- "license": "MIT",
11
- "repository": {
12
- "type": "git",
13
- "url": "https://github.com/rebasepro/rebase.git",
14
- "directory": "packages/ui"
15
- },
16
- "main": "./dist/index.es.js",
17
- "module": "./dist/index.es.js",
18
- "types": "./dist/index.d.ts",
19
- "source": "src/index.ts",
20
- "engines": {
21
- "node": ">=20"
22
- },
23
5
  "keywords": [
24
6
  "rebase",
25
7
  "cms",
@@ -31,11 +13,38 @@
31
13
  "content manager",
32
14
  "react"
33
15
  ],
16
+ "homepage": "https://rebase.pro",
17
+ "bugs": {
18
+ "url": "https://github.com/rebasepro/rebase/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/rebasepro/rebase.git",
23
+ "directory": "packages/ui"
24
+ },
25
+ "funding": {
26
+ "url": "https://github.com/sponsors/rebasepro"
27
+ },
28
+ "license": "MIT",
29
+ "author": "Rebase",
30
+ "type": "module",
31
+ "main": "./dist/index.es.js",
32
+ "module": "./dist/index.es.js",
33
+ "types": "./dist/index.d.ts",
34
+ "//sideEffects": "The stylesheet is the side effect. `index.css` and the CSS the build emits are imported for what they do, not for what they export, so a bundler told this package is side-effect-free would drop them and ship an unstyled admin panel.",
35
+ "sideEffects": [
36
+ "**/*.css",
37
+ "./index.css"
38
+ ],
39
+ "engines": {
40
+ "node": ">=22.22.0"
41
+ },
34
42
  "exports": {
35
43
  ".": {
36
44
  "types": "./dist/index.d.ts",
37
45
  "development": "./dist/index.es.js",
38
- "import": "./dist/index.es.js"
46
+ "import": "./dist/index.es.js",
47
+ "default": "./dist/index.es.js"
39
48
  },
40
49
  "./package.json": "./package.json",
41
50
  "./index.css": "./dist/index.css",
@@ -74,8 +83,8 @@
74
83
  "tailwind-merge": "^3.6.0"
75
84
  },
76
85
  "peerDependencies": {
77
- "react": ">=19.0.0",
78
- "react-dom": ">=19.0.0"
86
+ "react": "^19.2.7",
87
+ "react-dom": "^19.2.7"
79
88
  },
80
89
  "devDependencies": {
81
90
  "@jest/globals": "^30.4.1",
@@ -104,7 +113,6 @@
104
113
  },
105
114
  "files": [
106
115
  "dist",
107
- "src",
108
116
  "index.css"
109
117
  ],
110
118
  "gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
@@ -116,6 +124,7 @@
116
124
  "build": "rm -rf dist || true && vite build && cp dist/src/index.css dist/src/theme.css dist/ && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../tooling/scripts/add-dts-extensions.mjs dist && node ../../tooling/scripts/assert-build-output.mjs",
117
125
  "test:lint": "eslint \"src/**\" --quiet",
118
126
  "test": "jest --passWithNoTests",
127
+ "test:watch": "jest --watch",
119
128
  "clean": "rm -rf dist && find ./src \\( -name '*.js' -o -name '*.d.ts' \\) -type f | xargs rm -f",
120
129
  "generateIcons": "bun --esm src/scripts/generateIcons.ts"
121
130
  }
@@ -1,118 +0,0 @@
1
- import * as React from "react";
2
- import { cls } from "../util";
3
- import { AlertCircleIcon, AlertTriangleIcon, CheckCircleIcon, InfoIcon, XIcon } from "../icons";
4
-
5
- export interface AlertProps {
6
- children: React.ReactNode;
7
- onDismiss?: () => void;
8
- color?: "error" | "warning" | "info" | "success" | "base";
9
- size?: "small" | "medium" | "large";
10
- action?: React.ReactNode;
11
- className?: string;
12
- outerClassName?: string;
13
- style?: React.CSSProperties;
14
- }
15
-
16
- const getSizeClasses = (size: "small" | "medium" | "large") => {
17
- switch (size) {
18
- case "small":
19
- return "px-3 py-1.5 text-xs gap-2 rounded-md";
20
- case "large":
21
- return "px-4 py-3.5 text-sm gap-3 rounded-lg";
22
- case "medium":
23
- default:
24
- return "px-3.5 py-2.5 text-sm gap-2.5 rounded-lg";
25
- }
26
- }
27
-
28
- const getIconSize = (size: "small" | "medium" | "large") => size === "small" ? 16 : 18;
29
-
30
- const getColorClasses = (severity: string) => {
31
- switch (severity) {
32
- case "error":
33
- return "bg-red-500/8 dark:bg-red-500/12 text-red-800 dark:text-red-200 border border-red-500/20 dark:border-red-500/25";
34
- case "warning":
35
- return "bg-amber-500/8 dark:bg-amber-500/12 text-amber-800 dark:text-amber-200 border border-amber-500/20 dark:border-amber-500/25";
36
- case "info":
37
- return "bg-blue-500/8 dark:bg-blue-500/12 text-blue-800 dark:text-blue-200 border border-blue-500/20 dark:border-blue-500/25";
38
- case "success":
39
- return "bg-emerald-500/8 dark:bg-emerald-500/12 text-emerald-800 dark:text-emerald-200 border border-emerald-500/20 dark:border-emerald-500/25";
40
- case "base":
41
- default:
42
- return "bg-surface-accent-500/8 dark:bg-surface-accent-400/10 text-surface-accent-800 dark:text-surface-accent-100 border border-surface-accent-500/15 dark:border-surface-accent-400/20";
43
- }
44
- };
45
-
46
- const getIconColor = (severity: string) => {
47
- switch (severity) {
48
- case "error":
49
- return "text-red-500 dark:text-red-400";
50
- case "warning":
51
- return "text-amber-500 dark:text-amber-400";
52
- case "info":
53
- return "text-blue-500 dark:text-blue-400";
54
- case "success":
55
- return "text-emerald-500 dark:text-emerald-400";
56
- case "base":
57
- default:
58
- return "text-surface-accent-500 dark:text-surface-accent-400";
59
- }
60
- };
61
-
62
- const getIcon = (severity: string, size: number, className: string) => {
63
- switch (severity) {
64
- case "error":
65
- return <AlertCircleIcon size={size} className={className}/>;
66
- case "warning":
67
- return <AlertTriangleIcon size={size} className={className}/>;
68
- case "success":
69
- return <CheckCircleIcon size={size} className={className}/>;
70
- case "info":
71
- case "base":
72
- default:
73
- return <InfoIcon size={size} className={className}/>;
74
- }
75
- };
76
-
77
- export const Alert: React.FC<AlertProps> = ({
78
- children,
79
- onDismiss,
80
- color = "info",
81
- size = "medium",
82
- action,
83
- outerClassName,
84
- className,
85
- style
86
- }) => {
87
- const classes = getColorClasses(color);
88
- const iconSize = getIconSize(size);
89
-
90
- return (
91
- <div
92
- style={style}
93
- className={cls(
94
- getSizeClasses(size),
95
- "w-full",
96
- "font-medium leading-snug",
97
- "flex items-start",
98
- classes,
99
- outerClassName)}>
100
- <span className={cls("shrink-0 mt-px", getIconColor(color))}>
101
- {getIcon(color, iconSize, "")}
102
- </span>
103
- <div className={cls("grow min-w-0 self-center", className)}>{children}</div>
104
- {action}
105
- {onDismiss && (
106
- <button
107
- type="button"
108
- aria-label="Dismiss"
109
- className={cls(
110
- "shrink-0 rounded p-0.5 opacity-60 transition-opacity hover:opacity-100",
111
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-current/40")}
112
- onClick={onDismiss}>
113
- <XIcon size={iconSize - 2}/>
114
- </button>
115
- )}
116
- </div>
117
- );
118
- };
@@ -1,93 +0,0 @@
1
- "use client";
2
- import React from "react";
3
-
4
- import { paperMixin } from "../styles";
5
- import { Collapse } from "./Collapse";
6
- import { cls } from "../util";
7
- import { useOutsideAlerter } from "../hooks";
8
-
9
- export type AutocompleteProps = {
10
- children: React.ReactNode;
11
- open: boolean;
12
- setOpen: (open: boolean) => void;
13
- className?: string;
14
- }
15
-
16
- export const useAutoComplete = ({ ref }: {
17
- ref: React.MutableRefObject<HTMLDivElement | null>;
18
- }) => {
19
-
20
- const [autoCompleteOpen, setAutoCompleteOpen] = React.useState(false);
21
- const [inputFocused, setInputFocused] = React.useState(false);
22
-
23
- // if ref is not focused, close autocomplete
24
- React.useEffect(() => {
25
- if (ref.current) {
26
- ref.current.onfocus = () => {
27
- setAutoCompleteOpen(true);
28
- setInputFocused(true);
29
- }
30
- ref.current.onblur = () => {
31
- setInputFocused(false);
32
- }
33
- }
34
- }, [ref]);
35
-
36
- return {
37
- inputFocused,
38
- autoCompleteOpen,
39
- setAutoCompleteOpen
40
- };
41
- }
42
-
43
- export function Autocomplete({
44
- children,
45
- open,
46
- setOpen,
47
- className
48
- }: AutocompleteProps) {
49
-
50
- const autocompleteRef = React.useRef<HTMLDivElement>(null);
51
- useOutsideAlerter(autocompleteRef, () => setOpen(false));
52
-
53
- return <Collapse
54
- in={open}
55
- duration={30}
56
- className={cls(
57
- "absolute top-full left-0 right-0 overflow-visible",
58
- open ? "shadow-2xs" : "",
59
- "my-2",
60
- "z-20",
61
- "w-full")}>
62
- <div ref={autocompleteRef}
63
- className={cls(
64
- open ? paperMixin : "",
65
- "bg-surface-50 dark:bg-surface-800",
66
- className
67
- )}>
68
- {children}
69
- </div>
70
- </Collapse>;
71
-
72
- }
73
-
74
- export type AutocompleteItemProps = {
75
- children: React.ReactNode,
76
- onClick?: () => void,
77
- className?: string
78
- };
79
-
80
- export const AutocompleteItem = React.memo(function AutocompleteItem({
81
- children,
82
- onClick,
83
- className
84
- }: AutocompleteItemProps) {
85
-
86
- return (
87
- <div
88
- className={cls("flex w-full items-center h-[48px] cursor-pointer transition-colors duration-150 hover:bg-surface-accent-100 dark:hover:bg-surface-accent-800", className)}
89
- onClick={onClick}>
90
- {children}
91
- </div>
92
- )
93
- });
@@ -1,73 +0,0 @@
1
- "use client";
2
- import React, { useState } from "react";
3
- import { cls } from "../util";
4
-
5
- export interface AvatarProps {
6
- src?: string;
7
- alt?: string;
8
- children?: React.ReactNode;
9
- className?: string;
10
- outerClassName?: string;
11
- hover?: boolean;
12
- style?: React.CSSProperties;
13
- }
14
-
15
- const AvatarInner: React.ForwardRefRenderFunction<HTMLButtonElement, AvatarProps> = (
16
- {
17
- src,
18
- alt,
19
- children,
20
- className,
21
- style,
22
- outerClassName,
23
- hover = true,
24
- ...props
25
- },
26
- ref
27
- ) => {
28
- const [isImageError, setIsImageError] = useState(false);
29
-
30
- const handleImageError = () => {
31
- setIsImageError(true);
32
- };
33
-
34
- return (
35
- <button
36
- ref={ref}
37
- style={style}
38
- {...props}
39
- className={cls(
40
- "rounded-full flex items-center justify-center overflow-hidden",
41
- "p-1 w-12 h-12 min-w-12 min-h-12",
42
- hover && "transition-colors duration-150 hover:bg-surface-accent-200 dark:hover:bg-surface-accent-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
43
- outerClassName
44
- )}
45
- >
46
- {src && !isImageError ? (
47
- <img
48
- className={cls(
49
- "bg-surface-accent-100 dark:bg-surface-accent-800",
50
- "w-full h-full object-cover rounded-full",
51
- className
52
- )}
53
- src={src}
54
- alt={alt}
55
- onError={handleImageError}
56
- />
57
- ) : (
58
- <span
59
- className={cls(
60
- "bg-surface-accent-100 dark:bg-surface-accent-800",
61
- "flex items-center justify-center",
62
- "w-full h-full py-1.5 text-lg font-medium text-surface-accent-900 dark:text-text-primary-dark rounded-full",
63
- className
64
- )}
65
- >
66
- {children}
67
- </span>
68
- )}
69
- </button>
70
- );
71
- };
72
-
73
- export const Avatar = React.forwardRef(AvatarInner);
@@ -1,44 +0,0 @@
1
- import React from "react";
2
-
3
- export type BadgeColor = "primary" | "secondary" | "warning" | "error";
4
-
5
- export interface BadgeProps {
6
- color?: BadgeColor;
7
- children: React.ReactNode;
8
- invisible?: boolean;
9
- }
10
-
11
- const getColor = (color: BadgeColor) => {
12
- switch (color) {
13
- case "primary":
14
- return "bg-primary";
15
- case "secondary":
16
- return "bg-secondary";
17
- case "warning":
18
- return "bg-orange-500";
19
- case "error":
20
- return "bg-red-500";
21
- default:
22
- return "bg-surface-accent-300 dark:bg-surface-accent-700";
23
- }
24
- }
25
-
26
- export const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(({
27
- color = "primary",
28
- invisible = false,
29
- children
30
- }, ref) => {
31
- return (
32
- <div ref={ref} className="relative inline-block w-fit">
33
- {children}
34
- <span
35
- className={`absolute top-0.5 right-0.5 transform translate-x-1/2 -translate-y-1/2 rounded-full
36
- ${getColor(color)}
37
- transition-all duration-200 ease-out
38
- ${invisible ? "w-0 h-0" : "w-2 h-2"}`}
39
- />
40
- </div>
41
- );
42
- });
43
-
44
- Badge.displayName = "Badge";
@@ -1,116 +0,0 @@
1
- "use client";
2
- import React from "react";
3
- import { cls } from "../util";
4
-
5
- /**
6
- * Switch geometry, one row per size: track w x h, knob k, and the knob's
7
- * off/on x-offsets. `on` is always `w - k - inset` so the knob is inset by the
8
- * same amount at both ends and vertically centred.
9
- *
10
- * This is a table rather than inline `cls({...})` conditions on purpose. The
11
- * offsets used to be computed object keys — `[value ? "translate-x-[24px]" :
12
- * "translate-x-[3px]"]: size === "large"` — and `large` and `medium` both
13
- * produced the key `translate-x-[3px]` when off. The later literal overwrote
14
- * the earlier, so the off-state class was silently dropped for `large` and the
15
- * knob sat flush against the edge.
16
- */
17
- const SWITCH_GEOMETRY = {
18
- smallest: {
19
- track: "w-[34px] h-[18px] min-w-[34px] min-h-[18px]",
20
- knob: "w-[16px] h-[16px]", off: "translate-x-[1px]", on: "translate-x-[17px]",
21
- bar: "w-[16px] h-[6px]", barX: "translate-x-[9px]"
22
- },
23
- small: {
24
- track: "w-[38px] h-[22px] min-w-[38px] min-h-[22px]",
25
- knob: "w-[18px] h-[18px]", off: "translate-x-[2px]", on: "translate-x-[18px]",
26
- bar: "w-[18px] h-[8px]", barX: "translate-x-[10px]"
27
- },
28
- medium: {
29
- track: "w-[44px] h-[26px] min-w-[44px] min-h-[26px]",
30
- knob: "w-[20px] h-[20px]", off: "translate-x-[3px]", on: "translate-x-[21px]",
31
- bar: "w-[20px] h-[10px]", barX: "translate-x-[12px]"
32
- },
33
- large: {
34
- track: "w-[48px] h-[28px] min-w-[48px] min-h-[28px]",
35
- knob: "w-[22px] h-[22px]", off: "translate-x-[3px]", on: "translate-x-[23px]",
36
- bar: "w-[22px] h-[11px]", barX: "translate-x-[13px]"
37
- }
38
- } as const;
39
-
40
- export type BooleanSwitchProps = {
41
- value: boolean | null;
42
- className?: string;
43
- disabled?: boolean;
44
- size?: "smallest" | "small" | "medium" | "large";
45
- } & ({
46
- allowIndeterminate: true;
47
- onValueChange?: (newValue: boolean | null) => void;
48
- } | {
49
- allowIndeterminate?: false;
50
- onValueChange?: (newValue: boolean) => void;
51
- });
52
-
53
- export const BooleanSwitch = React.forwardRef(function BooleanSwitch({
54
- value,
55
- allowIndeterminate,
56
- className,
57
- onValueChange,
58
- disabled = false,
59
- size = "medium",
60
- ...props
61
- }: BooleanSwitchProps, ref: React.Ref<HTMLButtonElement>) {
62
- return <button
63
- type="button"
64
- role="switch"
65
- aria-checked={allowIndeterminate && (value === null || value === undefined) ? "mixed" : !!value}
66
- aria-disabled={disabled || undefined}
67
- ref={ref}
68
- tabIndex={disabled ? -1 : undefined}
69
- onClick={disabled
70
- ? (e) => e.preventDefault()
71
- : (e) => {
72
- e.preventDefault();
73
- if (allowIndeterminate) {
74
- if (value === null || value === undefined) onValueChange?.(true)
75
- else if (value) onValueChange?.(false)
76
- else onValueChange?.(null);
77
- } else {
78
- onValueChange?.(!value);
79
- }
80
- }}
81
- className={cls(
82
- // `large` used to share the `medium` branch, so the two sizes
83
- // rendered identically and the prop looked inert. Geometry is
84
- // now derived from one table (see SWITCH_GEOMETRY) so the knob
85
- // inset is symmetric at every size.
86
- SWITCH_GEOMETRY[size].track,
87
- "outline-none outline-hidden rounded-full relative shadow-sm",
88
- value ? (disabled
89
- ? "bg-white bg-opacity-54 bg-white/54 dark:bg-surface-accent-950 border-surface-accent-100 dark:border-surface-accent-700 ring-1 ring-surface-accent-200 dark:ring-surface-accent-700"
90
- : "ring-secondary ring-1 bg-secondary dark:bg-secondary") : "bg-white bg-opacity-54 bg-white/54 dark:bg-surface-accent-900 ring-1 ring-surface-accent-200 dark:ring-surface-accent-700",
91
- className
92
- )}
93
- {...props}
94
- >
95
- {allowIndeterminate && (value === null || value === undefined) && <div
96
- key={"knob"}
97
- className={cls(
98
- "block rounded-full transition-transform duration-100 ease-out transform will-change-auto shadow-sm",
99
- disabled ? "bg-surface-accent-400 dark:bg-surface-accent-600" : "bg-surface-accent-400 dark:bg-surface-accent-600",
100
- SWITCH_GEOMETRY[size].bar,
101
- SWITCH_GEOMETRY[size].barX
102
- )}
103
- />}
104
-
105
- {!(allowIndeterminate && (value === null || value === undefined)) && <div
106
- key={"knob"}
107
- className={cls(
108
- "block rounded-full transition-transform duration-100 ease-out transform will-change-auto shadow-sm",
109
- disabled ? "bg-surface-accent-300 dark:bg-surface-accent-700" : (value ? "bg-white shadow" : "bg-surface-accent-600 dark:bg-surface-accent-400"),
110
- SWITCH_GEOMETRY[size].knob,
111
- value ? SWITCH_GEOMETRY[size].on : SWITCH_GEOMETRY[size].off
112
- )}
113
- />}
114
- </button>;
115
- }
116
- ) as React.ForwardRefExoticComponent<BooleanSwitchProps & React.RefAttributes<HTMLButtonElement>>;
@@ -1,120 +0,0 @@
1
- "use client";
2
- import React from "react";
3
- import {
4
- controlHeightMixin,
5
- fieldBackgroundDisabledMixin,
6
- fieldBackgroundHoverMixin,
7
- fieldBackgroundMixin,
8
- focusedClasses
9
- } from "../styles";
10
- import { BooleanSwitch, BooleanSwitchProps } from "./BooleanSwitch";
11
- import { cls } from "../util";
12
-
13
- export type BooleanSwitchWithLabelProps = BooleanSwitchProps & {
14
- position?: "start" | "end",
15
- invisible?: boolean,
16
- label?: React.ReactNode,
17
- error?: boolean,
18
- autoFocus?: boolean,
19
- fullWidth?: boolean,
20
- className?: string,
21
- inputClassName?: string,
22
- };
23
-
24
- /**
25
- * Simple boolean switch.
26
- *
27
- */
28
- export const BooleanSwitchWithLabel = function BooleanSwitchWithLabel({
29
- value,
30
- position = "end",
31
- size = "medium",
32
- invisible,
33
- onValueChange,
34
- error,
35
- label,
36
- autoFocus,
37
- disabled,
38
- className,
39
- fullWidth = true,
40
- inputClassName,
41
- ...props
42
- }: BooleanSwitchWithLabelProps) {
43
-
44
- const ref = React.useRef<HTMLDivElement | null>(null);
45
- const refInput = React.useRef<HTMLButtonElement | null>(null);
46
- const switchLabelId = React.useId();
47
- const [_, setFocused] = React.useState(autoFocus)
48
- const onFocus = () => setFocused(true);
49
- const onBlur = () => setFocused(false);
50
-
51
- React.useEffect(() => {
52
- if (autoFocus) {
53
- // refInput.current?.focus();
54
- }
55
- }, []);
56
-
57
- const focus = document.activeElement === refInput?.current || document.activeElement === ref?.current
58
-
59
- return (
60
- <div
61
- ref={ref}
62
- onFocus={onFocus}
63
- onBlur={onBlur}
64
- role="switch"
65
- aria-checked={props.allowIndeterminate && (value === null || value === undefined) ? "mixed" : !!value}
66
- aria-disabled={disabled || undefined}
67
- aria-labelledby={label ? switchLabelId : undefined}
68
- tabIndex={-1}
69
- className={cls(
70
- !invisible && fieldBackgroundMixin,
71
- !invisible && (disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin),
72
- disabled ? "cursor-default" : "cursor-pointer",
73
- "rounded-lg max-w-full justify-between box-border relative inline-flex items-center",
74
- !invisible && focus && !disabled ? focusedClasses : "",
75
- error ? "text-red-500 dark:text-red-600" : (focus && !disabled ? "text-primary" : (!disabled ? "text-text-primary dark:text-text-primary-dark" : "text-text-secondary dark:text-text-secondary-dark")),
76
- // The one control size scale — 28/32/40/48. This component still
77
- // carried its own map (…/44/64), so a switch at `large` stood
78
- // 16px taller than the text field beside it, which read as a
79
- // giant empty box once the label moved out of the control.
80
- controlHeightMixin[size],
81
- size === "small" || size === "smallest" ? "pl-2" : "pl-4",
82
- size === "small" || size === "smallest" ? "pr-4" : "pr-6",
83
- position === "end" ? "flex-row-reverse" : "flex-row",
84
- fullWidth ? "w-full" : "",
85
- className
86
- )}
87
- onClick={disabled ? undefined : (e) => {
88
- if (props.allowIndeterminate) {
89
- const onChange = onValueChange as ((newValue: boolean | null) => void) | undefined;
90
- if (value === null || value === undefined) onChange?.(true)
91
- else if (value) onChange?.(false)
92
- else onChange?.(null);
93
- } else {
94
- onValueChange?.(!value);
95
- }
96
- // refInput.current?.focus();
97
- }}
98
- >
99
-
100
- <BooleanSwitch
101
- value={value}
102
- ref={refInput}
103
- size={size}
104
- className={cls(invisible && focus ? focusedClasses : "", inputClassName)}
105
- disabled={disabled}
106
- {...props}
107
- />
108
-
109
- <div id={switchLabelId} className={cls(
110
- "grow",
111
- position === "end" ? "mr-4" : "ml-4",
112
- size === "small" || size === "smallest" ? "text-sm" : "text-base"
113
- )}>
114
- {label}
115
- </div>
116
-
117
- </div>
118
-
119
- );
120
- };