@dbcdk/react-components 0.0.9 → 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.
Files changed (80) hide show
  1. package/dist/components/accordion/Accordion.d.ts +27 -0
  2. package/dist/components/accordion/Accordion.js +66 -0
  3. package/dist/components/accordion/Accordion.module.css +87 -0
  4. package/dist/components/button/Button.module.css +1 -0
  5. package/dist/components/circle/Circle.d.ts +4 -1
  6. package/dist/components/circle/Circle.js +2 -2
  7. package/dist/components/circle/Circle.module.css +54 -2
  8. package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -8
  9. package/dist/components/datetime-picker/DateTimePicker.js +72 -92
  10. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -12
  11. package/dist/components/datetime-picker/dateTimeHelpers.js +25 -45
  12. package/dist/components/forms/checkbox/Checkbox.d.ts +2 -8
  13. package/dist/components/forms/checkbox/Checkbox.js +3 -5
  14. package/dist/components/forms/input/Input.d.ts +1 -0
  15. package/dist/components/forms/input/Input.js +2 -4
  16. package/dist/components/forms/input/Input.module.css +9 -11
  17. package/dist/components/forms/input-container/InputContainer.d.ts +2 -1
  18. package/dist/components/forms/input-container/InputContainer.js +3 -3
  19. package/dist/components/forms/input-container/InputContainer.module.css +65 -0
  20. package/dist/components/forms/radio-buttons/RadioButton.d.ts +36 -0
  21. package/dist/components/forms/radio-buttons/RadioButton.js +26 -0
  22. package/dist/components/forms/radio-buttons/RadioButtonGroup.d.ts +25 -0
  23. package/dist/components/forms/radio-buttons/RadioButtonGroup.js +19 -0
  24. package/dist/components/forms/radio-buttons/RadioButtons.module.css +117 -0
  25. package/dist/components/forms/select/Select.d.ts +1 -1
  26. package/dist/components/forms/select/Select.js +3 -3
  27. package/dist/components/forms/text-area/Textarea.js +3 -3
  28. package/dist/components/forms/text-area/Textarea.module.css +8 -1
  29. package/dist/components/headline/Headline.d.ts +2 -7
  30. package/dist/components/headline/Headline.js +5 -2
  31. package/dist/components/headline/Headline.module.css +61 -2
  32. package/dist/components/hyperlink/Hyperlink.d.ts +1 -0
  33. package/dist/components/hyperlink/Hyperlink.js +5 -1
  34. package/dist/components/icon/Icon.module.css +1 -0
  35. package/dist/components/interval-select/IntervalSelect.js +1 -1
  36. package/dist/components/nav-bar/NavBar.d.ts +24 -6
  37. package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
  38. package/dist/components/overlay/side-panel/SidePanel.js +60 -4
  39. package/dist/components/overlay/side-panel/SidePanel.module.css +151 -28
  40. package/dist/components/overlay/side-panel/useSidePanel.d.ts +1 -1
  41. package/dist/components/overlay/side-panel/useSidePanel.js +2 -2
  42. package/dist/components/page-layout/PageLayout.js +0 -2
  43. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
  44. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +16 -8
  45. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +0 -3
  46. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +3 -1
  47. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +4 -3
  48. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +109 -79
  49. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -3
  50. package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +20 -0
  51. package/dist/components/sidebar/providers/SidebarProvider.js +25 -46
  52. package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
  53. package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
  54. package/dist/components/state-page/StatePage.d.ts +9 -0
  55. package/dist/components/state-page/StatePage.js +20 -0
  56. package/dist/components/state-page/StatePage.module.css +9 -0
  57. package/dist/components/state-page/empty.d.ts +2 -0
  58. package/dist/components/state-page/empty.js +2 -0
  59. package/dist/components/state-page/error.d.ts +2 -0
  60. package/dist/components/state-page/error.js +2 -0
  61. package/dist/components/state-page/notFound.d.ts +2 -0
  62. package/dist/components/state-page/notFound.js +2 -0
  63. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +19 -0
  64. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +27 -0
  65. package/dist/components/table/Table.js +4 -4
  66. package/dist/components/table/Table.module.css +168 -60
  67. package/dist/components/table/components/empty-state/EmptyState.d.ts +1 -1
  68. package/dist/components/table/components/empty-state/EmptyState.js +6 -7
  69. package/dist/components/toast/Toast.js +5 -1
  70. package/dist/components/toast/Toast.module.css +40 -15
  71. package/dist/components/toast/provider/ToastProvider.js +1 -0
  72. package/dist/hooks/useTimeDuration.js +9 -3
  73. package/dist/hooks/useViewportFill.js +1 -0
  74. package/dist/index.d.ts +6 -0
  75. package/dist/index.js +6 -1
  76. package/dist/src/styles/styles.css +22 -3
  77. package/dist/styles/styles.css +22 -3
  78. package/dist/styles/themes/dbc/dark.css +1 -1
  79. package/dist/styles/themes/dbc/light.css +2 -1
  80. package/package.json +1 -1
