@synerise/ds-subtle-form 1.2.7 → 1.2.9

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/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.2.9](https://github.com/Synerise/synerise-design/compare/@synerise/ds-subtle-form@1.2.8...@synerise/ds-subtle-form@1.2.9) (2026-07-24)
7
+
8
+ **Note:** Version bump only for package @synerise/ds-subtle-form
9
+
10
+ ## [1.2.8](https://github.com/Synerise/synerise-design/compare/@synerise/ds-subtle-form@1.2.7...@synerise/ds-subtle-form@1.2.8) (2026-07-23)
11
+
12
+ **Note:** Version bump only for package @synerise/ds-subtle-form
13
+
6
14
  ## [1.2.7](https://github.com/Synerise/synerise-design/compare/@synerise/ds-subtle-form@1.2.6...@synerise/ds-subtle-form@1.2.7) (2026-07-16)
7
15
 
8
16
  **Note:** Version bump only for package @synerise/ds-subtle-form
package/CLAUDE.md ADDED
@@ -0,0 +1,172 @@
1
+ # SubtleForm (`@synerise/ds-subtle-form`)
2
+
3
+ > A compound form component providing 5 inline-edit sub-components (TextArea, Input, Select, DatePicker, Field) that toggle between a static display state and an active edit state on click/hover.
4
+
5
+ ## Package structure
6
+
7
+ ```
8
+ src/
9
+ SubtleForm.tsx — namespace component (returns null); attaches sub-components as static properties
10
+ SubtleForm.types.ts — SubtleFieldProps (shared base type)
11
+ SubtleForm.styles.ts — shared styled components: Inactive, MainContent, Suffix, Container, Subtle, SubtleFormField, ValueArea; animation keyframes
12
+ index.ts — default export only (no type re-exports)
13
+ Elements/
14
+ TextArea/TextArea.tsx — textarea with dynamic row height calculation
15
+ TextArea/TextArea.types.ts — SubtleTextAreaProps, TextareaAutosize
16
+ Input/Input.tsx — single-line text input
17
+ Input/Input.types.ts — SubtleInputProps
18
+ Select/Select.tsx — Ant Design select dropdown
19
+ Select/Select.types.ts — SubtleSelectProps
20
+ Select/Select.styles.ts — SelectContainer (handles active margin offset)
21
+ DatePicker/DatePicker.tsx — date picker with formatted value display and mask placeholder
22
+ DatePicker/DatePicker.types.ts — SubtleDatePickerProps
23
+ DatePicker/DatePicker.styles.ts — SelectContainer, MaskedDatePlaceholder
24
+ DatePicker/utils.ts — getFormattingString, replaceLettersWithUnderscore
25
+ Field/Field.tsx — generic field: renders activeElement() / inactiveElement(); uses useOnClickOutside
26
+ __specs__/
27
+ TextArea.spec.tsx — Vitest tests (TextArea only)
28
+ ```
29
+
30
+ ## Public exports
31
+
32
+ ### `SubtleForm` (default export)
33
+
34
+ The component itself returns `null`. It is used only as a namespace:
35
+
36
+ - `SubtleForm.TextArea`
37
+ - `SubtleForm.Input`
38
+ - `SubtleForm.Select`
39
+ - `SubtleForm.DatePicker`
40
+ - `SubtleForm.Field`
41
+
42
+ No type exports from `index.ts`.
43
+
44
+ ---
45
+
46
+ ### Shared `SubtleFieldProps` (base for all sub-components)
47
+
48
+ | Prop | Type | Description |
49
+ |------|------|-------------|
50
+ | `label` | `FormFieldCommonProps['label']` | Label above the field |
51
+ | `labelTooltip` | `FormFieldCommonProps['tooltip']` | Tooltip on the label |
52
+ | `errorText` | `FormFieldCommonProps['errorText']` | Error message; forces active/edit state |
53
+ | `disabled` | `boolean` | Disables interactions, 0.5 opacity |
54
+ | `suffix` | `ReactNode` | Custom suffix icon in inactive state (replaces default edit icon) |
55
+ | `suffixTooltip` | `ReactNode` | Tooltip on the suffix icon |
56
+ | `active` | `boolean` | Controlled active state (`Field` only) |
57
+ | `activeElement` | `() => ReactElement` | Render function for edit state (`Field` only) |
58
+ | `inactiveElement` | `() => ReactElement` | Render function for display state (`Field` only) |
59
+ | `mask` | `string` | Mask string shown below inactive content (`Field` only) |
60
+ | `maskVisible` | `boolean` | Whether to show the mask (`Field` only) |
61
+
62
+ ---
63
+
64
+ ### `SubtleForm.TextArea`
65
+
66
+ | Prop | Type | Default | Description |
67
+ |------|------|---------|-------------|
68
+ | `value` | `string` | — | Current value |
69
+ | `onChange` | `(value: string) => void` | — | Called on input change |
70
+ | `placeholder` | `string` | — | Shown when no value |
71
+ | `minRows` | `number` | `1` | Minimum textarea rows |
72
+ | `maxRows` | `number` | — | Maximum textarea rows (clamps auto-height) |
73
+ | `error` | `boolean` | — | Error state; forces edit mode |
74
+ | `textAreaProps` | `TextAreaProps` | — | Props forwarded to `@synerise/ds-input` TextArea |
75
+ | + all `SubtleFieldProps` | | | |
76
+
77
+ ### `SubtleForm.Input`
78
+
79
+ | Prop | Type | Default | Description |
80
+ |------|------|---------|-------------|
81
+ | `value` | `string` | — | Current value |
82
+ | `onChange` | `(value: string) => void` | — | Called on input change |
83
+ | `placeholder` | `string` | — | Shown when no value |
84
+ | `error` | `boolean` | — | Error state; forces edit mode |
85
+ | `inputProps` | `InputProps` | — (**required**) | Props forwarded to Ant Design `Input` |
86
+ | + all `SubtleFieldProps` | | | |
87
+
88
+ ### `SubtleForm.Select`
89
+
90
+ Extends `SubtleFieldProps` + `@synerise/ds-select` props. Additional:
91
+
92
+ | Prop | Type | Description |
93
+ |------|------|-------------|
94
+ | `placeholder` | `string` | Shown when no value |
95
+ | `dropdownAlign` | `object` | Ant Design `dropdownAlign`; defaults to `{}` (offset `[0, 8]` applied) |
96
+ | `children` | `ReactNode` | `Select.Option` children |
97
+
98
+ ### `SubtleForm.DatePicker`
99
+
100
+ Extends `SubtleFieldProps` + `@synerise/ds-date-picker` props. Additional:
101
+
102
+ | Prop | Type | Description |
103
+ |------|------|-------------|
104
+ | `value` | `Date` | Selected date |
105
+ | `onApply` | `(date: Date) => void` | Called when date is confirmed; also deactivates the field |
106
+ | `onClear` | `() => void` | Called on clear; also deactivates |
107
+ | `format` | `string` | Date format string (default: `'dd-MM-yyyy'`, or `'dd-MM-yyyy, HH:mm'` when `showTime`) |
108
+ | `placeholder` | `string` | Shown when no value |
109
+ | `activeProp` | `boolean` | External control of active state |
110
+
111
+ ### `SubtleForm.Field`
112
+
113
+ Generic field using `SubtleFieldProps` props only (no additional props). Uses `activeElement()` and `inactiveElement()` callbacks to render edit/display states.
114
+
115
+ ## Usage patterns
116
+
117
+ ```tsx
118
+ import SubtleForm from '@synerise/ds-subtle-form';
119
+
120
+ // TextArea
121
+ const [value, setValue] = React.useState('');
122
+ <SubtleForm.TextArea
123
+ value={value}
124
+ onChange={setValue}
125
+ placeholder="Description"
126
+ label="Label"
127
+ labelTooltip="Tooltip"
128
+ suffixTooltip="Edit"
129
+ minRows={3}
130
+ />
131
+
132
+ // Input
133
+ <SubtleForm.Input
134
+ value={name}
135
+ onChange={setName}
136
+ placeholder="Name"
137
+ label="Name"
138
+ inputProps={{}}
139
+ />
140
+
141
+ // Custom Field
142
+ <SubtleForm.Field
143
+ active={active}
144
+ activeElement={() => <InputNumber value={val} onBlur={() => setActive(false)} />}
145
+ inactiveElement={() => <>{val ?? 'placeholder'}</>}
146
+ label="Number"
147
+ />
148
+ ```
149
+
150
+ ## Styling
151
+
152
+ `SubtleForm.styles.ts`. Key pattern: `Inactive` div has `padding: 7px 40px 7px 0` at rest, animates to `padding: 7px 28px 7px 12px` on hover. The `Suffix` icon uses `opacity: 0` → `1` transition on hover. A `blurAnimation` keyframe plays once after blur to reverse the padding. `SubtleFormField` (extends `FormField`) uses `gap: 10px` when active, `8px` at rest.
153
+
154
+ ## Key dependencies
155
+
156
+ - `@synerise/ds-input` — `Input` and `TextArea` for edit state
157
+ - `@synerise/ds-select` — `Select` for edit state
158
+ - `@synerise/ds-date-picker` — `DatePicker` for edit state
159
+ - `@synerise/ds-form-field` — `FormField` wrapper for label + error
160
+ - `@synerise/ds-utils` — `useOnClickOutside` (Field only)
161
+ - `calculate-size` — measures text pixel width to compute visible rows in TextArea
162
+ - `@synerise/ds-tooltip` — suffix tooltip in inactive state
163
+
164
+ ## Implementation notes
165
+
166
+ - **Active/inactive toggle** — all sub-components maintain local `active` and `blurred` state. `errorText || error` forces the active/edit view.
167
+ - **`SubtleForm()` returns `null`** — the main component is a namespace-only wrapper; calling it renders nothing.
168
+ - **`inputProps` is required on `SubtleForm.Input`** — not optional in `SubtleInputProps`; pass `inputProps={{}}` if you have no extra props.
169
+ - **`activeProp` on DatePicker is unused in logic** — the prop is declared but not wired into the state machine (only `error` triggers the forced active path).
170
+ - **Deep import in Storybook** — stories import `@synerise/ds-subtle-form/dist/Elements/DatePicker/utils` directly. This is a fragile deep path not covered by public exports.
171
+ - **Only TextArea has tests** — Input, Select, DatePicker, and Field have no spec files.
172
+ - **Uses Vitest**.
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
2
  import { SubtleSelectProps } from './Select.types';
3
3
  declare const SubtleSelect: {
4
- ({ disabled, value, suffix, suffixTooltip, label, children, labelTooltip, placeholder, error, errorText, dropdownAlign, ...rest }: SubtleSelectProps): React.JSX.Element;
4
+ ({ disabled, value, suffix, suffixTooltip, label, children, labelTooltip, placeholder, error, errorText, ...rest }: SubtleSelectProps): React.JSX.Element;
5
5
  displayName: string;
6
6
  };
7
7
  export default SubtleSelect;
@@ -17,7 +17,6 @@ const SubtleSelect = ({
17
17
  placeholder,
18
18
  error,
19
19
  errorText,
20
- dropdownAlign = {},
21
20
  ...rest
22
21
  }) => {
23
22
  const [active, setActive] = useState(false);
@@ -33,10 +32,7 @@ const SubtleSelect = ({
33
32
  setBlurred(false);
34
33
  }, []);
35
34
  const theme = useTheme();
36
- return /* @__PURE__ */ jsx(Subtle, { className: "ds-subtle-form", $disabled: disabled, children: /* @__PURE__ */ jsx(SubtleFormField, { $active: active, label, tooltip: labelTooltip, children: /* @__PURE__ */ jsx(SelectContainer, { ref: containerRef, className: "ds-subtle-select", active, children: active && !blurred || hasError ? /* @__PURE__ */ jsx(Select, { disabled, autoFocus: !hasError, size: "middle", onBlur: handleDeactivate, value, placeholder, errorText, error, defaultOpen: !hasError, dropdownAlign: {
37
- offset: [0, 8],
38
- ...dropdownAlign
39
- }, ...rest, children }) : /* @__PURE__ */ jsxs(Inactive, { className: "inactive-content", onClick: !disabled ? handleActivate : void 0, $blurred: blurred, $disabled: disabled, children: [
35
+ return /* @__PURE__ */ jsx(Subtle, { className: "ds-subtle-form", $disabled: disabled, children: /* @__PURE__ */ jsx(SubtleFormField, { $active: active, label, tooltip: labelTooltip, children: /* @__PURE__ */ jsx(SelectContainer, { ref: containerRef, className: "ds-subtle-select", active, children: active && !blurred || hasError ? /* @__PURE__ */ jsx(Select, { disabled, autoFocus: !hasError, size: "middle", onBlur: handleDeactivate, value, placeholder, errorText, error, defaultOpen: !hasError, ...rest, children }) : /* @__PURE__ */ jsxs(Inactive, { className: "inactive-content", onClick: !disabled ? handleActivate : void 0, $blurred: blurred, $disabled: disabled, children: [
40
36
  /* @__PURE__ */ jsx(MainContent, { className: "main-content", hasMargin: true, children: /* @__PURE__ */ jsx(Fragment, { children: value && !!String(value).trim() ? value : placeholder }) }),
41
37
  !active && !disabled && suffix && /* @__PURE__ */ jsx(Suffix, { select: true, children: /* @__PURE__ */ jsx(Tooltip, { title: suffixTooltip, children: /* @__PURE__ */ jsx(Icon, { component: /* @__PURE__ */ jsx(AngleDownS, {}), color: theme.palette["grey-600"] }) }) })
42
38
  ] }) }) }) });
@@ -2,7 +2,7 @@ import styled, { css } from "styled-components";
2
2
  const SelectContainer = /* @__PURE__ */ styled.div.withConfig({
3
3
  displayName: "Selectstyles__SelectContainer",
4
4
  componentId: "sc-u85d8m-0"
5
- })(["position:relative;min-height:32px;width:100%;", " .ant-select-focused{.ds-status{margin:-1px 0 0 0;}}.ds-status{margin:0;}div.ant-select-selector{transition:border 0s linear !important;}"], (props) => props.active && css(["margin:-2px 0 0 -1px;"]));
5
+ })(["position:relative;min-height:32px;width:100%;", " .ds-select-open{.ds-status{margin:-1px 0 0 0;}}.ds-status{margin:0;}.ds-select{transition:border 0s linear !important;}"], (props) => props.active && css(["margin:-2px 0 0 -1px;"]));
6
6
  export {
7
7
  SelectContainer
8
8
  };
@@ -5,7 +5,7 @@ declare const SubtleForm: {
5
5
  displayName: string;
6
6
  };
7
7
  Select: {
8
- ({ disabled, value, suffix, suffixTooltip, label, children, labelTooltip, placeholder, error, errorText, dropdownAlign, ...rest }: import('./Elements/Select/Select.types').SubtleSelectProps): import("react").JSX.Element;
8
+ ({ disabled, value, suffix, suffixTooltip, label, children, labelTooltip, placeholder, error, errorText, ...rest }: import('./Elements/Select/Select.types').SubtleSelectProps): import("react").JSX.Element;
9
9
  displayName: string;
10
10
  };
11
11
  DatePicker: {
@@ -46,7 +46,7 @@ const ValueArea = /* @__PURE__ */ styled.textarea.withConfig({
46
46
  const Container = /* @__PURE__ */ styled.div.withConfig({
47
47
  displayName: "SubtleFormstyles__Container",
48
48
  componentId: "sc-1wunvof-4"
49
- })(["&.ds-subtle-input{", "{line-height:18px;}input::placeholder{line-height:16px;}}position:relative;width:100%;", " > div{margin:0;}.ds-subtle-select{.ant-select-selector:{transition:all 0s linear !important;}}&& .ds-input-number-input::placeholder{padding-bottom:8px;}", ""], Inactive, (props) => !!props.active && css(["margin:-1px 0 0 -1px;"]), (props) => !!props.disabled && css(["&&{cursor:not-allowed;}"]));
49
+ })(["&.ds-subtle-input{", "{line-height:18px;}input::placeholder{line-height:16px;}}position:relative;width:100%;", " > div{margin:0;}.ds-subtle-select{.ds-select{transition:all 0s linear !important;}}&& .ds-input-number-input::placeholder{padding-bottom:8px;}", ""], Inactive, (props) => !!props.active && css(["margin:-1px 0 0 -1px;"]), (props) => !!props.disabled && css(["&&{cursor:not-allowed;}"]));
50
50
  const Subtle = /* @__PURE__ */ styled.div.withConfig({
51
51
  displayName: "SubtleFormstyles__Subtle",
52
52
  componentId: "sc-1wunvof-5"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-subtle-form",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "SubtleForm UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "Synerise/synerise-design",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "/dist",
19
19
  "CHANGELOG.md",
20
+ "CLAUDE.md",
20
21
  "README.md",
21
22
  "package.json",
22
23
  "LICENSE.md"
@@ -41,13 +42,13 @@
41
42
  ],
42
43
  "types": "dist/index.d.ts",
43
44
  "dependencies": {
44
- "@synerise/ds-date-picker": "^1.4.21",
45
- "@synerise/ds-form-field": "^1.3.23",
46
- "@synerise/ds-icon": "^1.18.4",
47
- "@synerise/ds-input": "^1.7.12",
48
- "@synerise/ds-select": "^1.3.33",
49
- "@synerise/ds-tooltip": "^1.5.3",
50
- "@synerise/ds-utils": "^1.10.1",
45
+ "@synerise/ds-date-picker": "^1.4.23",
46
+ "@synerise/ds-form-field": "^1.3.24",
47
+ "@synerise/ds-icon": "^1.18.5",
48
+ "@synerise/ds-input": "^1.7.14",
49
+ "@synerise/ds-select": "^1.4.0",
50
+ "@synerise/ds-tooltip": "^1.5.4",
51
+ "@synerise/ds-utils": "^1.10.2",
51
52
  "calculate-size": "^1.1.1"
52
53
  },
53
54
  "peerDependencies": {
@@ -57,5 +58,5 @@
57
58
  "styled-components": "^5.3.3",
58
59
  "vitest": "4"
59
60
  },
60
- "gitHead": "a81ab6519d49a3dea9c0cfebcdc9104cbb4f4226"
61
+ "gitHead": "018414e0d7ffd102a07f8aa35fa2e2d3d4ed19e2"
61
62
  }