@dbcdk/react-components 0.0.8 → 0.0.10
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/README.md +167 -0
- package/dist/components/__stories__/_data/tabs.d.ts +9 -0
- package/dist/components/__stories__/_data/tabs.js +31 -0
- package/dist/components/accordion/Accordion.d.ts +27 -0
- package/dist/components/accordion/Accordion.js +66 -0
- package/dist/components/accordion/Accordion.module.css +87 -0
- package/dist/components/button/Button.module.css +1 -0
- package/dist/components/circle/Circle.d.ts +4 -1
- package/dist/components/circle/Circle.js +2 -2
- package/dist/components/circle/Circle.module.css +54 -2
- package/dist/components/code-block/CodeBlock.module.css +1 -1
- package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -7
- package/dist/components/datetime-picker/DateTimePicker.js +117 -64
- package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -2
- package/dist/components/datetime-picker/dateTimeHelpers.js +32 -17
- package/dist/components/forms/checkbox/Checkbox.d.ts +2 -8
- package/dist/components/forms/checkbox/Checkbox.js +3 -5
- package/dist/components/forms/input/Input.d.ts +1 -0
- package/dist/components/forms/input/Input.js +2 -4
- package/dist/components/forms/input/Input.module.css +9 -11
- package/dist/components/forms/input-container/InputContainer.d.ts +2 -1
- package/dist/components/forms/input-container/InputContainer.js +3 -3
- package/dist/components/forms/input-container/InputContainer.module.css +65 -0
- package/dist/components/forms/radio-buttons/RadioButton.d.ts +36 -0
- package/dist/components/forms/radio-buttons/RadioButton.js +26 -0
- package/dist/components/forms/radio-buttons/RadioButtonGroup.d.ts +25 -0
- package/dist/components/forms/radio-buttons/RadioButtonGroup.js +19 -0
- package/dist/components/forms/radio-buttons/RadioButtons.module.css +117 -0
- package/dist/components/forms/select/Select.d.ts +1 -1
- package/dist/components/forms/select/Select.js +3 -3
- package/dist/components/forms/text-area/Textarea.js +3 -3
- package/dist/components/forms/text-area/Textarea.module.css +8 -1
- package/dist/components/headline/Headline.d.ts +2 -7
- package/dist/components/headline/Headline.js +5 -2
- package/dist/components/headline/Headline.module.css +61 -2
- package/dist/components/hyperlink/Hyperlink.d.ts +1 -0
- package/dist/components/hyperlink/Hyperlink.js +5 -1
- package/dist/components/icon/Icon.module.css +1 -0
- package/dist/components/interval-select/IntervalSelect.js +1 -1
- package/dist/components/nav-bar/NavBar.d.ts +24 -6
- package/dist/components/overlay/modal/provider/ModalProvider.d.ts +2 -2
- package/dist/components/overlay/modal/provider/ModalProvider.js +24 -25
- package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
- package/dist/components/overlay/side-panel/SidePanel.js +60 -4
- package/dist/components/overlay/side-panel/SidePanel.module.css +151 -28
- package/dist/components/overlay/side-panel/useSidePanel.d.ts +1 -1
- package/dist/components/overlay/side-panel/useSidePanel.js +2 -2
- package/dist/components/page-layout/PageLayout.js +0 -2
- package/dist/components/popover/Popover.js +33 -14
- package/dist/components/popover/Popover.module.css +0 -4
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +16 -8
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +0 -3
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +3 -1
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +4 -3
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +109 -79
- package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -3
- package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +20 -0
- package/dist/components/sidebar/providers/SidebarProvider.js +25 -46
- package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
- package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
- package/dist/components/state-page/StatePage.d.ts +9 -0
- package/dist/components/state-page/StatePage.js +20 -0
- package/dist/components/state-page/StatePage.module.css +9 -0
- package/dist/components/state-page/empty.d.ts +2 -0
- package/dist/components/state-page/empty.js +2 -0
- package/dist/components/state-page/error.d.ts +2 -0
- package/dist/components/state-page/error.js +2 -0
- package/dist/components/state-page/notFound.d.ts +2 -0
- package/dist/components/state-page/notFound.js +2 -0
- package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +19 -0
- package/dist/components/sticky-footer-layout/StickyFooterLayout.js +27 -0
- package/dist/components/table/Table.js +4 -4
- package/dist/components/table/Table.module.css +168 -60
- package/dist/components/table/components/empty-state/EmptyState.d.ts +1 -1
- package/dist/components/table/components/empty-state/EmptyState.js +6 -7
- package/dist/components/toast/Toast.js +5 -1
- package/dist/components/toast/Toast.module.css +40 -15
- package/dist/components/toast/provider/ToastProvider.js +1 -0
- package/dist/hooks/useTimeDuration.js +9 -3
- package/dist/hooks/useViewportFill.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -1
- package/dist/src/styles/styles.css +22 -3
- package/dist/styles/styles.css +22 -3
- package/dist/styles/themes/dbc/dark.css +1 -1
- package/dist/styles/themes/dbc/light.css +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { JSX } from 'react';
|
|
3
|
+
type Variant = 'default' | 'primary' | 'outlined';
|
|
4
|
+
type Size = 'sm' | 'md' | 'lg';
|
|
5
|
+
export interface RadioButtonProps {
|
|
6
|
+
/** group name (required for radio behavior) */
|
|
7
|
+
name: string;
|
|
8
|
+
/** this option's value */
|
|
9
|
+
value: string;
|
|
10
|
+
/** controlled selected value for the group */
|
|
11
|
+
selectedValue?: string;
|
|
12
|
+
/** uncontrolled checked (rare; prefer selectedValue on group) */
|
|
13
|
+
checked?: boolean;
|
|
14
|
+
/** called with (value, event) */
|
|
15
|
+
onChange?: (value: string, event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
label?: string;
|
|
18
|
+
variant?: Variant;
|
|
19
|
+
size?: Size;
|
|
20
|
+
containerLabel?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
helpText?: string;
|
|
23
|
+
orientation?: 'vertical' | 'horizontal';
|
|
24
|
+
labelWidth?: string;
|
|
25
|
+
fullWidth?: boolean;
|
|
26
|
+
required?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* If true, do NOT wrap with InputContainer.
|
|
29
|
+
* Use inside RadioGroup.
|
|
30
|
+
*/
|
|
31
|
+
noContainer?: boolean;
|
|
32
|
+
id?: string;
|
|
33
|
+
'data-cy'?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function RadioButton({ name, value, selectedValue, checked, onChange, disabled, label, variant, size, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, }: RadioButtonProps): JSX.Element;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from 'react';
|
|
4
|
+
import styles from './RadioButtons.module.css';
|
|
5
|
+
import { InputContainer } from '../input-container/InputContainer';
|
|
6
|
+
export function RadioButton({ name, value, selectedValue, checked, onChange, disabled, label, variant = 'outlined', size = 'md', containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
|
|
7
|
+
const generatedId = useId();
|
|
8
|
+
const controlId = id !== null && id !== void 0 ? id : `radio-${generatedId}`;
|
|
9
|
+
const isChecked = selectedValue !== undefined ? selectedValue === value : Boolean(checked);
|
|
10
|
+
const content = (_jsxs("span", { className: styles.container, "data-cy": dataCy, children: [_jsxs("span", { className: styles.controlWrap, children: [_jsx("input", { id: controlId, className: styles.input, type: "radio", name: name, value: value, checked: isChecked, disabled: disabled, required: required, "aria-invalid": Boolean(error) || undefined, onChange: e => {
|
|
11
|
+
if (disabled)
|
|
12
|
+
return;
|
|
13
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e.target.value, e);
|
|
14
|
+
} }), _jsx("span", { className: [
|
|
15
|
+
styles.radio,
|
|
16
|
+
isChecked ? styles.checked : '',
|
|
17
|
+
disabled ? styles.disabled : '',
|
|
18
|
+
styles[variant],
|
|
19
|
+
styles[size],
|
|
20
|
+
]
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.join(' '), "aria-hidden": "true", children: _jsx("span", { className: styles.dot }) })] }), label && (_jsx("label", { className: styles.label, htmlFor: controlId, children: label }))] }));
|
|
23
|
+
if (noContainer || (!containerLabel && !error))
|
|
24
|
+
return content;
|
|
25
|
+
return (_jsx(InputContainer, { label: containerLabel, htmlFor: controlId, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: content }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { JSX } from 'react';
|
|
2
|
+
export type RadioOption<V extends string = string> = {
|
|
3
|
+
label: string;
|
|
4
|
+
value: V;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export interface RadioGroupProps<V extends string = string> {
|
|
8
|
+
name?: string;
|
|
9
|
+
value: V | null;
|
|
10
|
+
onChange: (value: V) => void;
|
|
11
|
+
options: RadioOption<V>[];
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
label?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
helpText?: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
orientation?: 'vertical' | 'horizontal';
|
|
18
|
+
labelWidth?: string;
|
|
19
|
+
fullWidth?: boolean;
|
|
20
|
+
modified?: boolean;
|
|
21
|
+
/** layout of radios themselves */
|
|
22
|
+
direction?: 'row' | 'column';
|
|
23
|
+
'data-cy'?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function RadioButtonGroup<V extends string = string>({ name, value, onChange, options, disabled, label, error, helpText, required, orientation, labelWidth, fullWidth, modified, direction, 'data-cy': dataCy, }: RadioGroupProps<V>): JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from 'react';
|
|
4
|
+
import { RadioButton } from './RadioButton';
|
|
5
|
+
import { InputContainer } from '../input-container/InputContainer';
|
|
6
|
+
export function RadioButtonGroup({ name, value, onChange, options, disabled = false, label, error, helpText, required = false, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, modified = false, direction = 'column', 'data-cy': dataCy, }) {
|
|
7
|
+
const generated = useId();
|
|
8
|
+
const groupName = name !== null && name !== void 0 ? name : `radio-group-${generated}`;
|
|
9
|
+
const content = (_jsx("div", { role: "radiogroup", "aria-invalid": Boolean(error) || undefined, "data-cy": dataCy, style: {
|
|
10
|
+
display: 'flex',
|
|
11
|
+
flexDirection: direction,
|
|
12
|
+
gap: '8px',
|
|
13
|
+
}, children: options.map(opt => (_jsx(RadioButton, { noContainer: true, name: groupName, value: String(opt.value), selectedValue: value !== null && value !== void 0 ? value : undefined, disabled: disabled || opt.disabled, label: opt.label, required: required, onChange: v => onChange(v), "data-cy": dataCy ? `${dataCy}-option-${String(opt.value)}` : undefined }, String(opt.value)))) }));
|
|
14
|
+
// If no InputContainer props were provided, just return group content
|
|
15
|
+
if (!label && !error)
|
|
16
|
+
return content;
|
|
17
|
+
// Wrap once as a proper field
|
|
18
|
+
return (_jsx(InputContainer, { label: label, htmlFor: undefined, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, modified: modified, children: content }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: 8px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.controlWrap {
|
|
8
|
+
position: relative;
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Real input stays for a11y, but visually hidden */
|
|
14
|
+
.input {
|
|
15
|
+
position: absolute;
|
|
16
|
+
inset: 0;
|
|
17
|
+
width: var(--component-size-xs);
|
|
18
|
+
height: var(--component-size-xs);
|
|
19
|
+
margin: 0;
|
|
20
|
+
opacity: 0;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* The visible radio */
|
|
25
|
+
.radio {
|
|
26
|
+
width: var(--component-size-xs);
|
|
27
|
+
height: var(--component-size-xs);
|
|
28
|
+
border-radius: 999px;
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
|
|
33
|
+
background: var(--color-bg-surface);
|
|
34
|
+
border: 2px solid rgba(0, 0, 0, 0.25);
|
|
35
|
+
|
|
36
|
+
transition:
|
|
37
|
+
border-color 120ms ease,
|
|
38
|
+
box-shadow 120ms ease,
|
|
39
|
+
background-color 120ms ease;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dot {
|
|
43
|
+
width: 10px;
|
|
44
|
+
height: 10px;
|
|
45
|
+
border-radius: 999px;
|
|
46
|
+
transform: scale(0);
|
|
47
|
+
transition: transform 120ms ease;
|
|
48
|
+
background: var(--color-brand);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* checked state */
|
|
52
|
+
.checked {
|
|
53
|
+
border-color: var(--color-brand);
|
|
54
|
+
}
|
|
55
|
+
.checked .dot {
|
|
56
|
+
transform: scale(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* hover (only when not disabled) */
|
|
60
|
+
.input:not(:disabled):hover + .radio {
|
|
61
|
+
border-color: var(--color-brand-hover);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* focus ring */
|
|
65
|
+
.input:focus-visible + .radio {
|
|
66
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-brand) 30%, transparent);
|
|
67
|
+
border-color: var(--color-brand-strong);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* disabled */
|
|
71
|
+
.disabled {
|
|
72
|
+
opacity: 0.5;
|
|
73
|
+
}
|
|
74
|
+
.input:disabled {
|
|
75
|
+
cursor: not-allowed;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.label {
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
user-select: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.primary.checked {
|
|
84
|
+
background: color-mix(in srgb, var(--color-brand) 10%, var(--color-bg-surface));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.default {
|
|
88
|
+
border-color: rgba(0, 0, 0, 0.2);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Sizes */
|
|
92
|
+
.sm {
|
|
93
|
+
width: 16px;
|
|
94
|
+
height: 16px;
|
|
95
|
+
}
|
|
96
|
+
.sm .dot {
|
|
97
|
+
width: 8px;
|
|
98
|
+
height: 8px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.md {
|
|
102
|
+
width: 20px;
|
|
103
|
+
height: 20px;
|
|
104
|
+
}
|
|
105
|
+
.md .dot {
|
|
106
|
+
width: 10px;
|
|
107
|
+
height: 10px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.lg {
|
|
111
|
+
width: 24px;
|
|
112
|
+
height: 24px;
|
|
113
|
+
}
|
|
114
|
+
.lg .dot {
|
|
115
|
+
width: 12px;
|
|
116
|
+
height: 12px;
|
|
117
|
+
}
|
|
@@ -18,5 +18,5 @@ export type SelectProps<T> = Omit<InputContainerProps, 'children' | 'htmlFor' |
|
|
|
18
18
|
tooltip?: React.ReactNode;
|
|
19
19
|
tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
|
|
20
20
|
};
|
|
21
|
-
export declare function Select<T extends string | number | Record<string, any>>({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, }: SelectProps<T>): React.ReactNode;
|
|
21
|
+
export declare function Select<T extends string | number | Record<string, any>>({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, }: SelectProps<T>): React.ReactNode;
|
|
22
22
|
export {};
|
|
@@ -10,7 +10,7 @@ import { Popover } from '../../popover/Popover';
|
|
|
10
10
|
import { InputContainer } from '../input-container/InputContainer';
|
|
11
11
|
export function Select({
|
|
12
12
|
// InputContainer props
|
|
13
|
-
label, error, helpText, orientation = 'vertical', labelWidth = '
|
|
13
|
+
label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required, tooltip, tooltipPlacement = 'right', modified = false,
|
|
14
14
|
// Select props
|
|
15
15
|
id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'outlined', onClear, datakey, dataCy, disabled, }) {
|
|
16
16
|
const generatedId = useId();
|
|
@@ -72,10 +72,10 @@ id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'ou
|
|
|
72
72
|
ids.push(tooltipId);
|
|
73
73
|
return ids.length ? ids.join(' ') : undefined;
|
|
74
74
|
})();
|
|
75
|
-
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
75
|
+
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
76
76
|
setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
|
|
77
77
|
onClick(e);
|
|
78
|
-
}, size: size, type: "button", "aria-haspopup": "listbox", "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : placeholder }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
78
|
+
}, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : placeholder }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
79
79
|
const isSelected = typeof opt.value === 'object' && typeof selectedValue === 'object' && datakey
|
|
80
80
|
? (selectedValue === null || selectedValue === void 0 ? void 0 : selectedValue[datakey]) === opt.value[datakey]
|
|
81
81
|
: opt.value === selectedValue;
|
|
@@ -4,9 +4,9 @@ import { useCallback, useId, useMemo } from 'react';
|
|
|
4
4
|
import { useTooltipTrigger } from '../../../components/overlay/tooltip/useTooltipTrigger';
|
|
5
5
|
import styles from './Textarea.module.css';
|
|
6
6
|
import { InputContainer } from '../input-container/InputContainer';
|
|
7
|
-
export const Textarea = function Textarea({ value, inputChanged, disabled, rows = 3, showCount, tooltip, tooltipPlacement = 'right', showTooltip, placeholder, adjustHeight, id,
|
|
7
|
+
export const Textarea = function Textarea({ value, inputChanged, disabled, rows = 3, showCount, tooltip, tooltipPlacement = 'right', showTooltip, placeholder, adjustHeight, id, modified = false,
|
|
8
8
|
// InputContainer props
|
|
9
|
-
label, error, helpText, orientation = 'horizontal', labelWidth = '
|
|
9
|
+
label, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required,
|
|
10
10
|
// Native textarea props
|
|
11
11
|
className, ...rest }) {
|
|
12
12
|
const generatedId = useId();
|
|
@@ -28,6 +28,6 @@ className, ...rest }) {
|
|
|
28
28
|
placement: tooltipPlacement,
|
|
29
29
|
offset: 8,
|
|
30
30
|
});
|
|
31
|
-
return (_jsx(InputContainer, { label: label, htmlFor: textareaId, error: error, helpText: helpText, helpTextAddition: showCount ? `${value === null || value === void 0 ? void 0 : value.length} tegn i denne boks` : undefined, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: _jsx("div", { className: styles.container, children: _jsx("div", { ...(tooltipEnabled ? triggerProps : {}), children: inputField }) }) }));
|
|
31
|
+
return (_jsx(InputContainer, { modified: modified, label: label, htmlFor: textareaId, error: error, helpText: helpText, helpTextAddition: showCount ? `${value === null || value === void 0 ? void 0 : value.length} tegn i denne boks` : undefined, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: _jsx("div", { className: styles.container, children: _jsx("div", { ...(tooltipEnabled ? triggerProps : {}), children: inputField }) }) }));
|
|
32
32
|
};
|
|
33
33
|
Textarea.displayName = 'Textarea';
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
font-family: var(--font-family);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
.container textarea:disabled {
|
|
13
|
+
background-color: var(--color-disabled-bg);
|
|
14
|
+
border: 0;
|
|
15
|
+
color: var(--color-disabled-fg);
|
|
16
|
+
cursor: not-allowed;
|
|
17
|
+
opacity: 0.5;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
.internalCount {
|
|
13
21
|
text-align: right;
|
|
14
22
|
font-size: var(--font-size-sm);
|
|
@@ -22,5 +30,4 @@
|
|
|
22
30
|
.container textarea:focus-visible {
|
|
23
31
|
outline: none;
|
|
24
32
|
border-color: var(--color-border-selected);
|
|
25
|
-
box-shadow: var(--focus-ring);
|
|
26
33
|
}
|
|
@@ -10,13 +10,8 @@ interface HeadlineProps extends React.AriaAttributes {
|
|
|
10
10
|
subHeadline?: string | JSX.Element;
|
|
11
11
|
addition?: React.ReactNode;
|
|
12
12
|
icon?: React.ReactNode;
|
|
13
|
-
|
|
14
|
-
* Optional visual tone override:
|
|
15
|
-
* - dark: force normal foreground colours
|
|
16
|
-
* - light: force on-strong/light text
|
|
17
|
-
* If omitted, the headline simply inherits its colour from its parent.
|
|
18
|
-
*/
|
|
13
|
+
allowWrap?: boolean;
|
|
19
14
|
tone?: HeadlineTone;
|
|
20
15
|
}
|
|
21
|
-
export declare function Headline({ size, marker, disableMargin, children, severity, weight, subHeadline, addition, icon, tone, }: PropsWithChildren<HeadlineProps>): React.ReactNode;
|
|
16
|
+
export declare function Headline({ size, marker, disableMargin, children, severity, weight, subHeadline, addition, icon, tone, allowWrap, }: PropsWithChildren<HeadlineProps>): React.ReactNode;
|
|
22
17
|
export {};
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import styles from './Headline.module.css';
|
|
4
4
|
import { SeverityBgColor } from '../../constants/severity';
|
|
5
5
|
import { Icon } from '../icon/Icon';
|
|
6
|
-
export function Headline({ size = 2, marker, disableMargin, children, severity, weight = 600, subHeadline, addition, icon, tone, }) {
|
|
6
|
+
export function Headline({ size = 2, marker, disableMargin, children, severity, weight = 600, subHeadline, addition, icon, tone, allowWrap = true, }) {
|
|
7
7
|
const Tag = `h${size}`;
|
|
8
8
|
const containerClassName = [styles.headlineContainer, tone ? styles[`tone-${tone}`] : '']
|
|
9
9
|
.filter(Boolean)
|
|
@@ -15,8 +15,11 @@ export function Headline({ size = 2, marker, disableMargin, children, severity,
|
|
|
15
15
|
]
|
|
16
16
|
.filter(Boolean)
|
|
17
17
|
.join(' ');
|
|
18
|
+
const textClassName = [styles.text, allowWrap ? styles.wrap : styles.truncate]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' ');
|
|
18
21
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: containerClassName, children: [_jsxs(Tag, { style: {
|
|
19
22
|
'--font-weight': weight,
|
|
20
23
|
'--marker-color': severity ? SeverityBgColor[severity] : undefined,
|
|
21
|
-
}, className: headlineClassName, children: [icon || (severity && !marker) ? _jsx(Icon, { customIcon: icon, severity: severity }) : null, children] }), addition] }), subHeadline && _jsx("div", { className: styles.subHeadline, children: subHeadline })] }));
|
|
24
|
+
}, className: headlineClassName, children: [icon || (severity && !marker) ? (_jsx("span", { className: styles.icon, children: _jsx(Icon, { customIcon: icon, severity: severity }) })) : null, _jsx("span", { className: textClassName, children: children })] }), addition] }), subHeadline && _jsx("div", { className: styles.subHeadline, children: subHeadline })] }));
|
|
22
25
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
align-items: center;
|
|
4
4
|
gap: var(--spacing-lg);
|
|
5
5
|
flex-wrap: wrap;
|
|
6
|
+
max-width: 100%;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
/* Base headline: inherit colour from parent surface */
|
|
@@ -18,8 +19,6 @@
|
|
|
18
19
|
transition: color var(--transition-fast) var(--ease-standard);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
/* Optional tone overrides for special cases (hero sections, etc.) */
|
|
22
|
-
|
|
23
22
|
.tone-dark .headline {
|
|
24
23
|
color: var(--color-fg-default);
|
|
25
24
|
}
|
|
@@ -58,3 +57,63 @@
|
|
|
58
57
|
margin-block-start: calc(var(--spacing-2xs) * -1);
|
|
59
58
|
line-height: var(--line-height-normal);
|
|
60
59
|
}
|
|
60
|
+
|
|
61
|
+
.headline {
|
|
62
|
+
position: relative;
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: var(--spacing-xs);
|
|
66
|
+
font-weight: var(--font-weight, var(--font-weight-bold));
|
|
67
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
68
|
+
color: inherit;
|
|
69
|
+
line-height: var(--line-height-tight);
|
|
70
|
+
transition: color var(--transition-fast) var(--ease-standard);
|
|
71
|
+
|
|
72
|
+
/* Needed so truncation works inside flex parents */
|
|
73
|
+
min-width: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Truncated variant (single line with ellipsis) */
|
|
77
|
+
.truncate {
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
text-overflow: ellipsis;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.headline {
|
|
84
|
+
position: relative;
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: var(--spacing-xs);
|
|
88
|
+
font-weight: var(--font-weight, var(--font-weight-bold));
|
|
89
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
90
|
+
color: inherit;
|
|
91
|
+
line-height: var(--line-height-tight);
|
|
92
|
+
transition: color var(--transition-fast) var(--ease-standard);
|
|
93
|
+
|
|
94
|
+
/* helps inside flex parents */
|
|
95
|
+
min-width: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.icon {
|
|
99
|
+
flex: 0 0 auto;
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* text wrapper must be the shrinkable element */
|
|
105
|
+
.text {
|
|
106
|
+
min-width: 0;
|
|
107
|
+
flex: 1 1 auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.truncate {
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
white-space: nowrap;
|
|
113
|
+
text-overflow: ellipsis;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.wrap {
|
|
117
|
+
white-space: normal;
|
|
118
|
+
overflow: visible;
|
|
119
|
+
}
|
|
@@ -6,6 +6,10 @@ export function Hyperlink({ component, icon }) {
|
|
|
6
6
|
return React.cloneElement(component, {
|
|
7
7
|
...originalProps,
|
|
8
8
|
className: styles.link,
|
|
9
|
-
onClick: (e) =>
|
|
9
|
+
onClick: (e) => {
|
|
10
|
+
var _a;
|
|
11
|
+
e.stopPropagation();
|
|
12
|
+
(_a = originalProps === null || originalProps === void 0 ? void 0 : originalProps.onClick) === null || _a === void 0 ? void 0 : _a.call(originalProps, e);
|
|
13
|
+
},
|
|
10
14
|
}, _jsxs(_Fragment, { children: [_jsx("span", { className: styles.content, children: originalProps.children }), icon && _jsx("span", { className: styles.icon, children: icon })] }));
|
|
11
15
|
}
|
|
@@ -10,7 +10,7 @@ import { useTooltipTrigger } from '../../components/overlay/tooltip/useTooltipTr
|
|
|
10
10
|
import { Popover } from '../../components/popover/Popover';
|
|
11
11
|
export function IntervalSelect({
|
|
12
12
|
// InputContainer props
|
|
13
|
-
label, error, helpText, orientation = 'vertical', labelWidth = '
|
|
13
|
+
label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required,
|
|
14
14
|
// tooltip
|
|
15
15
|
tooltip, tooltipPlacement = 'right',
|
|
16
16
|
// IntervalSelect props
|
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import type { ElementType, ReactNode, JSX } from 'react';
|
|
2
|
-
export type NavBarItem =
|
|
3
|
-
|
|
2
|
+
export type NavBarItem = NavBarLinkItem | NavBarExpandableItem | NavBarGroupItem;
|
|
3
|
+
type NavBarBase = {
|
|
4
4
|
label: string;
|
|
5
5
|
icon?: ReactNode;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
tags?: string[];
|
|
8
|
+
};
|
|
9
|
+
/** Simple clickable item */
|
|
10
|
+
export type NavBarLinkItem = NavBarBase & {
|
|
11
|
+
type?: 'item';
|
|
12
|
+
href: string;
|
|
13
|
+
component?: ElementType<any>;
|
|
6
14
|
active?: boolean;
|
|
7
15
|
external?: boolean;
|
|
8
|
-
|
|
16
|
+
};
|
|
17
|
+
/** Clickable + expandable item (has href AND children) */
|
|
18
|
+
export type NavBarExpandableItem = NavBarBase & {
|
|
19
|
+
type: 'expandable';
|
|
9
20
|
href: string;
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
component?: ElementType<any>;
|
|
22
|
+
active?: boolean;
|
|
23
|
+
external?: boolean;
|
|
24
|
+
children: NavBarItem[];
|
|
25
|
+
};
|
|
26
|
+
/** Non-clickable group header */
|
|
27
|
+
export type NavBarGroupItem = NavBarBase & {
|
|
28
|
+
type: 'group';
|
|
29
|
+
children: NavBarItem[];
|
|
12
30
|
};
|
|
13
31
|
interface NavBarProps {
|
|
14
32
|
logo?: ReactNode;
|
|
15
|
-
items:
|
|
33
|
+
items: NavBarLinkItem[];
|
|
16
34
|
productName?: string;
|
|
17
35
|
addition?: ReactNode;
|
|
18
36
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { ReactNode } from 'react';
|
|
2
|
-
import { ModalProps } from '../Modal';
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
import { type ModalProps } from '../Modal';
|
|
3
3
|
type ModalConfig = Omit<ModalProps, 'isOpen' | 'onRequestClose'> & {
|
|
4
4
|
onRequestClose?: () => void;
|
|
5
5
|
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useCallback, useContext,
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
import { Modal } from '../Modal';
|
|
5
6
|
const ModalContext = createContext(undefined);
|
|
6
7
|
export function ModalProvider({ children }) {
|
|
7
8
|
const [isOpen, setIsOpen] = useState(false);
|
|
8
9
|
const [config, setConfig] = useState(null);
|
|
10
|
+
const [mounted, setMounted] = useState(false);
|
|
11
|
+
useEffect(() => setMounted(true), []);
|
|
9
12
|
// Holds the resolver for the current "confirm" call, if any
|
|
10
13
|
const pendingResolverRef = useRef(null);
|
|
11
14
|
const resolvePending = useCallback((value) => {
|
|
@@ -24,47 +27,43 @@ export function ModalProvider({ children }) {
|
|
|
24
27
|
setIsOpen(true);
|
|
25
28
|
}, [resolvePending]);
|
|
26
29
|
const handleRequestClose = useCallback(() => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
// Any non-explicit confirm click counts as "false"
|
|
30
|
+
var _a;
|
|
31
|
+
(_a = config === null || config === void 0 ? void 0 : config.onRequestClose) === null || _a === void 0 ? void 0 : _a.call(config);
|
|
31
32
|
resolvePending(false);
|
|
32
33
|
closeModal();
|
|
33
34
|
}, [config, closeModal, resolvePending]);
|
|
34
35
|
const confirm = useCallback((confirmConfig) => {
|
|
35
36
|
return new Promise(resolve => {
|
|
36
|
-
// cancel any previous pending confirm
|
|
37
37
|
resolvePending(false);
|
|
38
38
|
pendingResolverRef.current = resolve;
|
|
39
39
|
const { confirmLabel = 'Ok', cancelLabel = 'Annuller', ...rest } = confirmConfig;
|
|
40
|
-
const primaryAction = {
|
|
41
|
-
label: confirmLabel,
|
|
42
|
-
onClick: () => {
|
|
43
|
-
resolvePending(true);
|
|
44
|
-
closeModal();
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
const secondaryAction = {
|
|
48
|
-
label: cancelLabel,
|
|
49
|
-
onClick: () => {
|
|
50
|
-
resolvePending(false);
|
|
51
|
-
closeModal();
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
40
|
setConfig({
|
|
55
41
|
...rest,
|
|
56
|
-
primaryAction
|
|
57
|
-
|
|
42
|
+
primaryAction: {
|
|
43
|
+
label: confirmLabel,
|
|
44
|
+
onClick: () => {
|
|
45
|
+
resolvePending(true);
|
|
46
|
+
closeModal();
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
secondaryAction: {
|
|
50
|
+
label: cancelLabel,
|
|
51
|
+
onClick: () => {
|
|
52
|
+
resolvePending(false);
|
|
53
|
+
closeModal();
|
|
54
|
+
},
|
|
55
|
+
},
|
|
58
56
|
});
|
|
59
57
|
setIsOpen(true);
|
|
60
58
|
});
|
|
61
59
|
}, [closeModal, resolvePending]);
|
|
62
|
-
|
|
60
|
+
const modalNode = (_jsxs(ModalContext.Provider, { value: { openModal, closeModal, confirm }, children: [children, mounted &&
|
|
61
|
+
createPortal(_jsx(Modal, { ...(config !== null && config !== void 0 ? config : {}), isOpen: isOpen, onRequestClose: handleRequestClose, children: config === null || config === void 0 ? void 0 : config.children }), document.body)] }));
|
|
62
|
+
return modalNode;
|
|
63
63
|
}
|
|
64
64
|
export function useModal() {
|
|
65
65
|
const ctx = useContext(ModalContext);
|
|
66
|
-
if (!ctx)
|
|
66
|
+
if (!ctx)
|
|
67
67
|
throw new Error('useModal must be used within a ModalProvider');
|
|
68
|
-
}
|
|
69
68
|
return ctx;
|
|
70
69
|
}
|
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React, { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import { Severity } from '../../../constants/severity.types';
|
|
3
3
|
interface SidePanelProps {
|
|
4
4
|
children?: ReactNode;
|
|
5
5
|
header: ReactNode;
|
|
6
6
|
headerAddition?: ReactNode;
|
|
7
|
-
actions
|
|
8
|
-
onClose: () => void;
|
|
7
|
+
actions?: ReactNode;
|
|
8
|
+
onClose: (event?: React.MouseEvent<HTMLButtonElement> | React.MouseEvent<HTMLDivElement>) => void;
|
|
9
9
|
isOpen: boolean;
|
|
10
10
|
showBackdrop?: boolean;
|
|
11
11
|
severity?: Severity;
|
|
12
12
|
showHeaderMarker?: boolean;
|
|
13
13
|
width?: number | string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional details pane (separate column).
|
|
16
|
+
*/
|
|
17
|
+
details?: ReactNode;
|
|
18
|
+
detailsHeader?: ReactNode;
|
|
19
|
+
detailsWidth?: number | string;
|
|
20
|
+
onCloseDetails?: () => void;
|
|
21
|
+
detailsHeaderAddition?: ReactNode;
|
|
14
22
|
}
|
|
15
|
-
export declare function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop, severity, showHeaderMarker, width, ...props }: SidePanelProps & HTMLAttributes<HTMLElement>): ReactNode;
|
|
23
|
+
export declare function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop, severity, showHeaderMarker, width, details, detailsHeader, detailsWidth, onCloseDetails, detailsHeaderAddition, ...props }: SidePanelProps & HTMLAttributes<HTMLElement>): ReactNode;
|
|
16
24
|
export {};
|