@@ -35,56 +35,42 @@ export function maskSingle(text, enableTime) {
35
35
  return timePart ? `${datePart} ${timePart}` : datePart;
36
36
  }
37
37
  // Range: mask both sides around common separators (–, -, to, til)
38
- export function maskRange(text, enableTime) {
38
+ export function maskRange(text, _enableTime) {
39
+ // NOTE: range is date-only in the UI
39
40
  const sepRe = /\s*(?:–|-|to|til)\s*/i;
40
41
  const parts = text.split(sepRe);
41
- if (parts.length === 1) {
42
- // user typing first side
43
- return maskSingle(parts[0], enableTime);
44
- }
45
- const a = maskSingle(parts[0], enableTime);
46
- const b = maskSingle(parts.slice(1).join(' '), enableTime); // everything after first sep
42
+ if (parts.length === 1)
43
+ return maskSingle(parts[0], false);
44
+ const a = maskSingle(parts[0], false);
45
+ const b = maskSingle(parts.slice(1).join(' '), false);
47
46
  return `${a} – ${b}`.trim();
48
47
  }
49
48
  // Pad helper
50
49
  export const pad2 = (n) => String(n).padStart(2, '0');
51
- export function parseYMD(ymd) {
52
- const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
53
- if (!m)
54
- return null;
55
- const y = +m[1];
56
- const mo = +m[2];
57
- const d = +m[3];
58
- if (mo < 1 || mo > 12 || d < 1 || d > 31)
59
- return null;
60
- return { y, m: mo, d };
50
+ export function isUtcIsoString(v) {
51
+ return typeof v === 'string' && /Z$/.test(v) && !Number.isNaN(Date.parse(v));
61
52
  }
62
- export function ymdFromLocalDate(dLocal) {
63
- const y = dLocal.getFullYear();
64
- const m = dLocal.getMonth() + 1;
65
- const d = dLocal.getDate();
66
- return `${y}-${pad2(m)}-${pad2(d)}`;
53
+ export function utcMillisFromIso(iso) {
54
+ return Date.parse(iso);
67
55
  }
68
- export function ymdFromUTCDateOnly(utcDay) {
69
- const y = utcDay.getUTCFullYear();
70
- const m = utcDay.getUTCMonth() + 1;
71
- const d = utcDay.getUTCDate();
72
- return `${y}-${pad2(m)}-${pad2(d)}`;
56
+ /**
57
+ * Build a *local* Date from y/m/d/hh/mm and return UTC ISO string (Z).
58
+ * This keeps the UI meaning "local wall time", while emitting a stable UTC instant.
59
+ */
60
+ export function isoFromLocalParts(y, m0, // 0-based
61
+ d, hh = 0, mm = 0) {
62
+ return new Date(y, m0, d, hh, mm, 0, 0).toISOString();
73
63
  }
74
- // Used only for ordering/comparing date-only values.
75
- export function utcMillisFromYMD(ymd) {
76
- const p = parseYMD(ymd);
77
- if (!p)
78
- return NaN;
79
- return Date.UTC(p.y, p.m - 1, p.d);
64
+ /** Convert a local Date to a UTC ISO string (Z). */
65
+ export function isoFromLocalDate(dLocal) {
66
+ return dLocal.toISOString();
80
67
  }
81
- // For anchoring the calendar grid safely from a date-only value.
82
- export function localDateFromYMD(ymd) {
83
- const p = parseYMD(ymd);
84
- if (!p)
68
+ /** For anchoring the calendar safely from a UTC ISO string. */
69
+ export function localDateFromIso(iso) {
70
+ const ms = Date.parse(iso);
71
+ if (Number.isNaN(ms))
85
72
  return null;
86
- // local noon avoids DST edge cases for date-only anchors
87
- return new Date(p.y, p.m - 1, p.d, 12, 0, 0, 0);
73
+ return new Date(ms); // local rendering
88
74
  }
89
75
  /* ---------- Formatting (UI shows local) ---------- */
90
76
  // From Date → "DD-MM-YYYY" or "DD-MM-YYYY HH:mm" (local time)
@@ -97,12 +83,6 @@ export function toMaskedFromDate(d, enableTime) {
97
83
  out += ` ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
98
84
  return out;
99
85
  }
100
- export function toMaskedFromYMD(ymd) {
101
- const p = parseYMD(ymd);
102
- if (!p)
103
- return '';
104
- return `${pad2(p.d)}-${pad2(p.m)}-${p.y}`;
105
- }
106
86
  /* ---------- Parsing helpers (no deps) ---------- */
107
87
  // Accepts: YYYY-MM-DD, DD-MM-YYYY, DD/MM/YYYY, DD.MM.YYYY (+ optional HH:mm)
108
88
  export function parseLooseDateOrDateTime(input) {
@@ -7,7 +7,7 @@ interface CheckboxProps {
7
7
  onChange?: (checked: boolean, event: React.MouseEvent<HTMLButtonElement>) => void;
8
8
  variant?: Variant;
9
9
  disabled?: boolean;
10
- /** Text shown next to the box (per-item label) */
10
+ modified?: boolean;
11
11
  label?: string;
12
12
  size?: Size;
13
13
  containerLabel?: string;
@@ -17,15 +17,9 @@ interface CheckboxProps {
17
17
  labelWidth?: string;
18
18
  fullWidth?: boolean;
19
19
  required?: boolean;
20
- /**
21
- * If true, do NOT wrap with InputContainer.
22
- * Use this inside CheckboxGroup (so you don't get group-form layout per item).
23
- */
24
20
  noContainer?: boolean;
25
- /** Optional id for accessibility (label htmlFor) */
26
21
  id?: string;
27
- /** Data attributes pass-through */
28
22
  'data-cy'?: string;
29
23
  }
30
- export declare function Checkbox({ checked: controlled, onChange, variant, disabled, label, size, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, }: CheckboxProps): JSX.Element;
24
+ export declare function Checkbox({ checked: controlled, onChange, variant, disabled, label, size, modified, containerLabel, error, helpText, orientation, labelWidth, fullWidth, required, noContainer, id, 'data-cy': dataCy, }: CheckboxProps): JSX.Element;
31
25
  export {};
@@ -4,7 +4,7 @@ import { Check } from 'lucide-react';
4
4
  import { useId, useState } from 'react';
5
5
  import styles from './Checkbox.module.css';
6
6
  import { InputContainer } from '../input-container/InputContainer';
7
- export function Checkbox({ checked: controlled, onChange, variant = 'outlined', disabled, label, size = 'md', containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '120px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
7
+ export function Checkbox({ checked: controlled, onChange, variant = 'outlined', disabled, label, size = 'md', modified, containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
8
8
  const [internal, setInternal] = useState(false);
9
9
  const isChecked = controlled !== null && controlled !== void 0 ? controlled : internal;
10
10
  const generatedId = useId();
@@ -19,9 +19,7 @@ export function Checkbox({ checked: controlled, onChange, variant = 'outlined',
19
19
  const content = (_jsxs("span", { className: styles.container, "data-cy": dataCy, children: [_jsx("button", { id: controlId, disabled: disabled, type: "button", role: "checkbox", "aria-checked": isChecked, "aria-disabled": disabled || undefined, "aria-invalid": Boolean(error) || undefined, onClick: toggle, className: [styles.checkbox, isChecked ? styles.checked : '', styles[variant], styles[size]]
20
20
  .filter(Boolean)
21
21
  .join(' '), children: isChecked && _jsx(Check, { className: styles.icon }) }), label && (_jsx("label", { className: styles.label, htmlFor: controlId, children: label }))] }));
22
- // For CheckboxGroup use-case
23
- if (noContainer || (!containerLabel && !error))
22
+ if (noContainer)
24
23
  return content;
25
- // Standalone form field use-case
26
- return (_jsx(InputContainer, { label: containerLabel, htmlFor: controlId, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: content }));
24
+ return (_jsx(InputContainer, { modified: modified, label: containerLabel, htmlFor: controlId, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, fullWidth: fullWidth, required: required, children: content }));
27
25
  }
@@ -15,6 +15,7 @@ export type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size
15
15
  buttonIcon?: React.ReactNode;
16
16
  tooltip?: React.ReactNode;
17
17
  tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
18
+ modified?: boolean;
18
19
  };
19
20
  /**
20
21
  * Explicit exported type annotation is required with --isolatedDeclarations.
@@ -23,9 +23,7 @@ function mergeRefs(...refs) {
23
23
  */
24
24
  export const Input = forwardRef(function Input({
25
25
  // InputContainer props
26
- label, error, helpText, orientation = 'horizontal', labelWidth = '120px', fullWidth = false, required,
27
- // ✅ Input-level tooltip props
28
- tooltip, tooltipPlacement = 'right',
26
+ label, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required, tooltip, tooltipPlacement = 'right', modified,
29
27
  // Input-only props
30
28
  icon, autoFocus, minWidth, width, inputSize = 'md', variant = 'outlined', onClear, onButtonClick, buttonLabel, buttonIcon,
31
29
  // Native input props
@@ -50,7 +48,7 @@ id, style, className, ...inputProps }, ref) {
50
48
  placement: tooltipPlacement,
51
49
  offset: 8,
52
50
  });
53
- return (_jsx(InputContainer, { label: label, htmlFor: inputId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: _jsxs("div", { style: rootStyle, className: [
51
+ return (_jsx(InputContainer, { label: label, htmlFor: inputId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: _jsxs("div", { style: rootStyle, className: [
54
52
  styles.container,
55
53
  fullWidth ? styles.fullWidth : '',
56
54
  onClear ? styles.withClear : '',
@@ -4,7 +4,6 @@
4
4
  display: inline-flex;
5
5
  align-items: stretch;
6
6
  gap: 0;
7
-
8
7
  /* width control */
9
8
  inline-size: var(--input-width, auto);
10
9
  min-inline-size: var(--input-min-width, 0);
@@ -33,14 +32,13 @@
33
32
  flex: 1 1 auto;
34
33
  min-inline-size: 0; /* critical */
35
34
  inline-size: 100%;
36
-
35
+ background: var(--color-bg-surface);
37
36
  font-family: var(--font-family);
38
37
  font-size: var(--font-size-sm);
39
38
  line-height: var(--line-height-normal);
40
39
  box-sizing: border-box;
41
40
  text-overflow: ellipsis;
42
41
 
43
- background-color: var(--color-bg-surface);
44
42
  border: var(--border-width-thin) solid var(--color-border-default);
45
43
  border-radius: var(--border-radius-default);
46
44
 
@@ -53,6 +51,13 @@
53
51
  box-shadow var(--transition-fast) var(--ease-standard);
54
52
  }
55
53
 
54
+ .input:disabled {
55
+ background-color: var(--color-disabled-bg);
56
+ border: 0;
57
+ color: var(--color-disabled-fg);
58
+ cursor: not-allowed;
59
+ opacity: 0.5;
60
+ }
56
61
  /* Button group styling */
57
62
  .withButton .input {
58
63
  border-top-right-radius: 0;
@@ -75,26 +80,19 @@
75
80
  }
76
81
 
77
82
  .input:focus-visible {
78
- outline: none;
79
83
  border-color: var(--color-border-selected);
80
- box-shadow: var(--focus-ring);
81
84
  }
82
85
 
83
86
  /* Variants */
84
87
  .filled {
85
88
  background-color: var(--color-bg-surface);
86
- border: 0;
87
89
  }
88
90
  .standalone {
89
91
  border-radius: var(--border-radius-rounded);
90
92
  background-color: var(--color-bg-surface);
91
93
  box-shadow: var(--shadow-xs), var(--shadow-md);
92
94
  }
93
- .standalone:focus-visible {
94
- outline: none;
95
- border-color: var(--color-border-selected);
96
- box-shadow: var(--focus-ring);
97
- }
95
+
98
96
  .outlined {
99
97
  background-color: transparent;
100
98
  }
@@ -11,5 +11,6 @@ export interface InputContainerProps {
11
11
  orientation?: 'vertical' | 'horizontal';
12
12
  labelWidth?: string;
13
13
  labelAlignment?: 'top' | 'center';
14
+ modified?: boolean;
14
15
  }
15
- export declare function InputContainer({ label, htmlFor, error, helpText, helpTextAddition, fullWidth, required, children, orientation, labelWidth, }: InputContainerProps): React.ReactElement;
16
+ export declare function InputContainer({ label, htmlFor, error, helpText, helpTextAddition, fullWidth, required, children, orientation, labelWidth, modified, }: InputContainerProps): React.ReactElement;
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import styles from './InputContainer.module.css';
3
- export function InputContainer({ label, htmlFor, error, helpText, helpTextAddition, fullWidth = false, required = false, children, orientation = 'horizontal', labelWidth = '120px', }) {
3
+ export function InputContainer({ label, htmlFor, error, helpText, helpTextAddition, fullWidth = false, required = false, children, orientation = 'horizontal', labelWidth = '160px', modified = false, }) {
4
4
  const message = error !== null && error !== void 0 ? error : helpText;
5
5
  const messageClass = error ? styles.errorText : styles.helpText;
6
6
  const renderLabel = label && (_jsxs("label", { className: styles.label, htmlFor: htmlFor, children: [label, required && _jsx("span", { className: styles.required, children: " *" })] }));
7
7
  const renderMessageRow = (message || helpTextAddition) && (_jsxs("div", { className: `${messageClass} ${styles.messageRow}`, children: [_jsx("span", { children: message }), helpTextAddition && _jsx("span", { className: styles.helpTextAddition, children: helpTextAddition })] }));
8
8
  if (orientation === 'vertical') {
9
- return (_jsxs("div", { className: "dbc-flex dbc-flex-column dbc-gap-xs", style: { width: fullWidth ? '100%' : undefined }, children: [renderLabel, children, renderMessageRow] }));
9
+ return (_jsxs("div", { "data-modified": modified ? true : undefined, className: `dbc-flex dbc-flex-column dbc-gap-xs ${styles.inputContainer}`, style: { width: fullWidth ? '100%' : undefined }, children: [renderLabel, children, renderMessageRow] }));
10
10
  }
11
- return (_jsx("div", { className: styles.inputContainer, style: {
11
+ return (_jsx("div", { "data-modified": modified ? true : undefined, className: styles.inputContainer, style: {
12
12
  '--label-width': labelWidth,
13
13
  width: fullWidth ? '100%' : undefined,
14
14
  }, children: _jsxs("div", { className: `${styles.horizontal} dbc-flex dbc-flex-column dbc-gap-xs`, children: [_jsxs("div", { className: `${styles.labelContainer} dbc-flex dbc-items-center dbc-gap-xs`, children: [renderLabel, children] }), renderMessageRow] }) }));
@@ -6,6 +6,12 @@
6
6
  gap: var(--gap);
7
7
  }
8
8
 
9
+ .label {
10
+ color: var(--color-fg-default);
11
+ font-size: var(--font-size-sm);
12
+ font-weight: var(--font-weight-medium);
13
+ }
14
+
9
15
  .horizontal .errorText,
10
16
  .horizontal .helpText {
11
17
  margin-left: calc(var(--label-width) + var(--gap));
@@ -32,3 +38,62 @@
32
38
  color: var(--color-status-error);
33
39
  font-weight: bold;
34
40
  }
41
+
42
+ /* ---------------- MODIFIED FIELD (DIRECT CONTROL TINT) ---------------- */
43
+
44
+ /* Optional scan cue: left bar only (no box around control) */
45
+ .inputContainer[data-modified] {
46
+ border-left: var(--border-width-thick) solid var(--color-status-warning-border);
47
+ padding-left: var(--spacing-xs);
48
+ }
49
+
50
+ /**
51
+ * Tint "real controls" directly.
52
+ * This covers:
53
+ * - native input/textarea
54
+ * - button-based components (Select trigger, Checkbox button, etc.)
55
+ * - combobox triggers
56
+ */
57
+ .inputContainer[data-modified]
58
+ :is(input, textarea, button[data-forminput], [role='combobox'][data-forminput]) {
59
+ background-color: color-mix(in srgb, var(--color-status-warning-bg) 45%, var(--color-bg-surface));
60
+ }
61
+
62
+ /**
63
+ * If your controls also have borders, nudge them slightly warmer.
64
+ * (Keep subtle so it doesn't look like validation.)
65
+ */
66
+ .inputContainer[data-modified]
67
+ :is(input, textarea, button[data-forminput], [role='combobox'][data-forminput]) {
68
+ border-color: color-mix(
69
+ in srgb,
70
+ var(--color-status-warning-border) 35%,
71
+ var(--color-border-default)
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Checkbox/Radio special case:
77
+ * - Their "label" is inside children.
78
+ * - InputContainer’s own label uses .label class.
79
+ * We want to tint ONLY the child labels, not the container label column.
80
+ */
81
+ .inputContainer[data-modified] label:not(.label) {
82
+ background-color: color-mix(in srgb, var(--color-status-warning-bg) 35%, transparent);
83
+ border-radius: var(--border-radius-md);
84
+ padding: 2px 6px;
85
+ }
86
+
87
+ /* Correct disabled selector (no accidental “actionable” tint) */
88
+ .inputContainer[data-modified]
89
+ :is(input, textarea, button[data-forminput], [role='combobox'][data-forminput]):disabled,
90
+ .inputContainer[data-modified]
91
+ :is(
92
+ input,
93
+ textarea,
94
+ button[data-forminput],
95
+ [role='combobox'][data-forminput]
96
+ )[aria-disabled='true'] {
97
+ background-color: var(--color-disabled-bg);
98
+ border-color: var(--color-disabled-border);
99
+ }
@@ -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 = '120px', fullWidth = true, required, tooltip, tooltipPlacement = 'right',
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 = '120px', fullWidth = false, required,
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
  }