freemium-ui 2.0.133 → 2.0.134
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/.github/actionlint.yaml +6 -0
- package/.github/pr_validation_guide.md +305 -0
- package/.github/pull_request_template.md +189 -0
- package/.github/workflows/pr-quality.yml +114 -0
- package/.github/workflows/pr-template-validation.yml +367 -0
- package/.github/workflows/sonarqube-main.yml +106 -0
- package/.nvmrc +1 -0
- package/README.md +9 -0
- package/catalog-info.yaml +33 -0
- package/docs/adrs/index.md +11 -0
- package/docs/architecture/code-structure.md +109 -0
- package/docs/architecture/deep-dive.md +193 -0
- package/docs/getting-started.md +103 -0
- package/docs/index.md +35 -0
- package/docs/operations/alerts-configuration.md +11 -0
- package/docs/operations/how-to-debug.md +43 -0
- package/docs/operations/monitoring.md +16 -0
- package/docs/operations/recent-issues.md +14 -0
- package/docs/operations/sop.md +25 -0
- package/docs/operations/support.md +15 -0
- package/lib/README.md +9 -0
- package/lib/index.cjs.js +1041 -1021
- package/lib/index.d.ts +2 -0
- package/lib/index.js +315 -294
- package/lib/package.json +1 -1
- package/mkdocs.yml +38 -0
- package/package.json +3 -1
- package/sonar-project.properties +25 -0
- package/tech-docs/docs/architecture/components.md +49 -0
- package/tech-docs/docs/architecture/index.md +54 -0
- package/tech-docs/docs/development/build-publish.md +42 -0
- package/tech-docs/docs/development/storybook.md +37 -0
- package/tech-docs/docs/getting-started.md +67 -0
- package/tech-docs/docs/index.md +47 -0
- package/tech-docs/docs/repositories/index.md +22 -0
- package/tech-docs/mkdocs.yml +28 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Code Structure
|
|
2
|
+
|
|
3
|
+
"Where do I find X" map. All components live under `src/components/<Name>/`. The near-universal per-component file split is `<Name>.tsx` (implementation), `<Name>.types.ts` (props/types), `styles.ts` (styled-components), `index.ts` (public re-exports) — see [Deep Dive → Idiomatic vernacular](deep-dive.md#4-idiomatic-vernacular) for exceptions and the canonical file to copy when adding a new component.
|
|
4
|
+
|
|
5
|
+
Every component below is exported from `src/components/index.ts` (and therefore from the npm package root, `import { X } from 'freemium-ui'`) **unless marked "not exported"** — see [Deep Dive → Contracts & interfaces](deep-dive.md#2-contracts--interfaces) for the full PUBLIC/INTERNAL table.
|
|
6
|
+
|
|
7
|
+
## Form inputs & selection
|
|
8
|
+
|
|
9
|
+
| Component | Path | Description |
|
|
10
|
+
| ---------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `Input` | `src/components/Input/Input.tsx` | Single-line text input with label, error, adornment, and character-count support. |
|
|
12
|
+
| `TextArea` | `src/components/Input/TextArea.tsx` | Multi-line input box for larger free text. |
|
|
13
|
+
| `Checkbox` | `src/components/Checkbox/Checkbox.tsx` | Wraps `@radix-ui/react-checkbox`; adds label/required/error styling. |
|
|
14
|
+
| `Radio` / `RadioGroup` | `src/components/Radio/Radio.tsx`, `RadioGroup.tsx` | Single radio button and the group container built on `@radix-ui/react-radio-group`. |
|
|
15
|
+
| `BoxedRadio` | `src/components/BoxedRadio/BoxedRadio.tsx` | Radio group rendered as selectable "boxed" cards instead of plain radio dots. |
|
|
16
|
+
| `Toggle` | `src/components/Toggle/Toggle.tsx` | On/off switch button. |
|
|
17
|
+
| `Dropdown` | `src/components/Dropdown/Dropdown.tsx` | Single-select combobox/select; wraps `@radix-ui/react-select` + `downshift`. |
|
|
18
|
+
| `MultiSelectDropdown` | `src/components/MultiSelectDropdown/MultiSelectDropdown.tsx` | Multi-select combobox with chips; built on `downshift`'s `useMultipleSelection`. |
|
|
19
|
+
| `AutoComplete` | `src/components/AutoComplete/AutoComplete.tsx` | Dropdown variant that fetches its own options from a URL via `axios` (`request` util). |
|
|
20
|
+
| `QuillEditor` | `src/components/QuillEditor/QuillEditor.tsx` | Rich text editor wrapping `react-quill`. **Not exported** from `src/components/index.ts` — internal/unused from the package's public surface today. |
|
|
21
|
+
|
|
22
|
+
## Buttons & actions
|
|
23
|
+
|
|
24
|
+
| Component | Path | Description |
|
|
25
|
+
| ---------------- | -------------------------------------------------- | ---------------------------------------------------------------- |
|
|
26
|
+
| `Button` | `src/components/Button/Button.tsx` | Primary/secondary/danger/link button with loading state. |
|
|
27
|
+
| `ButtonDropdown` | `src/components/ButtonDropdown/ButtonDropdown.tsx` | Button that opens a `Menu`-style dropdown of selectable options. |
|
|
28
|
+
|
|
29
|
+
## Overlays & disclosure
|
|
30
|
+
|
|
31
|
+
| Component | Path | Description |
|
|
32
|
+
| ----------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `Modal` | `src/components/Modal/Modal.tsx` | Overlay dialog (confirm boxes, forms); built on Radix `Dialog`. |
|
|
34
|
+
| `Drawer` | `src/components/Drawer/Drawer.tsx` | Slide-in side panel; same Radix `Dialog` primitive as `Modal`, different placement/size styling. |
|
|
35
|
+
| `Popover` | `src/components/Popover/Popover.tsx` | Trigger + floating content panel; wraps `@radix-ui/react-popover`. |
|
|
36
|
+
| `Tooltip` | `src/components/Tooltip/Tooltip.tsx` | Hover/focus label; wraps `@radix-ui/react-tooltip`. |
|
|
37
|
+
| `Menu` | `src/components/Menu/Menu.tsx` | Trigger + list of selectable menu items; wraps `@radix-ui/react-dropdown-menu` + Radix `Popover` positioning. |
|
|
38
|
+
| `Accordion` | `src/components/Accordion/Accordion.tsx` | Expand/collapse single-select sections; wraps `@radix-ui/react-accordion`. |
|
|
39
|
+
| `Tabs` | `src/components/Tabs/Tabs.tsx` | Tabbed content switcher; wraps `@radix-ui/react-tabs`. |
|
|
40
|
+
|
|
41
|
+
## Data display
|
|
42
|
+
|
|
43
|
+
| Component | Path | Description |
|
|
44
|
+
| ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
45
|
+
| `Table` | `src/components/Table/Table.tsx` | Full-featured data table (sorting, pagination, column customize, row select, row menu); built on `@tanstack/react-table`. |
|
|
46
|
+
| `WorkspaceTable` | `src/components/WorkspaceTable/WorkspaceTable.tsx` | `Table` variant tuned for the "workspace" layout (adds `isWSOpen`, drops a few `Table`-only props such as `hideActions`/`sortingOptions`). |
|
|
47
|
+
| `Tag` | `src/components/Tag/Tag.tsx` | Small label/chip, optionally with avatar and a close button. |
|
|
48
|
+
| `Typography` | `src/components/Typography/Typography.tsx` | Text primitive for headings/body/caption variants and weights. |
|
|
49
|
+
| `Reorder` | `src/components/Reorder/Reorder.tsx` | Drag-to-reorder list; wraps `@dnd-kit/core` + `@dnd-kit/sortable`. |
|
|
50
|
+
|
|
51
|
+
## Feedback
|
|
52
|
+
|
|
53
|
+
| Component | Path | Description |
|
|
54
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
55
|
+
| `Alert` | `src/components/Alert/Alert.tsx` | Inline banner (success/warning/error/info) with optional dismiss/timeout/link. |
|
|
56
|
+
| `Toast` (`ToastProvider`, `useToasts`) | `src/components/Toast/ToastProvider.tsx`, `use-toasts.tsx`, `ToastContainer.tsx`, `ToastItem.tsx` | Ephemeral notifications rendered via a React Context queue; `ToastProvider` is mounted automatically by `ThemeProvider`. |
|
|
57
|
+
| `Loader` | `src/components/Loader/Loader.tsx` | Spinner, sized `mini`/`small`/`medium`/`big`. |
|
|
58
|
+
| `Confirmation` | `src/components/Confirmation/Confirmation.tsx` | Confirm/cancel dialog; built on the same Radix `Dialog` primitive as `Modal`. |
|
|
59
|
+
|
|
60
|
+
## Pickers
|
|
61
|
+
|
|
62
|
+
| Component | Path | Description |
|
|
63
|
+
| --------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
64
|
+
| `DatePicker` | `src/components/Pickers/DatePicker.tsx` | Single date input + calendar popup; wraps `@blueprintjs/datetime2`'s `DateInput3`. |
|
|
65
|
+
| `DateRangePicker` | `src/components/Pickers/DateRangePicker.tsx` | Date range input + calendar; wraps `DateRangeInput3`. |
|
|
66
|
+
| `DateTimePicker` | `src/components/Pickers/DateTimePicker.tsx` | Date input with a time component. |
|
|
67
|
+
| `DateTimeRangePicker` | `src/components/Pickers/DateTimeRangePicker.tsx` | Date range input with a time component. |
|
|
68
|
+
| `TimePicker` | `src/components/Pickers/TimePicker.tsx` | Time-only input with AM/PM and precision options. |
|
|
69
|
+
| `Calendar` | `src/components/Calendar/Calendar.tsx` | Lower-level calendar/day-grid primitive used to build the pickers above. |
|
|
70
|
+
|
|
71
|
+
## Theming
|
|
72
|
+
|
|
73
|
+
| Symbol | Path | Description |
|
|
74
|
+
| ------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
75
|
+
| `ThemeProvider` | `src/components/ThemeProvider/ThemeProvider.tsx` | Root wrapper: applies the `styled-components` `ThemeProvider`, global box-sizing reset, and mounts `ToastProvider`. See [Deep Dive → Architecture & control flow](deep-dive.md#1-architecture--control-flow). |
|
|
76
|
+
| `light`, `dark`, `DewTheme`, `Themes` | `src/themes/index.ts` | Concrete theme objects (`FreemiumUiTheme` shape) — classic light, classic dark, and the Freshworks Dew 2.0 design-system theme (CSS-variable driven). |
|
|
77
|
+
| `palette` | `src/themes/palette.ts` | Raw Freshworks color palette (`FreshworksPalette`) that `light`/`dark` are built from. |
|
|
78
|
+
| `FreemiumUIGlobalStyles` | `src/styles/globalStyles.ts` | Extra global CSS (z-index overrides for tooltip/popover containers) exported separately — not auto-mounted by `ThemeProvider`; consumers opt in. |
|
|
79
|
+
| Theme types | `src/themes/types.ts` | `FreemiumUiTheme`, `TextColors`, `FillColors`, `SemanticColors`, `Breakpoints`, `ZIndex`, etc. |
|
|
80
|
+
|
|
81
|
+
## Icons
|
|
82
|
+
|
|
83
|
+
| Path | Exported as | Notes |
|
|
84
|
+
| --------------------------------------- | -------------- | ------------------------------------------------------------------------------- |
|
|
85
|
+
| `src/components/Icons/Checkmark.tsx` | `Checkmark` | |
|
|
86
|
+
| `src/components/Icons/ChevronUp.tsx` | `ChevronUp` | |
|
|
87
|
+
| `src/components/Icons/Cross.tsx` | `CrossIcon` | |
|
|
88
|
+
| `src/components/Icons/Error.tsx` | `ErrorIcon` | |
|
|
89
|
+
| `src/components/Icons/Freddy.tsx` | `Freddy` | Freshworks "Freddy" assistant glyph, used by `QuillEditor`'s `freddyMenu` slot. |
|
|
90
|
+
| `src/components/Icons/Info.tsx` | `InfoIcon` | |
|
|
91
|
+
| `src/components/Icons/Minus.tsx` | `Minus` | |
|
|
92
|
+
| `src/components/Icons/MoreVertical.tsx` | `MoreVertical` | |
|
|
93
|
+
| `src/components/Icons/TickIcon.tsx` | `TickIcon` | |
|
|
94
|
+
| `src/components/Icons/Warning.tsx` | `Warning` | |
|
|
95
|
+
| `src/components/Icons/ChevronDown.tsx` | _not exported_ | Used internally by `Table` (`Table.tsx:41`) via a relative import. |
|
|
96
|
+
| `src/components/Icons/Search.tsx` | _not exported_ | Used internally by `Table/CustomizeColumns.tsx`. |
|
|
97
|
+
| `src/components/Icons/Settings.tsx` | _not exported_ | Used internally by `Table.tsx` and `WorkspaceTable.tsx`. |
|
|
98
|
+
| `src/components/Icons/Drag.tsx` | _not exported_ | Used internally by `Reorder.tsx` as the drag handle. |
|
|
99
|
+
|
|
100
|
+
## Cross-cutting / shared
|
|
101
|
+
|
|
102
|
+
| Path | Purpose |
|
|
103
|
+
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `src/global/ErrorText/` | Shared `ErrorText` styled span used by form components for validation messages (not re-exported from `src/index.ts` — internal only, imported via relative paths). |
|
|
105
|
+
| `src/hooks/index.ts` | `usePortalContainer`, `useShadowDomScrollFix` — shadow-DOM-aware portal helpers shared by every Radix-based overlay (`Modal`, `Drawer`, `Tooltip`, `Confirmation`, `Menu`, `Dropdown`, `ButtonDropdown`, `Table/SortDropdown`). Internal only. |
|
|
106
|
+
| `src/utils/index.ts` | `getId`, `isValidDateFormat`, `getPropsAndTheme`, `getThemeColorArgTypes`/`getButtonColorArgTypes` (Storybook theme-color controls), `appLogger`/`getFreshSurveyDebug` (localStorage-gated debug logging), `getPortalContainer`. Internal only. |
|
|
107
|
+
| `src/utils/request.ts` | Thin `axios` wrapper used by `AutoComplete` to fetch remote options. |
|
|
108
|
+
| `src/utils/use-portal.ts`, `use-current-state.ts`, `use-ssr.ts` | Small internal hooks (portal element creation, ref-backed state, SSR detection). |
|
|
109
|
+
| `src/stories/` | Storybook stories (`*.stories.tsx`) and MDX docs (`Colors.mdx`, `DewColors.mdx`, `DewPalette.mdx`, `DewTypography.mdx`) — one story file per component, 35 files total. |
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Deep Dive
|
|
2
|
+
|
|
3
|
+
## 1. Architecture & control flow
|
|
4
|
+
|
|
5
|
+
**Entry points.** The npm package's only entry point is `src/index.ts`, which re-exports:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
export * from './components'; // every component (see table below)
|
|
9
|
+
export * from './types'; // FreshworksPalette and related color/type shapes
|
|
10
|
+
export * from './styles'; // shared styled-components primitives (StyledLabel, dialog mixins, ...)
|
|
11
|
+
export { default as FreemiumUIGlobalStyles } from './styles/globalStyles';
|
|
12
|
+
export * as Themes from './themes'; // light, dark, DewTheme, hextoRGB, Z_INDEX, appendDewStyles, getClassByThemeType
|
|
13
|
+
export { FreemiumUiTheme } from './themes/types';
|
|
14
|
+
export { StyleSheetManager } from 'styled-components'; // convenience re-export for shadow-DOM style scoping
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Rollup (`rollup.config.ts`) builds this single entry three times: CJS (`lib/index.cjs.js`), ESM (`lib/index.js`), and a rolled-up `.d.ts` (`lib/index.d.ts`, via `rollup-plugin-dts` from the `tsc`-emitted `lib/src/index.d.ts`). There are no per-component subpath builds — the commented-out `folderBuilds`/`folderTypesBuilds` blocks in `rollup.config.ts` show a per-component build was planned but is currently dead code.
|
|
18
|
+
|
|
19
|
+
**Theming flow.** `ThemeProvider` (`src/components/ThemeProvider/ThemeProvider.tsx`) is the composition root every consumer app is expected to mount once, near their own app root:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
export const ThemeProvider: ThemeProviderType<FreemiumUiTheme> = (props) => (
|
|
23
|
+
<StyledThemeProvider theme={props.theme}>
|
|
24
|
+
<GlobalStyle />
|
|
25
|
+
<ToastProvider>{props.children}</ToastProvider>
|
|
26
|
+
</StyledThemeProvider>
|
|
27
|
+
);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
It does three things, always in this order: (1) hands the caller-supplied theme object (`Themes.light`, `Themes.dark`, or `Themes.DewTheme`) to `styled-components`' own `ThemeProvider`, making `theme` available to every styled component via `useTheme()`/the `theme` prop in a template literal; (2) injects a small global reset (`box-sizing: border-box` — `src/components/ThemeProvider/styles.ts`); (3) mounts `ToastProvider`, which means **every** subtree wrapped by `ThemeProvider` implicitly gets its own toast queue and a `<ToastContainer />`, whether or not the app calls `useToasts()`.
|
|
31
|
+
|
|
32
|
+
Individual components never receive the theme as a prop — they read it off styled-components context (`theme.colors.*`, `theme.typography.*`, `theme.zIndex.*`, `theme.shadow.*`, etc., defined by `FreemiumUiTheme` in `src/themes/types.ts`) inside their own `styles.ts`. Overlay-style components (`Modal`, `Drawer`, `Tooltip`, `Menu`, `Dropdown`, `ButtonDropdown`, `Confirmation`, `Table/SortDropdown`) additionally call `usePortalContainer`/`useShadowDomScrollFix` (`src/hooks/index.ts`) to render into the correct DOM root when the whole app is embedded inside a Shadow DOM host (a real SurveyServ deployment shape — widgets/microfrontends).
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
flowchart TD
|
|
36
|
+
App[Consumer app] --> TP[ThemeProvider theme is light or dark or DewTheme]
|
|
37
|
+
TP --> SC[styled-components ThemeProvider]
|
|
38
|
+
SC --> GS[GlobalStyle box-sizing reset]
|
|
39
|
+
SC --> ToastP[ToastProvider mounts ToastContext plus ToastContainer]
|
|
40
|
+
ToastP --> Children[props.children - rest of the app]
|
|
41
|
+
Children --> Comp[Any exported component]
|
|
42
|
+
Comp -->|reads theme via styled-components context| SC
|
|
43
|
+
Comp -->|useToasts| ToastP
|
|
44
|
+
Comp -.->|Modal Drawer Tooltip Menu Dropdown ButtonDropdown Confirmation SortDropdown| Portal[usePortalContainer + useShadowDomScrollFix]
|
|
45
|
+
Portal --> Root[ShadowRoot if inside shadow DOM, else document.body]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A typical consumer usage (from `README.md`, still accurate):
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { Button, ThemeProvider, Themes } from 'freemium-ui';
|
|
52
|
+
|
|
53
|
+
<ThemeProvider theme={Themes.light}>
|
|
54
|
+
<Button variant="primary">Freemium Button</Button>
|
|
55
|
+
</ThemeProvider>;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 2. Contracts & interfaces
|
|
59
|
+
|
|
60
|
+
Every row below is exported (directly or via `export *`) from `src/index.ts`. "Visibility" is `PUBLIC` if reachable as `import { X } from 'freemium-ui'` today, `INTERNAL` if the implementation exists in `src/components/` but is **not** re-exported from `src/components/index.ts` (verified by grep — see [Failure modes](#6-failure-modes--sharp-edges)).
|
|
61
|
+
|
|
62
|
+
| Export | Kind | Key props / signature | Inputs | Outputs (rendered/returned) | Error modes | Side effects | Visibility |
|
|
63
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `Accordion` | Component | `AccordionProps<T>`: `options`, `renderContent`, `renderTitle`, `idAccessor`, `nameAccessor`, `hideIcon`, `value`, `defaultValue`, `onChange` | list of items + render functions | Radix `Accordion.Root` (`type="single"`) | none thrown; missing `renderContent`/`renderTitle` renders nothing for that slot | none | PUBLIC |
|
|
65
|
+
| `Alert` | Component | `AlertProps`: `level` (required), `data`, `canDismiss`, `timeout`, `onClose`, `onDisappear`, `icon`, `alignContent` | `level` + optional message data | inline banner `div` | none | `setTimeout` if `timeout` set (auto-dismiss) | PUBLIC |
|
|
66
|
+
| `AutoComplete` | Component | `AutoCompleteProps<T>`: `url`, `items`, `itemToString` (required), `onChange`, `processFetchData`, `processDisplayData`, `headers` | either a static `items` array or a `url` to fetch from | `Dropdown`-style combobox | network failures from `request()` resolve to `undefined` (swallowed, see [Failure modes](#6-failure-modes--sharp-edges) in `utils/request.ts`) | **network call** via `axios` (`src/utils/request.ts`) when `url` is provided | PUBLIC |
|
|
67
|
+
| `Button` | Component | `ButtonProps`: `size`, `variant`, `loading`, `inline` + native `button` attrs | click handlers via native `onClick` | `<button>` | none | none | PUBLIC |
|
|
68
|
+
| `ButtonDropdown` | Component | `ButtonDropdownProps<T>`: `options`, `onClick`, `idAccessor`, `nameAccessor`, `defaultSelectedItem`, plus `Button` sizing/variant props | options list | `Button` + Radix popover menu | none | none | PUBLIC |
|
|
69
|
+
| `BoxedRadio` | Component | `BoxedRadioProps<T>`: `options`, `value`, `onChange`, `getTextContent` (required), `itemComponent`, `customIndicator` | options list | Radix `RadioGroup` rendered as cards | none | none | PUBLIC |
|
|
70
|
+
| `Checkbox` | Component | `CheckboxProps` extends Radix `CheckboxProps`: `isRequired`, `isErrored`, `readOnly`, `label` | boolean-ish `checked`/`onCheckedChange` (from Radix) | Radix `Checkbox.Root` | none | none | PUBLIC |
|
|
71
|
+
| `Confirmation` | Component | `ConfirmationProps`: `isOpen`, `title`, `description`, `onConfirm`, `onDismiss`, `confirmButtonText`, `cancelButtonText`, `customFooter` | boolean open state (controlled) | Radix `Dialog` styled as a confirm box | none | portal render via `usePortalContainer` | PUBLIC |
|
|
72
|
+
| `Drawer` | Component | `DrawerProps`: `isOpen`, `placement` (`left`\|`right`), `size`, `isFullHeight`, `clickOutToClose`, `onDismiss` | boolean open state (controlled) | Radix `Dialog` styled as a slide-in panel | none | portal render via `usePortalContainer`/`useShadowDomScrollFix` | PUBLIC |
|
|
73
|
+
| `Dropdown`, `DropdownItem`, `RadixSelectItem`, `DropdownInputItem` | Component + re-exports | `DropdownProps<T>`: `items`, `itemToString` (required), `onChange`, `type` (`button`\|`input`), `selectValueOnBlur`, `portalContainer` | items array + selection callbacks | Radix `Select` or a `downshift` combobox depending on `type` | none | portal render | PUBLIC |
|
|
74
|
+
| `Checkmark`, `ChevronUp`, `CrossIcon`, `ErrorIcon`, `Freddy`, `InfoIcon`, `Minus`, `MoreVertical`, `TickIcon`, `Warning` | Components | `React.SVGProps<SVGSVGElement>` | none | inline `<svg>` | none | none | PUBLIC |
|
|
75
|
+
| `ChevronDown`, `Search`, `Settings`, `Drag` (Icons) | Components | same shape as above | — | — | — | — | **INTERNAL** (used only via relative imports by `Table`, `CustomizeColumns`, `WorkspaceTable`, `Reorder`) |
|
|
76
|
+
| `Input`, `TextArea` | Components | `InputProps`/`TextAreaProps`: `isRequired`, `isErrored`, `errorText`, `label`, `maxLength`, `showCount`, `startAdornment`, `endAdornment` + native input/textarea attrs | native `value`/`onChange` | `<input>`/`<textarea>` + label/error/adornments | none | none | PUBLIC |
|
|
77
|
+
| `Loader` | Component | `LoaderPropTypes`: `size`, `speed` | — | animated spinner | none | none | PUBLIC |
|
|
78
|
+
| `Menu`, `DropdownMenuItem`, `DropdownMenuItemDew`, `RadixMenuItem` | Component + re-exports | `MenuProps<T>`: `trigger` (required), `options`, `onSelect`, `value`, `isOpen`, `closeMenuCallback` | trigger node + options | Radix `DropdownMenu` + `Popover` positioning | none | portal render | PUBLIC |
|
|
79
|
+
| `Modal` | Component | `ModalProps`: `isOpen`, `align`, `position`, `size`, `fullWidth`, `onDismiss`, `customModalContentStyles` | boolean open state (controlled) | Radix `Dialog` | none | portal render, `usePortalContainer`/`useShadowDomScrollFix` | PUBLIC |
|
|
80
|
+
| `MultiSelectDropdown` | Component | `MultiSelectDropdownProps<T>`: `items`, `defaultSelectedItems`, `onChange`, `itemToString`, `removeSelectedItem`, `maxAllowedLength`, `minAllowedLength` | items array | `downshift` `useMultipleSelection` combobox with chips | none | none | PUBLIC |
|
|
81
|
+
| `DatePicker`, `DateRangePicker`, `DateTimePicker`, `DateTimeRangePicker`, `TimePicker` | Components | `DefaultProps & Partial<@blueprintjs/datetime2 props>`: `isRequired`, `isErrored`, `errorText`, `label`, `disabled`, `dateToHilight`, `displayFormat` | ISO date strings | `@blueprintjs/datetime2` input + calendar popup | invalid date strings are treated as "no value" (`isValidDateFormat` in `src/utils/index.ts`) | none | PUBLIC |
|
|
82
|
+
| `Calendar` | Component | `CalendarProps`: `items` (required), `value`, `onChange`, `idAccessor`, `nameAccessor` | day/option items | day-grid UI, used internally by the Pickers | none | none | PUBLIC |
|
|
83
|
+
| `Popover`, `PopoverClose`, `PopoverTrigger` | Component + re-exports | `PopoverProps`: `trigger` (required), `onClose`, `popoverContentProps`, `side`, `align`, `hoverToOpen`, `hoverOutToClose` | trigger + content children | Radix `Popover` | none | portal render | PUBLIC |
|
|
84
|
+
| `Radio`, `RadioGroup` | Components | `RadioProps`, `RadioGroupProps<T>`: `options`, `idAccessor`, `nameAccessor`, `value`/`defaultValue`, `onChange` | options list | Radix `RadioGroup` | none | none | PUBLIC |
|
|
85
|
+
| `Reorder` | Component | `ReorderProps<T>`: `items` (required), `customRender` (required), `onReorder`, `disabled`, `showDragger`, `customFilter` | ordered items | `@dnd-kit` sortable list | none | drag events via `@dnd-kit/core` | PUBLIC |
|
|
86
|
+
| `Table`, `SortDropdown` (`export * from './Table/SortDropdown'`) | Components | `TableProps<T>`: `data` (required), `columns` (required), `pageSize`, `pageIndex`, `onRowSelect`, `onSelectAllRows`, `rowMenuOptions`, `onMenuOptionSelect`, `onSortingOptionSelect`, `setPageIndex`, `setSelectedRowIds`, `setColumnOrder`, `setHiddenColumns` | tabular `data`/`columns` (`@tanstack/react-table` shape) + controlled paging/sorting/selection state passed in by the caller | `<table>` with header/body/footer/pagination | none | none — fully controlled, no internal fetch/paging state | PUBLIC |
|
|
87
|
+
| `WorkspaceTable` | Component | `WorkspaceTableProps<T>`: same family as `Table` minus `sortingOptions`/`hideActions`/`onSortingOptionSelect`/`rowPropGetter` extras, plus `isWSOpen` | same as `Table` | `<table>` tuned for the workspace layout | none | none | PUBLIC |
|
|
88
|
+
| `Tabs`, `TabsContent`, `TabsRoot`, `TabsList`, `TabsTrigger` | Component + re-exports | `TabsProps<T>`: `options`, `renderTabsContent`, `value`/`defaultValue`, `onChange`, `idAccessor`, `nameAccessor`, `disabledAccessor` | tab items | Radix `Tabs` | none | none | PUBLIC |
|
|
89
|
+
| `Tag` | Component | `TagProps`: `state`, `showBorder`, `hideClose`, `avatarImgSrc`/`avatarImgAlt`/`avatarText`, `onClose` | — | `<span>` chip | none | none | PUBLIC |
|
|
90
|
+
| `ThemeProvider` | Component | `ThemeProviderType<Theme>` = `React.ComponentType<{ children, theme }>` | a `FreemiumUiTheme` object (`Themes.light`/`.dark`/`.DewTheme`) | styled-components context + global style + `ToastProvider` | none | mounts `ToastProvider` (see §1) | PUBLIC |
|
|
91
|
+
| `ToastProvider`, `useToasts` | Component + hook | `ToastProvider(children, retainOnHover=true, retainOnWindowBlur=false)`; `useToasts(): [ToastWithID[], (t: ToastType) => void, () => void]` | `ToastType` objects (`text`, `type`, `delay`, `showProgress`, `fixed`) | `ToastContainer` + toast queue array | `useToasts()` outside a `ToastProvider` silently returns the default context (`toasts: []`, no-op `addToast`) — no throw | `window` `focus`/`blur` listeners when `retainOnWindowBlur` (buggy cleanup, see [Failure modes](#6-failure-modes--sharp-edges)) | PUBLIC |
|
|
92
|
+
| `Toggle` | Component | `ToggleProps`: `on`, `handleChange`, `size`, `hasIcon`, `label` + native button attrs | boolean `on` (controlled) | `<button>` switch | none | none | PUBLIC |
|
|
93
|
+
| `Tooltip` | Component | `TooltipProps`: `position` (required), `label`/`html`, `delay`, `arrow`, `disabled`, `children` | trigger children | Radix `Tooltip` | none | portal render | PUBLIC |
|
|
94
|
+
| `Typography` | Component | `TypographyProps`: `variant`, `weight`, `children` + native span attrs | — | `<span>` | none | none | PUBLIC |
|
|
95
|
+
| `QuillEditor` | Component | `QuillEditorProps`: `disabled`, `isErrored`, `errorText`, `placeholder`, `placeholders`, `freddyMenu` | rich text value via `react-quill` | `ReactQuill` wrapper + toolbar | none | none | **INTERNAL** (own `index.ts` exists, `src/components/QuillEditor/index.ts`, but not re-exported from `src/components/index.ts` — `import { QuillEditor } from 'freemium-ui'` fails today) |
|
|
96
|
+
| `FreemiumUIGlobalStyles` | Component | `createGlobalStyle` result, no props | — | global CSS (`z-index` overrides for tooltip/popover host containers) | none | mutates global stylesheet | PUBLIC (opt-in — not auto-mounted by `ThemeProvider`) |
|
|
97
|
+
| `Themes` (namespace: `light`, `dark`, `DewTheme`, `hextoRGB`, `Z_INDEX`, `appendDewStyles`, `getClassByThemeType`) | Values/functions | `light`/`dark`/`DewTheme: DefaultTheme` (the `FreemiumUiTheme` shape); `appendDewStyles({ root, theme, mode })` injects Dew CSS variables into the DOM | a target theme name/mode | theme object, or DOM style mutation (`appendDewStyles`) | none | `appendDewStyles` mutates `document.body.className`/appends a `<style>` tag | PUBLIC |
|
|
98
|
+
| `FreemiumUiTheme` (type) | Type | shape consumed by every `styles.ts` (`colors`, `typography`, `zIndex`, `shadow`, `radius`, `opacity`, `breakpoints`, `transitions`, `components`) | — | — | — | — | PUBLIC |
|
|
99
|
+
| `FreshworksPalette` + related color/type exports (`src/types/index.ts`) | Types | raw palette shape (`primary`, `secondary` color ramps) | — | — | — | — | PUBLIC |
|
|
100
|
+
| `StyledLabel`, `InputLabel`, `RequiredIndicator`, `IconButton`, `FreshLink`, `CardContent`, `CardActions`, `CardTitle`, `CardDescription`, `CardText`, `TriggerButtonWrapper`, `dialogOverlayMixin`, `DialogChildrenWrapper`, `dialogContentMixin` (`src/styles/index.ts`) | styled-components primitives | plain styled-component props | — | rendered styled elements used internally by `Input`/`Modal`/`Drawer`/`Confirmation`/etc., also importable directly | none | none | PUBLIC |
|
|
101
|
+
| `StyleSheetManager` | Re-export | `styled-components`'s own `StyleSheetManager` | `target` (e.g. a Shadow Root) | scopes injected `<style>` tags | none | none | PUBLIC |
|
|
102
|
+
|
|
103
|
+
Internal-only support code that is **not** exported from `src/index.ts` at all (still worth knowing when tracing a bug): `usePortalContainer`, `useShadowDomScrollFix` (`src/hooks/index.ts`); `getId`, `isValidDateFormat`, `getPropsAndTheme`, `getThemeColorArgTypes`, `getButtonColorArgTypes`, `appLogger`, `getFreshSurveyDebug`, `getPortalContainer` (`src/utils/index.ts`); `usePortal`, `useCurrentState`, `useSSR`, `request` (`src/utils/use-portal.ts`, `use-current-state.ts`, `use-ssr.ts`, `request.ts`); `ErrorText` (`src/global/ErrorText/`).
|
|
104
|
+
|
|
105
|
+
## 3. Dependencies & coupling
|
|
106
|
+
|
|
107
|
+
**Depends on** (from `package.json`):
|
|
108
|
+
|
|
109
|
+
| Dependency | Version | Used for | Bundled or external at publish time? |
|
|
110
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| `react`, `react-dom` | `^17.0.2` | React itself | **external** — explicitly listed in `rollup.config.ts`'s `external: ['react', 'react-dom']` for both the CJS and ESM builds. Consumer supplies these. |
|
|
112
|
+
| `styled-components` | `^6.1.8` | Theming/CSS-in-JS for every component | **bundled** into `lib/index.cjs.js`/`lib/index.js` — see [Failure modes](#6-failure-modes--sharp-edges) for why (`package.json` has no `peerDependencies` block, so `rollup-plugin-peer-deps-external` has nothing to externalize). |
|
|
113
|
+
| `@radix-ui/react-*` (accordion, alert-dialog, avatar, checkbox, dropdown-menu, label, popover, radio-group, select, switch, tabs, toast, tooltip) | `^1.0.x`–`^2.0.x` | Accessible unstyled primitives behind most overlay/selection components | bundled |
|
|
114
|
+
| `@blueprintjs/datetime2` | `^2.3.4` | Date/time pickers (pulls in `@blueprintjs/core`, `select`, `icons`, `datetime` transitively) | bundled |
|
|
115
|
+
| `@dnd-kit/core`, `/modifiers`, `/sortable`, `/utilities` | `^6`–`^8` | `Reorder` drag-and-drop | bundled |
|
|
116
|
+
| `@tanstack/react-table` | `^8.16.0` | `Table`/`WorkspaceTable` | bundled |
|
|
117
|
+
| `downshift` | `^9.0.4` | Combobox logic for `Dropdown`/`MultiSelectDropdown`/`AutoComplete` | bundled |
|
|
118
|
+
| `axios` | `^1.7.2` | `AutoComplete`'s `request()` fetch | bundled |
|
|
119
|
+
| `moment` | `^2.30.1` | date formatting (Pickers) | bundled |
|
|
120
|
+
| `react-quill` | `^2.0.0` | `QuillEditor` (not itself public — see §2) | bundled |
|
|
121
|
+
| `lodash.debounce` | `^4.0.8` | debouncing (search/filter inputs) | bundled |
|
|
122
|
+
| `@freshworks/dew-styles` | `^0.4.4` | CSS variables/fonts for the `DewTheme` (`src/themes/index.ts`) | consumed via a webpack-only inline `css-loader` import string, not a normal module import — see [Failure modes](#6-failure-modes--sharp-edges) |
|
|
123
|
+
|
|
124
|
+
**Depended on by** (real, evidenced consumers in the `platforms/surveyserv` Prism system):
|
|
125
|
+
|
|
126
|
+
| Consumer repo | Declared version range | Consumer's own React version | Why it depends on `freemium-ui` |
|
|
127
|
+
| ---------------------------- | -------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
128
|
+
| `freshsurvey-web-admin-app` | `"freemium-ui": "^1.0.335"` (`package.json`) | `react ^17.0.2` | Pulls the full shared component set for its admin screens. Note the version range is a full major behind the currently published `2.0.133` — worth confirming with the owning team whether this is intentionally pinned or simply stale. |
|
|
129
|
+
| `survey-admin-microfrontend` | `"freemium-ui": "^2.0.133"` (`package.json`) | `react ^17.0.2` | Same shared component set, used across the survey builder/collector/list/preview micro-frontends. |
|
|
130
|
+
|
|
131
|
+
Prism entities: this component — [platforms/component/freemium-ui](https://prism.freshtools.ai/catalog/platforms/component/freemium-ui); dependents — `platforms/component/freshsurvey-web-admin-app` and `platforms/component/survey-admin-microfrontend` on [prism.freshtools.ai/catalog](https://prism.freshtools.ai/catalog).
|
|
132
|
+
|
|
133
|
+
**Hidden coupling**
|
|
134
|
+
|
|
135
|
+
- **Dew CSS-variable contract.** `DewTheme` (`src/themes/index.ts`) is not a self-contained theme object — most of its `colors`/`typography`/`shadow` values are literal `var(--color-*)`/`var(--base-font-family)` strings. It only renders correctly if `@freshworks/dew-styles`' CSS variables are already present in the DOM, which `appendDewStyles({ root, theme, mode })` is responsible for injecting. Any consumer that selects `Themes.DewTheme` without also calling `appendDewStyles` (or otherwise loading the Dew CSS variables) gets components styled with literal `var(...)` fallbacks-less strings.
|
|
136
|
+
- **Z-index scheme.** `Z_INDEX` (`src/themes/index.ts`) and the `!important` overrides in `src/styles/globalStyles.ts` assume Freemium UI owns a specific stacking order for tooltip/popover/modal/drawer/snackbar. A host app with its own competing global z-index scheme can conflict; `FreemiumUIGlobalStyles` is opt-in, not automatic.
|
|
137
|
+
- **React 17 vs. transitive React 18 typings.** `react`/`react-dom` are pinned `^17.0.2` as a `dependency` (not a `peerDependency`), while `@blueprintjs/*`'s own dependency tree pulls `@types/react@18.2.73`; `npm ls` reports it as `deduped invalid: "^16.14.41 || 17"` under `@blueprintjs/datetime2`. Both known dependents are still on React 17, so this hasn't broken anything observed, but React 18/19 compatibility for this library is unverified.
|
|
138
|
+
- **Node engine drift.** CI (`buildspec.yml`, `.github/workflows/automated-version-bump.yml`) targets Node 18; several devDependencies (`storybook@8.2.9`, `rollup@^4`, `husky@9.0.11`) declare `engines.node >=18` and print `EBADENGINE` on older local Node installs (observed on Node 16.20.2 in this environment) — install still succeeds, just noisy.
|
|
139
|
+
|
|
140
|
+
## 4. Idiomatic vernacular
|
|
141
|
+
|
|
142
|
+
- **Per-component file split.** `src/components/<Name>/<Name>.tsx` (implementation + one-line JSDoc description above the exported component + `export default`), `<Name>.types.ts` (props/derived types), `styles.ts` (styled-components primitives, theme-driven), `index.ts` (barrel: `export { default as X } from './X'; export type {...} from './X.types';`). Cleanest, smallest example to copy: `src/components/Tag/` or `src/components/Toggle/`. Components with extra fixed lookup tables also have a `constants.ts` (`Button`, `Modal`, `Drawer`, `Dropdown`, `Loader`, `Pickers`, `Toggle`).
|
|
143
|
+
- **Overlay/portal pattern.** `Modal`, `Drawer`, `Tooltip`, `Menu`, `Dropdown`, `ButtonDropdown`, `Confirmation`, and `Table/SortDropdown` pair `usePortalContainer(elementRef)` + `useShadowDomScrollFix(container, isOpen)` (`src/hooks/index.ts`) for shadow-DOM-aware rendering. `Popover` is also Radix-based but renders through Radix's own `Popover.Portal` instead — it does not use these shared hooks. Canonical minimal example (shared-hook path): `src/components/Modal/Modal.tsx`.
|
|
144
|
+
- **Generic-item-list pattern.** Components taking arbitrary row/option objects (`Dropdown<T>`, `MultiSelectDropdown<T>`, `Table<T>`, `WorkspaceTable<T>`, `Menu<T>`, `RadioGroup<T>`, `Tabs<T>`, `Accordion<T>`, `Reorder<T>`, `ButtonDropdown<T>`, `BoxedRadio<T>`) use string-key accessors (`idAccessor`, `nameAccessor`) instead of hard-coded field names, with a `Default*ItemProps = { [key: string]: any }` escape hatch as the type default.
|
|
145
|
+
- **Deprecation-without-deletion.** Dead props are marked `@deprecated` in a JSDoc comment and left in the type rather than removed: `TableProps.tableOptions`/`loaderStyles`; `PopoverProps.anchorOrigin`/`transformOrigin`/`clickOutToClose`/`autoCloseDuration`; `MenuProps`/`TableSortingMenuProps.highlightedIndex`. Follow this convention when deprecating rather than deleting outright (a hard removal is a breaking change for both dependents).
|
|
146
|
+
- **Domain glossary:**
|
|
147
|
+
- **Dew / Dew 2.0** — Freshworks' newer cross-product design system, published as `@freshworks/dew-styles` and consumed as CSS variables; `Themes.DewTheme` is this library's adapter theme for it.
|
|
148
|
+
- **Workspace** (`WorkspaceTable`) — the SurveyServ admin "workspace" list-view layout; it is a `Table` variant, not a distinct data model.
|
|
149
|
+
- **`fui:debug`** (`FREEMIUM_UI_DEBUG` constant, `src/utils/index.ts`) — the `localStorage` key consumers can set (`{"log": true}`) to enable `appLogger` debug console output.
|
|
150
|
+
|
|
151
|
+
## 5. State, data & invariants
|
|
152
|
+
|
|
153
|
+
- **Controlled by default.** The large majority of components are fully controlled: `isOpen`/`value`/`checked`/`on` + `onChange`/`onDismiss` callbacks, with no internal open/closed state — `Modal`, `Drawer`, `Confirmation`, `Toggle`, `Checkbox`, `Radio`/`RadioGroup`, and all of `Table`/`WorkspaceTable`'s pagination/sorting/column/selection state (`setPageIndex`, `setHiddenColumns`, `setColumnOrder`, `setSelectedRowIds` are all supplied by the caller, not owned internally).
|
|
154
|
+
- **Real internal state — the exception.** `ToastProvider` (`src/components/Toast/ToastProvider.tsx`) owns actual mutable state: a `toasts: ToastWithID[]` array and a `windowState: 'focus' | 'blur'` flag, held in `React.useState` and exposed via `ToastContext`. This is the one piece of library-wide "global" state. `Dropdown`/`MultiSelectDropdown`/`AutoComplete` also hold `downshift`-internal combobox open/highlighted-index state, and `Reorder` holds transient drag state via `@dnd-kit`.
|
|
155
|
+
- **Ownership.** `ToastContext` (`src/components/Toast/ToastContext.ts`) is created once per `ToastProvider` instance; `ThemeProvider` mounts exactly one `ToastProvider` per call, so every `ThemeProvider` subtree gets its own independent toast queue (nested `ThemeProvider`s get independent, non-deduplicated queues and `ToastContainer`s).
|
|
156
|
+
- **Invariants:**
|
|
157
|
+
- Exactly one `<ToastContainer />` renders per `ThemeProvider` instance; nesting `ThemeProvider`s multiplies containers.
|
|
158
|
+
- `useToasts()` called outside any `ThemeProvider`/`ToastProvider` does not throw — it silently receives the default context (`toasts: []`, no-op `addToast`/`updateToasts`), so toasts are dropped rather than erroring.
|
|
159
|
+
- `usePortalContainer` returns `document.body` unless the trigger element's `getRootNode()` is a `ShadowRoot`; if an overlay is rendered inside a shadow DOM without a discoverable shadow root at render time, it silently falls back to `document.body`, breaking style encapsulation for that instance.
|
|
160
|
+
- `getPropsAndTheme` (Storybook-only helper, `src/utils/index.ts`) assumes prop keys never collide with theme-color argType paths (e.g. `colors.text.link`) — any prop whose key matches a known theme-color path is silently rerouted into the theme object instead of component props. This only runs in Storybook decorators, not production render paths.
|
|
161
|
+
|
|
162
|
+
## 6. Failure modes & sharp edges
|
|
163
|
+
|
|
164
|
+
1. **`ToastProvider`'s window-blur listener cleanup is a no-op bug** (`src/components/Toast/ToastProvider.tsx`). The `useEffect` adds `focus`/`blur` listeners with inline arrow functions, then the cleanup calls `window.removeEventListener('focus', () => {})` with a **newly created** function — which never equals the one that was added, so the listeners are never actually removed. Don't copy this pattern; a real fix needs stable, stored function references. Only manifests when `retainOnWindowBlur` is passed as `true` (default is `false`).
|
|
165
|
+
2. **Almost the entire dependency tree is bundled, not externalized.** `package.json` declares no `peerDependencies` block at all — `react`/`react-dom` are the only packages listed in `rollup.config.ts`'s `external` array. Everything else (`styled-components`, all `@radix-ui/*`, `@blueprintjs/*`, `@dnd-kit/*`, `@tanstack/react-table`, `downshift`, `axios`, `moment`, `react-quill`) ships bundled inside `lib/index.cjs.js`/`lib/index.js`. Confirmed by an actual `npm run rollup` build in this environment. This is very likely why "build size optimization" is an active Confluence roadmap concern for this repo family (`Frontend Roadmap — Surveyserv Backlog 2026`). Adding a new heavy dependency to any single component inflates every consumer's bundle, not just that component's.
|
|
166
|
+
3. **Circular dependency warning from the bundled `@blueprintjs/core`.** Every `npm run rollup` build prints `(!) Circular dependency ... popover.js -> tooltip.js -> popover.js` from inside the bundled `@blueprintjs/core` package (reproduced in this environment). This is pre-existing inside `@blueprintjs/core` itself, not introduced by this repo — don't chase it as a regression when it shows up in build output.
|
|
167
|
+
4. **The Dew theme's CSS import can't be resolved by non-webpack bundlers.** `src/themes/index.ts` imports `@freshworks/dew-styles/colors.css`/`fonts.css` via the webpack-only inline loader syntax `!!css-loader?{...}!@freshworks/dew-styles/colors.css`. Rollup reports this as an "Unresolved dependency" (reproduced in this environment) and treats it as an external string import — Storybook's webpack5 builder resolves it, but a consumer app bundling with something other than webpack may fail to resolve the "dew" theme path (`Themes.DewTheme`/`appendDewStyles`) at build or runtime.
|
|
168
|
+
5. **`QuillEditor` and four Icons exist but are not public.** `QuillEditor` has a full `index.ts`/types/styles under `src/components/QuillEditor/` but is **not** re-exported from `src/components/index.ts` — `import { QuillEditor } from 'freemium-ui'` fails today (verified by grep). Same for the `ChevronDown`, `Search`, `Settings`, `Drag` icon files — present, used internally via relative imports (`Table.tsx`, `CustomizeColumns.tsx`, `WorkspaceTable.tsx`, `Reorder.tsx`), but absent from `Icons/index.ts`. Don't assume either is already part of the public surface; exporting them is itself a (minor, additive) public-API change that needs a story + a version bump.
|
|
169
|
+
6. **`src/components/index.ts` has a duplicate export line** (`export * from './Accordion';` appears twice). Harmless — duplicate named exports collapse — but a sign the barrel isn't tidy; don't infer meaning from its current ordering.
|
|
170
|
+
7. **React 17 pinned as a plain dependency, React 18 typings leak in transitively.** `npm ls` in this environment shows `@types/react@18.2.73 deduped invalid: "^16.14.41 || 17"` under `@blueprintjs/datetime2`'s own subtree. Both known dependents are on React 17 today; React 18/19 compatibility is `> [UNVERIFIED]` — no test or CI matrix exercises it.
|
|
171
|
+
8. **Node engine drift.** Several devDependencies (`storybook@8.2.9`, `rollup@^4`, `husky@9.0.11`, `@testing-library/dom`) declare `engines.node >=18` and emit `EBADENGINE` warnings on Node 16 (reproduced in this environment); CI is on Node 18 (`buildspec.yml`, `automated-version-bump.yml`), so this only bites local development on an older Node.
|
|
172
|
+
9. `> [UNVERIFIED — checked and not reproduced]` a `'use client'`-directive-stripping warning from `@radix-ui` packages during bundling was flagged as a thing to watch for, but the currently pinned `@radix-ui/*` versions in this repo (e.g. `react-popover@1.0.7`) predate that directive and contain no `'use client'` strings anywhere in `node_modules` — don't assume it applies unless these packages are bumped to a newer major.
|
|
173
|
+
10. **Don't touch `rollup.config.ts`'s `external` array in isolation.** It's the _only_ place peer-vs-bundled behavior is actually decided (see #2) — changing it without also deciding whether to add a real `peerDependencies` block in `package.json` will silently change what gets published inside `lib/`.
|
|
174
|
+
11. **Don't reshape `FreemiumUiTheme` (`src/themes/types.ts`) without a cross-repo sweep.** Every component's `styles.ts` reads deep paths off it (`theme.colors.*`, `theme.typography.*`, `theme.zIndex.*`, `theme.shadow.*`, `theme.radius.*`, `theme.opacity.*`) — a shape change is a breaking change felt by both `freshsurvey-web-admin-app` and `survey-admin-microfrontend`.
|
|
175
|
+
|
|
176
|
+
## 7. Change guidance
|
|
177
|
+
|
|
178
|
+
- **New component:** scaffold `src/components/<Name>/{<Name>.tsx, <Name>.types.ts, styles.ts, index.ts}` (copy `src/components/Tag/` or `src/components/Toggle/` as the template); add `export * from './<Name>';` to `src/components/index.ts`; add `src/stories/<Name>.stories.tsx`; if it's an overlay, wire `usePortalContainer`/`useShadowDomScrollFix` per `Modal.tsx`.
|
|
179
|
+
- **Prop/type change:** edit `<Name>.types.ts`; deprecate rather than delete removed props (`@deprecated` JSDoc, see §4); update the matching story; confirm `lib/index.d.ts` regenerates cleanly.
|
|
180
|
+
- **Theme value change:** edit `src/themes/palette.ts` and `src/themes/index.ts` (`light`/`dark`/`DewTheme`) and `src/themes/types.ts` together; visually check `src/stories/Colors.mdx`, `DewColors.mdx`, `DewPalette.mdx`, `DewTypography.mdx` and a few representative component stories in Storybook — theme edits are not type-checked against rendered output.
|
|
181
|
+
- **Commands:** `npm run lint` (ESLint `--fix --quiet`) · `npm run format` (Prettier `--write .`) · `npm run typescript` (`tsc -p tsconfig.json`, `noEmit`) · `npm test` (`react-scripts test` — 0 test files exist in the repo today) · `npm run storybook` (dev server, port 6006) · `npm run build` (`rollup` then `post:build`'s `scripts/frankBuild.js`, which trims `package.json` and copies `README.md` into `lib/`) · `npm run build-storybook` (static Storybook site — this is what `buildspec.yml`/CI actually builds and deploys, not the library itself).
|
|
182
|
+
- `.husky/pre-commit` runs `npm run lint && npm run format && npm install && npm run build` on every commit — slow but real; don't bypass with `--no-verify` without a strong reason.
|
|
183
|
+
|
|
184
|
+
## 8. Staleness triggers
|
|
185
|
+
|
|
186
|
+
- `src/index.ts`'s export list (adding/removing a top-level export invalidates the §2 contracts table).
|
|
187
|
+
- `src/components/index.ts`'s barrel (e.g. if `QuillEditor` or the unexported Icons ever get added — invalidates §2 and §6.5).
|
|
188
|
+
- `src/themes/types.ts#FreemiumUiTheme` shape (invalidates §1's control-flow description and §6.11).
|
|
189
|
+
- `rollup.config.ts`'s `external` array or `package.json`'s `dependencies`/`peerDependencies` split (invalidates §3's bundled-vs-external table and §6.2).
|
|
190
|
+
- `.github/workflows/automated-version-bump.yml`'s publish/tagging logic (invalidates the npm publish flow described in [Getting Started](../getting-started.md)).
|
|
191
|
+
- `src/components/Toast/ToastProvider.tsx`'s listener cleanup (invalidates §6.1 if fixed).
|
|
192
|
+
- `src/hooks/index.ts#usePortalContainer`/`useShadowDomScrollFix` signature (used by 8 components — invalidates §1 and §5's shadow-DOM invariant).
|
|
193
|
+
- `freshsurvey-web-admin-app`'s or `survey-admin-microfrontend`'s `package.json` `freemium-ui` version range (invalidates §3's "Depended on by" table).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- Node.js — CI (`buildspec.yml`, `.github/workflows/automated-version-bump.yml`) uses **Node 18**. Several devDependencies (`storybook@8.2.9`, `rollup@^4`, `husky@9.0.11`, `@testing-library/dom`) declare `engines.node >=18` and print `EBADENGINE` warnings (not hard failures) on older Node — use Node 18+ locally to avoid the noise.
|
|
6
|
+
- A package manager compatible with the committed `package-lock.json` (npm).
|
|
7
|
+
- No local database, external service credentials, or environment variables are required — this is a pure UI component library with no backend of its own.
|
|
8
|
+
|
|
9
|
+
## Required peer setup (consumer apps)
|
|
10
|
+
|
|
11
|
+
Per `README.md`, any app consuming the published `freemium-ui` package must install `styled-components` itself:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install styled-components
|
|
15
|
+
npm install freemium-ui
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> [UNVERIFIED] `package.json` does not actually declare a `peerDependencies` block, so nothing enforces this at install time. It also doesn't prevent a duplicate `styled-components` instance — see [Deep Dive → Dependencies & coupling](architecture/deep-dive.md#3-dependencies--coupling): only `react`/`react-dom` are externalized by Rollup, so `freemium-ui` ships its own bundled copy of `styled-components` in `lib/` regardless of what the consumer installs. The reason README asks consumers to install it separately isn't confirmed in this pass — ask the architect if it's for something other than deduplication (e.g. a consumer importing `styled-components` APIs directly alongside `freemium-ui`).
|
|
19
|
+
|
|
20
|
+
Consumers must also mount `ThemeProvider` once near their app root and pass one of `Themes.light`, `Themes.dark`, or `Themes.DewTheme`:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { Button, ThemeProvider, Themes } from 'freemium-ui';
|
|
24
|
+
|
|
25
|
+
function App() {
|
|
26
|
+
return (
|
|
27
|
+
<ThemeProvider theme={Themes.light}>
|
|
28
|
+
<Button variant="primary">Freemium Button</Button>
|
|
29
|
+
</ThemeProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Local setup
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git clone git@github.com:freshdesk/freemium-ui.git
|
|
38
|
+
cd freemium-ui
|
|
39
|
+
npm install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If the Rollup build runs out of memory (`JavaScript heap out of memory`), bump Node's heap per `README.md`:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
export NODE_OPTIONS="--max-old-space-size=8192"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Running Storybook (primary local dev loop)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run storybook # dev server on http://localhost:6006
|
|
52
|
+
npm run build-storybook # static build to storybook-static/ — this is what buildspec.yml/CI actually builds and publishes
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`.storybook/preview.tsx` wraps every story in `ThemeProvider` and exposes a theme/mode toolbar (`light`, `dark`, `dew`), including calling `appendDewStyles` when the `dew` theme is selected.
|
|
56
|
+
|
|
57
|
+
## Building the library
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run build
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This runs `npm run rollup` (`rm -rf lib && rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript`, producing `lib/index.cjs.js`, `lib/index.js`, `lib/index.d.ts`) followed by `npm run post:build` (`scripts/frankBuild.js`, which writes a trimmed `lib/package.json` — dropping `scripts`/`devDependencies`, setting `private: false` — and copies `README.md` into `lib/`).
|
|
64
|
+
|
|
65
|
+
Expect (harmless, verified) Rollup warnings on every build: an "Unresolved dependency" for the `@freshworks/dew-styles` CSS imports in `src/themes/index.ts`, and a "Circular dependency" inside the bundled `@blueprintjs/core` (`popover.js` ↔ `tooltip.js`). See [Deep Dive → Failure modes](architecture/deep-dive.md#6-failure-modes--sharp-edges).
|
|
66
|
+
|
|
67
|
+
## Linting, formatting, type-checking, tests
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm run lint # eslint . --fix --quiet
|
|
71
|
+
npm run format # prettier --write .
|
|
72
|
+
npm run check # prettier --check . (CI-safe, no writes)
|
|
73
|
+
npm run typescript # tsc -p tsconfig.json (noEmit)
|
|
74
|
+
npm test # react-scripts test
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`npm test` runs `react-scripts test`, but the repository currently has **no `*.test.*`/`*.spec.*` files** (verified: `find src -name "*.test.*" -o -name "*.spec.*"` returns nothing) — running it exercises 0 tests today. Unit test coverage for this repo is an open item (see [Recent issues](operations/recent-issues.md)).
|
|
78
|
+
|
|
79
|
+
`.husky/pre-commit` runs `npm run lint && npm run format && npm install && npm run build` on every commit — expect commits to be slow; don't skip it with `--no-verify` without a strong reason.
|
|
80
|
+
|
|
81
|
+
## Version bump & npm publish flow
|
|
82
|
+
|
|
83
|
+
This is a **library**, not a deployed service — "release" means publishing a new npm version, not deploying infrastructure.
|
|
84
|
+
|
|
85
|
+
**Stable releases (automated, GitHub Actions):** `.github/workflows/automated-version-bump.yml` — triggered manually (`workflow_dispatch`, choose `patch`/`minor`/`major`). It:
|
|
86
|
+
|
|
87
|
+
1. Checks out `main`, runs `npm version <type> --no-git-tag-version` on a new `version-bump-<timestamp>` branch, commits `package.json`/`package-lock.json`, and opens a PR titled `chore(release): bump version to vX.Y.Z` back into `main`.
|
|
88
|
+
2. On merge to `main`, the `publish-after-merge` job detects the version-bump commit pattern, runs `npm ci && npm run build`, then `npm publish` (to `https://registry.npmjs.org/`, using the `NPM_TOKEN` secret — `prepublishOnly` also runs `npm run build` as a safety net), creates and pushes a `vX.Y.Z` git tag, generates release notes from the commit log, and deletes the version-bump branch.
|
|
89
|
+
|
|
90
|
+
**Beta/prerelease (manual, from a local checkout):**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run version:beta # npm version prerelease --preid=beta
|
|
94
|
+
npm run publish:beta # npm publish --tag beta
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**One-off manual publish** (per `README.md`, requires a PAT with `admin:org write:packages read:packages delete:packages` and `npm login --registry=https://registry.npmjs.org/` first):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm publish
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Current published version at the time of writing: `2.0.133` (`package.json`). Known consumers: `freshsurvey-web-admin-app` (pinned `^1.0.335`) and `survey-admin-microfrontend` (pinned `^2.0.133`) — see [Deep Dive → Dependencies & coupling](architecture/deep-dive.md#3-dependencies--coupling).
|