@kosmeo-ui/react 0.1.1 → 0.2.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/dist/components/buttons/button/index.d.ts +2 -0
- package/dist/components/buttons/button/index.d.ts.map +1 -1
- package/dist/components/buttons/button/index.js +2 -2
- package/dist/components/buttons/button/index.js.map +1 -1
- package/dist/components/buttons/button/styles.css +6 -4
- package/dist/components/buttons/rowActions/styles.css +7 -0
- package/dist/components/data-display/addressBlock/styles.css +2 -4
- package/dist/components/data-display/dataTable/styles.css +15 -0
- package/dist/components/data-display/infiniteScroll/filters/searchFilter.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/filters/searchFilter.js +4 -35
- package/dist/components/data-display/infiniteScroll/filters/searchFilter.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/filters/styles.css +4 -0
- package/dist/components/data-display/infiniteScroll/header/deleteButton.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/header/deleteButton.js +1 -1
- package/dist/components/data-display/infiniteScroll/header/deleteButton.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/header/index.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/header/index.js +1 -1
- package/dist/components/data-display/infiniteScroll/header/index.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/table/index.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/table/index.js +2 -2
- package/dist/components/data-display/infiniteScroll/table/index.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/index.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/index.js +1 -6
- package/dist/components/data-display/infiniteScroll/tags/index.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/listFilterTag.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/listFilterTag.js +3 -3
- package/dist/components/data-display/infiniteScroll/tags/listFilterTag.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/rangeFilterTag.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/rangeFilterTag.js +3 -13
- package/dist/components/data-display/infiniteScroll/tags/rangeFilterTag.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/selectFilterTag.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/selectFilterTag.js +3 -5
- package/dist/components/data-display/infiniteScroll/tags/selectFilterTag.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/styles.css +1 -29
- package/dist/components/data-display/recoveryCodeGrid/styles.css +1 -2
- package/dist/components/data-display/statCard/styles.css +1 -1
- package/dist/components/data-display/table/styles.css +12 -1
- package/dist/components/data-display/tagCard/index.d.ts.map +1 -1
- package/dist/components/data-display/tagCard/index.js +1 -1
- package/dist/components/data-display/tagCard/index.js.map +1 -1
- package/dist/components/feedback/badge/styles.css +8 -6
- package/dist/components/feedback/emptyState/styles.css +2 -4
- package/dist/components/feedback/progressBar/index.d.ts.map +1 -1
- package/dist/components/feedback/progressBar/index.js +7 -2
- package/dist/components/feedback/progressBar/index.js.map +1 -1
- package/dist/components/feedback/progressBar/styles.css +15 -6
- package/dist/components/feedback/spinner/styles.css +0 -13
- package/dist/components/forms/fieldShell/styles.css +1 -1
- package/dist/components/forms/formActions/styles.css +1 -1
- package/dist/components/forms/infoRow/styles.css +1 -2
- package/dist/components/forms/requiredAsterisk/styles.css +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/inputs/checkbox/index.d.ts +6 -1
- package/dist/components/inputs/checkbox/index.d.ts.map +1 -1
- package/dist/components/inputs/checkbox/index.js +9 -2
- package/dist/components/inputs/checkbox/index.js.map +1 -1
- package/dist/components/inputs/checkbox/styles.css +8 -0
- package/dist/components/inputs/colorPicker/styles.css +1 -1
- package/dist/components/inputs/dateInput/index.d.ts +3 -3
- package/dist/components/inputs/dateInput/index.d.ts.map +1 -1
- package/dist/components/inputs/dateInput/index.js +13 -3
- package/dist/components/inputs/dateInput/index.js.map +1 -1
- package/dist/components/inputs/dropdown/index.d.ts +2 -0
- package/dist/components/inputs/dropdown/index.d.ts.map +1 -1
- package/dist/components/inputs/dropdown/index.js +1 -1
- package/dist/components/inputs/dropdown/index.js.map +1 -1
- package/dist/components/inputs/dropdownArrowButton/index.d.ts.map +1 -1
- package/dist/components/inputs/dropdownArrowButton/index.js +1 -1
- package/dist/components/inputs/dropdownArrowButton/index.js.map +1 -1
- package/dist/components/inputs/dropdownArrowButton/styles.css +1 -1
- package/dist/components/inputs/input/index.d.ts +5 -1
- package/dist/components/inputs/input/index.d.ts.map +1 -1
- package/dist/components/inputs/input/index.js +25 -4
- package/dist/components/inputs/input/index.js.map +1 -1
- package/dist/components/inputs/input/styles.css +46 -2
- package/dist/components/inputs/multiSelect/index.d.ts.map +1 -1
- package/dist/components/inputs/multiSelect/index.js +1 -1
- package/dist/components/inputs/multiSelect/index.js.map +1 -1
- package/dist/components/inputs/multiSelect/styles.css +1 -1
- package/dist/components/inputs/phoneInput/index.d.ts.map +1 -1
- package/dist/components/inputs/phoneInput/index.js +1 -1
- package/dist/components/inputs/phoneInput/index.js.map +1 -1
- package/dist/components/inputs/richTextEditor/index.d.ts.map +1 -1
- package/dist/components/inputs/richTextEditor/index.js +14 -3
- package/dist/components/inputs/richTextEditor/index.js.map +1 -1
- package/dist/components/layout/borderedSection/index.d.ts +3 -1
- package/dist/components/layout/borderedSection/index.d.ts.map +1 -1
- package/dist/components/layout/borderedSection/index.js +2 -2
- package/dist/components/layout/borderedSection/index.js.map +1 -1
- package/dist/components/layout/card/styles.css +22 -0
- package/dist/components/layout/sectionHeader/styles.css +1 -2
- package/dist/components/navigation/pagination/index.d.ts +23 -0
- package/dist/components/navigation/pagination/index.d.ts.map +1 -0
- package/dist/components/navigation/pagination/index.js +29 -0
- package/dist/components/navigation/pagination/index.js.map +1 -0
- package/dist/components/navigation/pagination/styles.css +113 -0
- package/dist/components/navigation/pagination/utils.d.ts +9 -0
- package/dist/components/navigation/pagination/utils.d.ts.map +1 -0
- package/dist/components/navigation/pagination/utils.js +25 -0
- package/dist/components/navigation/pagination/utils.js.map +1 -0
- package/dist/components/navigation/sidebar/styles.css +9 -1
- package/dist/components/navigation/tabButton/styles.css +6 -6
- package/dist/components/navigation/tabs/styles.css +2 -2
- package/dist/components/overlays/confirmationModal/index.d.ts +5 -0
- package/dist/components/overlays/confirmationModal/index.d.ts.map +1 -1
- package/dist/components/overlays/confirmationModal/index.js +23 -4
- package/dist/components/overlays/confirmationModal/index.js.map +1 -1
- package/dist/components/overlays/confirmationModal/styles.css +3 -0
- package/dist/components/overlays/modal/index.d.ts +2 -0
- package/dist/components/overlays/modal/index.d.ts.map +1 -1
- package/dist/components/overlays/modal/index.js +13 -4
- package/dist/components/overlays/modal/index.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/usePaginated.d.ts +43 -0
- package/dist/hooks/usePaginated.d.ts.map +1 -0
- package/dist/hooks/usePaginated.js +60 -0
- package/dist/hooks/usePaginated.js.map +1 -0
- package/dist/styles.css +37 -14
- package/package.json +1 -1
- package/src/components/buttons/button/index.tsx +14 -5
- package/src/components/buttons/button/styles.css +6 -4
- package/src/components/buttons/rowActions/styles.css +7 -0
- package/src/components/data-display/addressBlock/styles.css +2 -4
- package/src/components/data-display/dataTable/styles.css +15 -0
- package/src/components/data-display/infiniteScroll/filters/searchFilter.tsx +16 -65
- package/src/components/data-display/infiniteScroll/filters/styles.css +4 -0
- package/src/components/data-display/infiniteScroll/header/deleteButton.tsx +1 -0
- package/src/components/data-display/infiniteScroll/header/index.tsx +4 -0
- package/src/components/data-display/infiniteScroll/table/index.tsx +2 -0
- package/src/components/data-display/infiniteScroll/tags/index.tsx +1 -11
- package/src/components/data-display/infiniteScroll/tags/listFilterTag.tsx +12 -16
- package/src/components/data-display/infiniteScroll/tags/rangeFilterTag.tsx +4 -22
- package/src/components/data-display/infiniteScroll/tags/selectFilterTag.tsx +4 -11
- package/src/components/data-display/infiniteScroll/tags/styles.css +1 -29
- package/src/components/data-display/recoveryCodeGrid/styles.css +1 -2
- package/src/components/data-display/statCard/styles.css +1 -1
- package/src/components/data-display/table/styles.css +12 -1
- package/src/components/data-display/tagCard/index.tsx +5 -3
- package/src/components/feedback/badge/styles.css +8 -6
- package/src/components/feedback/emptyState/styles.css +2 -4
- package/src/components/feedback/progressBar/index.tsx +14 -8
- package/src/components/feedback/progressBar/styles.css +15 -6
- package/src/components/feedback/spinner/styles.css +0 -13
- package/src/components/forms/fieldShell/styles.css +1 -1
- package/src/components/forms/formActions/styles.css +1 -1
- package/src/components/forms/infoRow/styles.css +1 -2
- package/src/components/forms/requiredAsterisk/styles.css +1 -1
- package/src/components/index.ts +2 -0
- package/src/components/inputs/checkbox/index.tsx +24 -4
- package/src/components/inputs/checkbox/styles.css +8 -0
- package/src/components/inputs/colorPicker/styles.css +1 -1
- package/src/components/inputs/dateInput/index.tsx +13 -4
- package/src/components/inputs/dropdown/index.tsx +4 -0
- package/src/components/inputs/dropdownArrowButton/index.tsx +1 -0
- package/src/components/inputs/dropdownArrowButton/styles.css +1 -1
- package/src/components/inputs/input/index.tsx +47 -4
- package/src/components/inputs/input/styles.css +46 -2
- package/src/components/inputs/multiSelect/index.tsx +1 -0
- package/src/components/inputs/multiSelect/styles.css +1 -1
- package/src/components/inputs/phoneInput/index.tsx +6 -2
- package/src/components/inputs/richTextEditor/index.tsx +27 -3
- package/src/components/layout/borderedSection/index.tsx +4 -1
- package/src/components/layout/card/styles.css +22 -0
- package/src/components/layout/sectionHeader/styles.css +1 -2
- package/src/components/navigation/pagination/index.tsx +134 -0
- package/src/components/navigation/pagination/styles.css +113 -0
- package/src/components/navigation/pagination/utils.ts +32 -0
- package/src/components/navigation/sidebar/styles.css +9 -1
- package/src/components/navigation/tabButton/styles.css +6 -6
- package/src/components/navigation/tabs/styles.css +2 -2
- package/src/components/overlays/confirmationModal/index.tsx +37 -1
- package/src/components/overlays/confirmationModal/styles.css +3 -0
- package/src/components/overlays/modal/index.tsx +23 -4
- package/src/hooks/index.ts +4 -0
- package/src/hooks/usePaginated.ts +123 -0
- package/src/styles.css +37 -14
- package/dist/components/data-display/infiniteScroll/tags/searchFilterTag.d.ts +0 -10
- package/dist/components/data-display/infiniteScroll/tags/searchFilterTag.d.ts.map +0 -1
- package/dist/components/data-display/infiniteScroll/tags/searchFilterTag.js +0 -22
- package/dist/components/data-display/infiniteScroll/tags/searchFilterTag.js.map +0 -1
- package/src/components/data-display/infiniteScroll/tags/searchFilterTag.tsx +0 -41
|
@@ -50,8 +50,12 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
50
50
|
const [charCount, setCharCount] = useState(0);
|
|
51
51
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
// Stable across renders: the editor reads labelId once, when it's created.
|
|
54
|
+
const [generatedId] = useState(
|
|
55
|
+
() => `richtext-${Math.random().toString(36).substring(2, 9)}`
|
|
56
|
+
);
|
|
57
|
+
const fieldId = props.id || generatedId;
|
|
58
|
+
const labelId = `${fieldId}-label`;
|
|
55
59
|
|
|
56
60
|
const cleanHtml = (html: string): string | undefined => {
|
|
57
61
|
// Remove trailing empty <p></p> tags
|
|
@@ -79,6 +83,14 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
79
83
|
}),
|
|
80
84
|
],
|
|
81
85
|
content: props.value,
|
|
86
|
+
editorProps: {
|
|
87
|
+
attributes: {
|
|
88
|
+
"aria-multiline": "true",
|
|
89
|
+
...(props.label
|
|
90
|
+
? { "aria-labelledby": labelId }
|
|
91
|
+
: { "aria-label": props.placeholder ?? "Rich text editor" }),
|
|
92
|
+
},
|
|
93
|
+
},
|
|
82
94
|
editable: !disabled,
|
|
83
95
|
autofocus: props.autofocus,
|
|
84
96
|
onUpdate: ({ editor }) => {
|
|
@@ -146,7 +158,7 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
146
158
|
<div className={`k-richtext-container k-richtext-container-${size}`}>
|
|
147
159
|
{props.label && (
|
|
148
160
|
<div className="k-richtext-label-wrapper">
|
|
149
|
-
<label
|
|
161
|
+
<label id={labelId} className="k-richtext-label">
|
|
150
162
|
{props.label}
|
|
151
163
|
{props.required && <MandatoryAsterisk />}
|
|
152
164
|
</label>
|
|
@@ -165,6 +177,8 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
165
177
|
<Tooltip content="Bold" position="top">
|
|
166
178
|
<IconButton
|
|
167
179
|
icon={faBold}
|
|
180
|
+
aria-label="Bold"
|
|
181
|
+
aria-pressed={editorState.isBold}
|
|
168
182
|
size={size}
|
|
169
183
|
variant={editorState.isBold ? "primary" : "secondary"}
|
|
170
184
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
@@ -174,6 +188,8 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
174
188
|
<Tooltip content="Italic" position="top">
|
|
175
189
|
<IconButton
|
|
176
190
|
icon={faItalic}
|
|
191
|
+
aria-label="Italic"
|
|
192
|
+
aria-pressed={editorState.isItalic}
|
|
177
193
|
size={size}
|
|
178
194
|
variant={editorState.isItalic ? "primary" : "secondary"}
|
|
179
195
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
@@ -183,6 +199,8 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
183
199
|
<Tooltip content="Underline" position="top">
|
|
184
200
|
<IconButton
|
|
185
201
|
icon={faUnderline}
|
|
202
|
+
aria-label="Underline"
|
|
203
|
+
aria-pressed={editorState.isUnderlined}
|
|
186
204
|
size={size}
|
|
187
205
|
variant={editorState.isUnderlined ? "primary" : "secondary"}
|
|
188
206
|
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
|
@@ -193,6 +211,8 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
193
211
|
<Tooltip content="Bullet List" position="top">
|
|
194
212
|
<IconButton
|
|
195
213
|
icon={faListUl}
|
|
214
|
+
aria-label="Bullet list"
|
|
215
|
+
aria-pressed={editorState.isBulletedList}
|
|
196
216
|
size={size}
|
|
197
217
|
variant={editorState.isBulletedList ? "primary" : "secondary"}
|
|
198
218
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
|
@@ -202,6 +222,8 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
202
222
|
<Tooltip content="Numbered List" position="top">
|
|
203
223
|
<IconButton
|
|
204
224
|
icon={faListOl}
|
|
225
|
+
aria-label="Numbered list"
|
|
226
|
+
aria-pressed={editorState.isNumberedList}
|
|
205
227
|
size={size}
|
|
206
228
|
variant={editorState.isNumberedList ? "primary" : "secondary"}
|
|
207
229
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
|
@@ -212,6 +234,7 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
212
234
|
<Tooltip content="Clear Formatting" position="top">
|
|
213
235
|
<IconButton
|
|
214
236
|
icon={faTextSlash}
|
|
237
|
+
aria-label="Clear formatting"
|
|
215
238
|
size={size}
|
|
216
239
|
variant="secondary"
|
|
217
240
|
onClick={() => {
|
|
@@ -234,6 +257,7 @@ const RichTextEditor = (props: RichTextEditorProps) => {
|
|
|
234
257
|
>
|
|
235
258
|
<IconButton
|
|
236
259
|
icon={faCode}
|
|
260
|
+
aria-label={showHtml ? "Show editor" : "Show HTML"}
|
|
237
261
|
size={size}
|
|
238
262
|
variant={showHtml ? "primary" : "secondary"}
|
|
239
263
|
onClick={() => setShowHtml(!showHtml)}
|
|
@@ -7,6 +7,8 @@ export interface BorderedSectionProps {
|
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
/** Optional title shown above the body. */
|
|
9
9
|
title?: ReactNode;
|
|
10
|
+
/** Heading element for the title, to fit the page's heading outline. Defaults to `h3`. Looks the same at every level. */
|
|
11
|
+
as?: "h2" | "h3" | "h4";
|
|
10
12
|
/** Right-aligned actions in the title row. Implies a title row even when `title` is empty. */
|
|
11
13
|
actions?: ReactNode;
|
|
12
14
|
/** Body layout. `"stack"` (default) stacks children; `"row"` lays them out as a horizontal flex strip. */
|
|
@@ -20,6 +22,7 @@ export interface BorderedSectionProps {
|
|
|
20
22
|
const BorderedSection = ({
|
|
21
23
|
children,
|
|
22
24
|
title,
|
|
25
|
+
as: Heading = "h3",
|
|
23
26
|
actions,
|
|
24
27
|
layout = "stack",
|
|
25
28
|
gap,
|
|
@@ -40,7 +43,7 @@ const BorderedSection = ({
|
|
|
40
43
|
{showHeader && (
|
|
41
44
|
<div className="k-bordered-section-header">
|
|
42
45
|
{title !== undefined && (
|
|
43
|
-
<
|
|
46
|
+
<Heading className="k-bordered-section-title">{title}</Heading>
|
|
44
47
|
)}
|
|
45
48
|
{actions !== undefined && (
|
|
46
49
|
<div className="k-bordered-section-actions">{actions}</div>
|
|
@@ -76,8 +76,30 @@
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/* Padding Variants */
|
|
79
|
+
/* Edge-to-edge content (tables, images): clip it to the rounded corners, and
|
|
80
|
+
give the header and footer their own padding instead. */
|
|
79
81
|
.k-card-padding-none {
|
|
80
82
|
padding: 0;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.k-card-padding-none .k-card-header {
|
|
87
|
+
padding: 0.75rem 1rem;
|
|
88
|
+
margin-bottom: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.k-card-padding-none .k-card-footer {
|
|
92
|
+
padding: 0.75rem 1rem;
|
|
93
|
+
margin-top: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* The card already draws the outer edge and the line under the header. */
|
|
97
|
+
.k-card-padding-none .k-card-body > .k-table {
|
|
98
|
+
border: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.k-card-padding-none .k-card-body > .k-table thead th {
|
|
102
|
+
border-top: none;
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
.k-card-padding-small {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
4
|
+
import Select from "../../inputs/select/index.js";
|
|
5
|
+
import { PropsWithSize } from "../../types.js";
|
|
6
|
+
import { pageRange } from "./utils.js";
|
|
7
|
+
import { useId } from "react";
|
|
8
|
+
import "./styles.css";
|
|
9
|
+
import {
|
|
10
|
+
faChevronLeft,
|
|
11
|
+
faChevronRight,
|
|
12
|
+
} from "@fortawesome/free-solid-svg-icons";
|
|
13
|
+
|
|
14
|
+
export interface PaginationProps extends PropsWithSize {
|
|
15
|
+
/** Current page, starting at 1. */
|
|
16
|
+
page: number;
|
|
17
|
+
/** Total number of pages. */
|
|
18
|
+
pageCount: number;
|
|
19
|
+
onPageChange: (page: number) => void;
|
|
20
|
+
/** Total item count. With `pageSize`, shows a "51–75 of 240" summary. */
|
|
21
|
+
total?: number;
|
|
22
|
+
/** Items per page. Needed for the summary and the page-size picker. */
|
|
23
|
+
pageSize?: number;
|
|
24
|
+
/** Page sizes to offer. With `onPageSizeChange`, shows a "Rows per page" picker. */
|
|
25
|
+
pageSizeOptions?: number[];
|
|
26
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
27
|
+
/** Pages shown either side of the current page before a "…" gap. Defaults to 1. */
|
|
28
|
+
siblingCount?: number;
|
|
29
|
+
/** Accessible name for the navigation landmark. Defaults to "Pagination". */
|
|
30
|
+
"aria-label"?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const Pagination = ({
|
|
34
|
+
page,
|
|
35
|
+
pageCount,
|
|
36
|
+
onPageChange,
|
|
37
|
+
total,
|
|
38
|
+
pageSize,
|
|
39
|
+
pageSizeOptions,
|
|
40
|
+
onPageSizeChange,
|
|
41
|
+
siblingCount = 1,
|
|
42
|
+
size = "md",
|
|
43
|
+
"aria-label": ariaLabel = "Pagination",
|
|
44
|
+
}: PaginationProps) => {
|
|
45
|
+
const pageSizeId = useId();
|
|
46
|
+
const lastPage = Math.max(pageCount, 1);
|
|
47
|
+
const current = Math.min(Math.max(page, 1), lastPage);
|
|
48
|
+
|
|
49
|
+
const goTo = (target: number) => {
|
|
50
|
+
if (target >= 1 && target <= lastPage && target !== current) {
|
|
51
|
+
onPageChange(target);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const summary =
|
|
56
|
+
total !== undefined && pageSize !== undefined
|
|
57
|
+
? total === 0
|
|
58
|
+
? "No results"
|
|
59
|
+
: `${(current - 1) * pageSize + 1}–${Math.min(current * pageSize, total)} of ${total.toLocaleString()}`
|
|
60
|
+
: undefined;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<nav className={`k-pagination k-pagination-${size}`} aria-label={ariaLabel}>
|
|
64
|
+
{summary && (
|
|
65
|
+
<p className="k-pagination-summary" aria-live="polite">
|
|
66
|
+
{summary}
|
|
67
|
+
</p>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
<ul className="k-pagination-pages">
|
|
71
|
+
<li>
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
className="k-pagination-button"
|
|
75
|
+
onClick={() => goTo(current - 1)}
|
|
76
|
+
disabled={current <= 1}
|
|
77
|
+
aria-label="Previous page"
|
|
78
|
+
>
|
|
79
|
+
<FontAwesomeIcon icon={faChevronLeft} aria-hidden="true" />
|
|
80
|
+
</button>
|
|
81
|
+
</li>
|
|
82
|
+
{pageRange(current, lastPage, siblingCount).map((item) =>
|
|
83
|
+
typeof item === "number" ? (
|
|
84
|
+
<li key={item}>
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
className={`k-pagination-button${item === current ? " k-pagination-button-current" : ""}`}
|
|
88
|
+
onClick={() => goTo(item)}
|
|
89
|
+
aria-label={`Page ${item}`}
|
|
90
|
+
aria-current={item === current ? "page" : undefined}
|
|
91
|
+
>
|
|
92
|
+
{item}
|
|
93
|
+
</button>
|
|
94
|
+
</li>
|
|
95
|
+
) : (
|
|
96
|
+
<li key={item} className="k-pagination-gap" aria-hidden="true">
|
|
97
|
+
…
|
|
98
|
+
</li>
|
|
99
|
+
)
|
|
100
|
+
)}
|
|
101
|
+
<li>
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
className="k-pagination-button"
|
|
105
|
+
onClick={() => goTo(current + 1)}
|
|
106
|
+
disabled={current >= lastPage}
|
|
107
|
+
aria-label="Next page"
|
|
108
|
+
>
|
|
109
|
+
<FontAwesomeIcon icon={faChevronRight} aria-hidden="true" />
|
|
110
|
+
</button>
|
|
111
|
+
</li>
|
|
112
|
+
</ul>
|
|
113
|
+
|
|
114
|
+
{pageSizeOptions && onPageSizeChange && (
|
|
115
|
+
<div className="k-pagination-page-size">
|
|
116
|
+
<label htmlFor={pageSizeId}>Rows per page</label>
|
|
117
|
+
{/* Select works in strings. */}
|
|
118
|
+
<Select
|
|
119
|
+
id={pageSizeId}
|
|
120
|
+
size="sm"
|
|
121
|
+
value={pageSize === undefined ? undefined : String(pageSize)}
|
|
122
|
+
options={pageSizeOptions.map((n) => ({
|
|
123
|
+
value: String(n),
|
|
124
|
+
label: String(n),
|
|
125
|
+
}))}
|
|
126
|
+
onChange={(n) => n !== undefined && onPageSizeChange(Number(n))}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</nav>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export default Pagination;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
.k-pagination {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
gap: 0.75rem 1.5rem;
|
|
7
|
+
color: var(--text-color);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.k-pagination-summary {
|
|
11
|
+
margin: 0;
|
|
12
|
+
color: var(--text-color-secondary);
|
|
13
|
+
font-variant-numeric: tabular-nums;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.k-pagination-pages {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 0.25rem;
|
|
20
|
+
margin: 0;
|
|
21
|
+
padding: 0;
|
|
22
|
+
list-style: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.k-pagination-button {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
border: 0.0625rem solid var(--border-color);
|
|
30
|
+
border-radius: 0.375rem;
|
|
31
|
+
background-color: var(--background-color-light);
|
|
32
|
+
color: var(--text-color);
|
|
33
|
+
font-variant-numeric: tabular-nums;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition:
|
|
36
|
+
background-color 0.15s ease-in-out,
|
|
37
|
+
border-color 0.15s ease-in-out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.k-pagination-button:not(:disabled):hover {
|
|
41
|
+
background-color: var(--background-color-hover);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.k-pagination-button:focus-visible {
|
|
45
|
+
outline: 0.125rem solid var(--border-color-active);
|
|
46
|
+
outline-offset: 0.125rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.k-pagination-button-current,
|
|
50
|
+
.k-pagination-button-current:not(:disabled):hover {
|
|
51
|
+
border-color: var(--primary-color);
|
|
52
|
+
background-color: var(--primary-color);
|
|
53
|
+
color: var(--text-color-light);
|
|
54
|
+
font-weight: 600;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.k-pagination-button:disabled {
|
|
58
|
+
cursor: not-allowed;
|
|
59
|
+
opacity: 0.5;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.k-pagination-gap {
|
|
63
|
+
color: var(--text-color-secondary);
|
|
64
|
+
text-align: center;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.k-pagination-page-size {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 0.5rem;
|
|
71
|
+
color: var(--text-color-secondary);
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Sizes: every button stays at least 32px, above the 24px target minimum. */
|
|
76
|
+
.k-pagination-sm {
|
|
77
|
+
font-size: var(--text-size-sm);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.k-pagination-sm .k-pagination-button,
|
|
81
|
+
.k-pagination-sm .k-pagination-gap {
|
|
82
|
+
min-width: 2rem;
|
|
83
|
+
height: 2rem;
|
|
84
|
+
padding: 0 0.5rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.k-pagination-md {
|
|
88
|
+
font-size: var(--text-size-sm);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.k-pagination-md .k-pagination-button,
|
|
92
|
+
.k-pagination-md .k-pagination-gap {
|
|
93
|
+
min-width: 2.25rem;
|
|
94
|
+
height: 2.25rem;
|
|
95
|
+
padding: 0 0.625rem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.k-pagination-lg {
|
|
99
|
+
font-size: var(--text-size-md);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.k-pagination-lg .k-pagination-button,
|
|
103
|
+
.k-pagination-lg .k-pagination-gap {
|
|
104
|
+
min-width: 2.75rem;
|
|
105
|
+
height: 2.75rem;
|
|
106
|
+
padding: 0 0.75rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.k-pagination-gap {
|
|
110
|
+
display: inline-flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
justify-content: center;
|
|
113
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type PageRangeItem = number | "start-gap" | "end-gap";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The page buttons to show: always the first and last page, the current page
|
|
5
|
+
* with `siblings` neighbours either side, and a gap marker wherever pages are
|
|
6
|
+
* skipped. The list is the same length on every page, so the control doesn't
|
|
7
|
+
* jump around as you move through it.
|
|
8
|
+
*/
|
|
9
|
+
export const pageRange = (
|
|
10
|
+
page: number,
|
|
11
|
+
pageCount: number,
|
|
12
|
+
siblings = 1
|
|
13
|
+
): PageRangeItem[] => {
|
|
14
|
+
const range = (from: number, to: number) =>
|
|
15
|
+
Array.from({ length: to - from + 1 }, (_, i) => from + i);
|
|
16
|
+
|
|
17
|
+
// first + last + current + siblings + two gaps
|
|
18
|
+
const slots = siblings * 2 + 5;
|
|
19
|
+
if (pageCount <= slots) return range(1, pageCount);
|
|
20
|
+
|
|
21
|
+
const left = Math.max(page - siblings, 1);
|
|
22
|
+
const right = Math.min(page + siblings, pageCount);
|
|
23
|
+
const gapBefore = left > 2;
|
|
24
|
+
const gapAfter = right < pageCount - 1;
|
|
25
|
+
// Pages shown at the start or end when only one gap is needed.
|
|
26
|
+
const edge = siblings * 2 + 3;
|
|
27
|
+
|
|
28
|
+
if (!gapBefore) return [...range(1, edge), "end-gap", pageCount];
|
|
29
|
+
if (!gapAfter)
|
|
30
|
+
return [1, "start-gap", ...range(pageCount - edge + 1, pageCount)];
|
|
31
|
+
return [1, "start-gap", ...range(left, right), "end-gap", pageCount];
|
|
32
|
+
};
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
font-weight: 700;
|
|
15
15
|
text-transform: uppercase;
|
|
16
16
|
letter-spacing: 0.05em;
|
|
17
|
-
|
|
17
|
+
/* 50% white: the weakest overlay that keeps small text above 4.5:1 on the
|
|
18
|
+
sidebar's dark background. */
|
|
19
|
+
color: var(--overlay-light-strong);
|
|
18
20
|
white-space: nowrap;
|
|
19
21
|
overflow: hidden;
|
|
20
22
|
}
|
|
@@ -38,6 +40,12 @@
|
|
|
38
40
|
background-color: var(--overlay-light-weak);
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
/* Browser default focus rings can vanish on the dark background. */
|
|
44
|
+
.k-sidebar-nav-link:focus-visible {
|
|
45
|
+
outline: 2px solid var(--text-color-light);
|
|
46
|
+
outline-offset: -2px;
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
.k-sidebar-nav-link.active {
|
|
42
50
|
background-color: var(--overlay-light-medium);
|
|
43
51
|
color: var(--text-color-light);
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.k-tab-button-primary:hover:not(.k-tab-button-disabled) {
|
|
38
|
-
color: var(--primary-color);
|
|
38
|
+
color: var(--primary-color-text);
|
|
39
39
|
background-color: var(--background-color);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
.k-tab-button-primary.k-tab-button-active {
|
|
43
|
-
color: var(--primary-color);
|
|
43
|
+
color: var(--primary-color-text);
|
|
44
44
|
background-color: var(--background-color);
|
|
45
45
|
border-bottom-color: var(--primary-color);
|
|
46
46
|
border-color: var(--primary-color) var(--primary-color) transparent
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
.k-tab-button-danger:hover:not(.k-tab-button-disabled) {
|
|
92
|
-
color: var(--danger-color);
|
|
92
|
+
color: var(--danger-color-text);
|
|
93
93
|
background-color: var(--background-color);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
.k-tab-button-danger.k-tab-button-active {
|
|
97
|
-
color: var(--danger-color);
|
|
97
|
+
color: var(--danger-color-text);
|
|
98
98
|
background-color: var(--background-color);
|
|
99
99
|
border-bottom-color: var(--danger-color);
|
|
100
100
|
border-color: var(--danger-color) var(--danger-color) transparent
|
|
@@ -107,12 +107,12 @@
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
.k-tab-button-warning:hover:not(.k-tab-button-disabled) {
|
|
110
|
-
color: var(--warning-color);
|
|
110
|
+
color: var(--warning-color-text);
|
|
111
111
|
background-color: var(--background-color);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
.k-tab-button-warning.k-tab-button-active {
|
|
115
|
-
color: var(--warning-color);
|
|
115
|
+
color: var(--warning-color-text);
|
|
116
116
|
background-color: var(--background-color);
|
|
117
117
|
border-bottom-color: var(--warning-color);
|
|
118
118
|
border-color: var(--warning-color) var(--warning-color) transparent
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.k-tabs-button:hover:not(:disabled) {
|
|
31
|
-
color: var(--primary-color);
|
|
31
|
+
color: var(--primary-color-text);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.k-tabs-button-active {
|
|
35
|
-
color: var(--primary-color);
|
|
35
|
+
color: var(--primary-color-text);
|
|
36
36
|
border-bottom-color: var(--primary-color);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import Button from "../../buttons/button/index.js";
|
|
4
|
+
import Input from "../../inputs/input/index.js";
|
|
4
5
|
import Modal from "../modal/index.js";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import "./styles.css";
|
|
5
8
|
|
|
6
9
|
export interface ConfirmationModalProps {
|
|
7
10
|
show: boolean;
|
|
@@ -12,14 +15,34 @@ export interface ConfirmationModalProps {
|
|
|
12
15
|
confirmButtonText: string;
|
|
13
16
|
cancelButtonText?: string;
|
|
14
17
|
isLoading?: boolean;
|
|
18
|
+
/** **_Optional_** text the user must type, exactly, before the confirm button enables (e.g. the name of what's being deleted) */
|
|
19
|
+
confirmationText?: string;
|
|
20
|
+
/** **_Optional_** whether Escape and a click outside close the modal. Defaults to `true` */
|
|
21
|
+
dismissible?: boolean;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
const ConfirmationModal = (props: ConfirmationModalProps) => {
|
|
25
|
+
const [typed, setTyped] = useState("");
|
|
26
|
+
const [wasShown, setWasShown] = useState(props.show);
|
|
27
|
+
|
|
28
|
+
// Clear the typed text each time the modal opens, so a match from a
|
|
29
|
+
// previous confirmation never carries over.
|
|
30
|
+
if (props.show !== wasShown) {
|
|
31
|
+
setWasShown(props.show);
|
|
32
|
+
if (props.show) setTyped("");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const confirmed =
|
|
36
|
+
props.confirmationText === undefined || typed === props.confirmationText;
|
|
37
|
+
const canConfirm = confirmed && !props.isLoading;
|
|
38
|
+
|
|
18
39
|
return (
|
|
19
40
|
<Modal
|
|
20
41
|
show={props.show}
|
|
21
42
|
title={props.title}
|
|
22
43
|
onClose={props.handleHide}
|
|
44
|
+
// Cancel is disabled while loading; Escape and the backdrop follow suit.
|
|
45
|
+
dismissible={props.dismissible !== false && !props.isLoading}
|
|
23
46
|
footerButtons={[
|
|
24
47
|
<Button
|
|
25
48
|
key="cancel"
|
|
@@ -34,13 +57,26 @@ const ConfirmationModal = (props: ConfirmationModalProps) => {
|
|
|
34
57
|
variant="danger"
|
|
35
58
|
onClick={props.handleConfirm}
|
|
36
59
|
isLoading={props.isLoading}
|
|
37
|
-
disabled={
|
|
60
|
+
disabled={!canConfirm}
|
|
38
61
|
>
|
|
39
62
|
{props.confirmButtonText}
|
|
40
63
|
</Button>,
|
|
41
64
|
]}
|
|
42
65
|
>
|
|
43
66
|
{props.body}
|
|
67
|
+
{props.confirmationText !== undefined && (
|
|
68
|
+
<div className="k-confirmation-modal-check">
|
|
69
|
+
<Input
|
|
70
|
+
label={`Type "${props.confirmationText}" to confirm`}
|
|
71
|
+
value={typed}
|
|
72
|
+
onChange={(e) => setTyped(e.target.value)}
|
|
73
|
+
onKeyDown={(e) => {
|
|
74
|
+
if (e.key === "Enter" && canConfirm) props.handleConfirm();
|
|
75
|
+
}}
|
|
76
|
+
autoComplete="off"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
44
80
|
</Modal>
|
|
45
81
|
);
|
|
46
82
|
};
|
|
@@ -23,13 +23,16 @@ interface ModalProps extends PropsWithChildren {
|
|
|
23
23
|
* editors) that would feel cramped at `lg`.
|
|
24
24
|
*/
|
|
25
25
|
size?: ModalSize;
|
|
26
|
+
/** **_Optional_** whether Escape and a click on the backdrop close the modal. When `false`, only the modal's own buttons can close it. Defaults to `true` */
|
|
27
|
+
dismissible?: boolean;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const Modal = (props: ModalProps) => {
|
|
29
|
-
const { size = "md" } = props;
|
|
31
|
+
const { size = "md", dismissible = true } = props;
|
|
30
32
|
const [shouldRender, setShouldRender] = useState(props.show);
|
|
31
33
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
32
34
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const pressStartedOnOverlay = useRef(false);
|
|
33
36
|
|
|
34
37
|
useEffect(() => {
|
|
35
38
|
if (props.show) {
|
|
@@ -46,13 +49,13 @@ const Modal = (props: ModalProps) => {
|
|
|
46
49
|
}, [props.show]);
|
|
47
50
|
|
|
48
51
|
useEffect(() => {
|
|
49
|
-
if (!props.show) return;
|
|
52
|
+
if (!props.show || !dismissible) return;
|
|
50
53
|
const handler = (e: KeyboardEvent) => {
|
|
51
54
|
if (e.key === "Escape") props.onClose();
|
|
52
55
|
};
|
|
53
56
|
document.addEventListener("keydown", handler);
|
|
54
57
|
return () => document.removeEventListener("keydown", handler);
|
|
55
|
-
}, [props.show, props.onClose]);
|
|
58
|
+
}, [props.show, props.onClose, dismissible]);
|
|
56
59
|
|
|
57
60
|
useFocusTrap(contentRef, !!props.show && !!shouldRender);
|
|
58
61
|
|
|
@@ -60,7 +63,23 @@ const Modal = (props: ModalProps) => {
|
|
|
60
63
|
|
|
61
64
|
return (
|
|
62
65
|
<Portal>
|
|
63
|
-
|
|
66
|
+
{/* Close only when the press starts and ends on the backdrop, so a text
|
|
67
|
+
selection dragged out of the dialog doesn't close it. */}
|
|
68
|
+
<div
|
|
69
|
+
className={`k-modal-overlay ${isAnimating ? "closing" : ""}`}
|
|
70
|
+
onMouseDown={(e) => {
|
|
71
|
+
pressStartedOnOverlay.current = e.target === e.currentTarget;
|
|
72
|
+
}}
|
|
73
|
+
onClick={(e) => {
|
|
74
|
+
if (
|
|
75
|
+
dismissible &&
|
|
76
|
+
pressStartedOnOverlay.current &&
|
|
77
|
+
e.target === e.currentTarget
|
|
78
|
+
) {
|
|
79
|
+
props.onClose();
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
64
83
|
<div
|
|
65
84
|
ref={contentRef}
|
|
66
85
|
className={`k-modal-content ${isAnimating ? "closing" : ""} ${size}`}
|
package/src/hooks/index.ts
CHANGED
|
@@ -4,6 +4,10 @@ export {
|
|
|
4
4
|
type UseInfiniteReturn,
|
|
5
5
|
} from "./useInfinite.js";
|
|
6
6
|
export { default as useInfiniteCursor } from "./useInfiniteCursor.js";
|
|
7
|
+
export {
|
|
8
|
+
default as usePaginated,
|
|
9
|
+
type UsePaginatedReturn,
|
|
10
|
+
} from "./usePaginated.js";
|
|
7
11
|
export { default as useScrollIntoView } from "./useScrollIntoView.js";
|
|
8
12
|
export { default as useDebouncedValue } from "./useDebouncedValue.js";
|
|
9
13
|
export { default as useOutsideClick } from "./useOutsideClick.js";
|