@ceed/cds 1.33.0 → 1.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SearchBar/SearchBar.d.ts +21 -0
- package/dist/components/SearchBar/index.d.ts +3 -0
- package/dist/components/ThemeProvider/ThemeProvider.d.ts +5 -0
- package/dist/components/data-display/Markdown.md +62 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/inputs/SearchBar.md +180 -0
- package/dist/components/inputs/llms.txt +1 -0
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +4 -4
- package/dist/index.cjs +88 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +137 -49
- package/dist/llms.txt +1 -0
- package/framer/index.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type BoxProps } from '@mui/joy/Box';
|
|
3
|
+
export type SearchBarSlot = 'root';
|
|
4
|
+
export interface SearchBarOption {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchBarProps extends Omit<BoxProps, 'onChange'> {
|
|
10
|
+
showSelect?: boolean;
|
|
11
|
+
options?: SearchBarOption[];
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
value: string;
|
|
14
|
+
onChange: (value: string) => void;
|
|
15
|
+
onSearch?: (params: {
|
|
16
|
+
selectValue?: string;
|
|
17
|
+
inputValue: string;
|
|
18
|
+
}) => void;
|
|
19
|
+
}
|
|
20
|
+
export type SearchBarOwnerState = Required<Pick<SearchBarProps, 'showSelect'>>;
|
|
21
|
+
export declare const SearchBar: React.ForwardRefExoticComponent<Omit<SearchBarProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -7,6 +7,7 @@ import { DateRangePickerProps } from '../DateRangePicker';
|
|
|
7
7
|
import { MonthPickerProps } from '../MonthPicker';
|
|
8
8
|
import { MonthRangePickerProps } from '../MonthRangePicker';
|
|
9
9
|
import { PercentageInputProps } from '../PercentageInput';
|
|
10
|
+
import type { SearchBarOwnerState, SearchBarProps, SearchBarSlot } from '../SearchBar/SearchBar';
|
|
10
11
|
declare module '@mui/joy/styles' {
|
|
11
12
|
interface TypographySystemOverrides {
|
|
12
13
|
'marketing-lg': true;
|
|
@@ -42,6 +43,10 @@ declare module '@mui/joy/styles' {
|
|
|
42
43
|
defaultProps?: Partial<PercentageInputProps>;
|
|
43
44
|
styleOverrides?: StyleOverrides<'root', {}, Theme>;
|
|
44
45
|
};
|
|
46
|
+
SearchBar?: {
|
|
47
|
+
defaultProps?: Partial<SearchBarProps>;
|
|
48
|
+
styleOverrides?: StyleOverrides<SearchBarSlot, SearchBarOwnerState, Theme>;
|
|
49
|
+
};
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
declare module '@mui/joy/Avatar' {
|
|
@@ -207,6 +207,68 @@ Full demonstration of all GFM features.
|
|
|
207
207
|
/>
|
|
208
208
|
````
|
|
209
209
|
|
|
210
|
+
### External Link Target
|
|
211
|
+
|
|
212
|
+
Override the built-in anchor renderer via `markdownOptions.components.a` to detect external links by origin comparison, open them in a new tab, and prepend an `OpenInNew` icon so users can tell external links apart at a glance. Internal links keep `defaultLinkAction` behavior and render without the icon.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<Markdown {...args} markdownOptions={{
|
|
216
|
+
components: {
|
|
217
|
+
a: ({
|
|
218
|
+
href,
|
|
219
|
+
children
|
|
220
|
+
}) => {
|
|
221
|
+
const isExternal = (() => {
|
|
222
|
+
if (!href || typeof window === 'undefined') return false;
|
|
223
|
+
try {
|
|
224
|
+
return new URL(href, window.location.href).origin !== window.location.origin;
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
})();
|
|
229
|
+
return <Link href={href} target={isExternal ? '_blank' : '_self'} rel={isExternal ? 'noopener noreferrer' : undefined} startDecorator={isExternal ? <OpenInNew sx={{
|
|
230
|
+
fontSize: '1em'
|
|
231
|
+
}} /> : undefined}>
|
|
232
|
+
{children}
|
|
233
|
+
</Link>;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}} />
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import OpenInNew from '@mui/icons-material/OpenInNew';
|
|
241
|
+
import { Markdown, Link } from '@ceed/cds';
|
|
242
|
+
|
|
243
|
+
<Markdown
|
|
244
|
+
content={content}
|
|
245
|
+
markdownOptions={{
|
|
246
|
+
components: {
|
|
247
|
+
a: ({ href, children }) => {
|
|
248
|
+
const isExternal = (() => {
|
|
249
|
+
if (!href || typeof window === 'undefined') return false;
|
|
250
|
+
try {
|
|
251
|
+
return new URL(href, window.location.href).origin !== window.location.origin;
|
|
252
|
+
} catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
return (
|
|
257
|
+
<Link
|
|
258
|
+
href={href}
|
|
259
|
+
target={isExternal ? '_blank' : '_self'}
|
|
260
|
+
rel={isExternal ? 'noopener noreferrer' : undefined}
|
|
261
|
+
startDecorator={isExternal ? <OpenInNew sx={{ fontSize: '1em' }} /> : undefined}
|
|
262
|
+
>
|
|
263
|
+
{children}
|
|
264
|
+
</Link>
|
|
265
|
+
);
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
}}
|
|
269
|
+
/>
|
|
270
|
+
```
|
|
271
|
+
|
|
210
272
|
## When to Use
|
|
211
273
|
|
|
212
274
|
### ✅ Good Use Cases
|
|
@@ -45,6 +45,7 @@ export { PercentageInput } from './PercentageInput';
|
|
|
45
45
|
export { Radio, RadioGroup } from './Radio';
|
|
46
46
|
export { RadioTileGroup } from './RadioTileGroup';
|
|
47
47
|
export { RadioList } from './RadioList';
|
|
48
|
+
export { SearchBar, type SearchBarProps, type SearchBarOwnerState, type SearchBarSlot, type SearchBarOption, } from './SearchBar';
|
|
48
49
|
export { Select, Option } from './Select';
|
|
49
50
|
export { Sheet } from './Sheet';
|
|
50
51
|
export { Stack } from './Stack';
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# SearchBar
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
A search input component combining a text field and a search button. Optionally includes a category Select that lets users narrow results by keyword type. Hovering over the input while it has a value reveals a clear (✕) button to reset the field. Sizes to its content by default (`inline-flex`) and accepts all `Box` props for layout control.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<SearchBar value={value} onChange={setValue} />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Field | Description | Default |
|
|
12
|
+
| ----------- | ----------- | ------- |
|
|
13
|
+
| showSelect | — | — |
|
|
14
|
+
| options | — | — |
|
|
15
|
+
| placeholder | — | — |
|
|
16
|
+
| value | — | — |
|
|
17
|
+
| onChange | — | — |
|
|
18
|
+
| onSearch | — | — |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { SearchBar } from '@ceed/cds';
|
|
24
|
+
|
|
25
|
+
// Basic
|
|
26
|
+
<SearchBar value={query} onChange={setQuery} onSearch={({ inputValue }) => fetch(inputValue)} />
|
|
27
|
+
|
|
28
|
+
// With category Select
|
|
29
|
+
<SearchBar
|
|
30
|
+
showSelect
|
|
31
|
+
options={[
|
|
32
|
+
{ label: 'Account #', value: 'account', placeholder: 'e.g. 1234567' },
|
|
33
|
+
{ label: 'Jira Issue #', value: 'jira', placeholder: 'e.g. PROC-1234' },
|
|
34
|
+
]}
|
|
35
|
+
value={query}
|
|
36
|
+
onChange={setQuery}
|
|
37
|
+
onSearch={({ selectValue, inputValue }) => fetch(selectValue, inputValue)}
|
|
38
|
+
/>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## With Select
|
|
42
|
+
|
|
43
|
+
Use `showSelect` together with `options` to display a category Select to the left of the text input. The selected category is passed as `selectValue` in the `onSearch` callback.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<SearchBar showSelect options={SAMPLE_OPTIONS} value={value} onChange={setValue} />
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Clearable Input
|
|
50
|
+
|
|
51
|
+
When the input has a value, hovering over the component reveals a clear (✕) button at the right of the text field. Clicking it calls `onChange('')` without triggering `onSearch`.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<Stack alignItems="flex-start" gap={2}>
|
|
55
|
+
<Typography level="body-sm">Hover over the input to reveal the clear (✕) button.</Typography>
|
|
56
|
+
<SearchBar value={value} onChange={setValue} />
|
|
57
|
+
</Stack>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Placeholder
|
|
61
|
+
|
|
62
|
+
Each `SearchBarOption` can include a `placeholder` field that is shown in the text input while that category is selected. Pass a `placeholder` prop directly to override the option-level placeholder for all categories.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<Stack alignItems="flex-start" gap={3}>
|
|
66
|
+
<Stack alignItems="flex-start" gap={1}>
|
|
67
|
+
<Typography level="body-xs" fontWeight="md">
|
|
68
|
+
No placeholder prop — uses the active option's placeholder
|
|
69
|
+
</Typography>
|
|
70
|
+
<SearchBar showSelect options={SAMPLE_OPTIONS} value={value} onChange={setValue} />
|
|
71
|
+
</Stack>
|
|
72
|
+
<Stack alignItems="flex-start" gap={1}>
|
|
73
|
+
<Typography level="body-xs" fontWeight="md">
|
|
74
|
+
placeholder="Search by keyword" — overrides option-level placeholder
|
|
75
|
+
</Typography>
|
|
76
|
+
<SearchBar showSelect options={SAMPLE_OPTIONS} placeholder="Search by keyword" value={value} onChange={setValue} />
|
|
77
|
+
</Stack>
|
|
78
|
+
</Stack>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## onSearch
|
|
82
|
+
|
|
83
|
+
`onSearch` fires when the search button is clicked or the Enter key is pressed. It receives `inputValue` (always present) and `selectValue` (only present when `showSelect` is `true`).
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Stack alignItems="flex-start" gap={2}>
|
|
87
|
+
<SearchBar showSelect options={SAMPLE_OPTIONS} value={value} onChange={setValue} onSearch={setLastSearch} />
|
|
88
|
+
<Typography level="body-sm">value: "{value}"</Typography>
|
|
89
|
+
{lastSearch && <Typography level="body-sm">
|
|
90
|
+
onSearch: [{lastSearch.selectValue}] "{lastSearch.inputValue}"
|
|
91
|
+
</Typography>}
|
|
92
|
+
</Stack>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
When `showSelect` is `false`, `selectValue` is omitted from the payload entirely — not `undefined` as a key, but absent.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<Stack alignItems="flex-start" gap={2}>
|
|
99
|
+
<Typography level="body-sm">
|
|
100
|
+
When <code>showSelect</code> is <code>false</code>, <code>selectValue</code> is omitted from the{' '}
|
|
101
|
+
<code>onSearch</code> payload.
|
|
102
|
+
</Typography>
|
|
103
|
+
<SearchBar value={value} onChange={setValue} onSearch={setLastSearch} />
|
|
104
|
+
{lastSearch && <Stack alignItems="flex-start" gap={0.5}>
|
|
105
|
+
<Typography level="body-sm">inputValue: "{lastSearch.inputValue}"</Typography>
|
|
106
|
+
<Typography level="body-sm" sx={{
|
|
107
|
+
color: lastSearch.selectValue === undefined ? 'success.500' : 'danger.500'
|
|
108
|
+
}}>
|
|
109
|
+
selectValue: {lastSearch.selectValue === undefined ? 'undefined ✅' : `"${lastSearch.selectValue}" ❌`}
|
|
110
|
+
</Typography>
|
|
111
|
+
</Stack>}
|
|
112
|
+
</Stack>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Width
|
|
116
|
+
|
|
117
|
+
SearchBar sizes to its content by default. Pass any `Box` width prop to constrain or stretch it.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<Stack alignItems="flex-start" gap={3}>
|
|
121
|
+
<Stack alignItems="flex-start" gap={1}>
|
|
122
|
+
<Typography level="body-xs" fontWeight="md">
|
|
123
|
+
width="100%" — stretches to parent width
|
|
124
|
+
</Typography>
|
|
125
|
+
<Box sx={{
|
|
126
|
+
border: '1px dashed',
|
|
127
|
+
borderColor: 'neutral.300',
|
|
128
|
+
padding: 1
|
|
129
|
+
}}>
|
|
130
|
+
<SearchBar width="100%" value={value} onChange={setValue} />
|
|
131
|
+
</Box>
|
|
132
|
+
</Stack>
|
|
133
|
+
<Stack alignItems="flex-start" gap={1}>
|
|
134
|
+
<Typography level="body-xs" fontWeight="md">
|
|
135
|
+
width={400} — fixed 400px
|
|
136
|
+
</Typography>
|
|
137
|
+
<SearchBar width={400} value={value} onChange={setValue} />
|
|
138
|
+
</Stack>
|
|
139
|
+
<Stack alignItems="flex-start" gap={1}>
|
|
140
|
+
<Typography level="body-xs" fontWeight="md">
|
|
141
|
+
No width prop — sizes to content (inline-flex default)
|
|
142
|
+
</Typography>
|
|
143
|
+
<SearchBar value={value} onChange={setValue} />
|
|
144
|
+
</Stack>
|
|
145
|
+
</Stack>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Props and Customization
|
|
149
|
+
|
|
150
|
+
| Prop | Type | Default | Description |
|
|
151
|
+
| ------------- | ---------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------- |
|
|
152
|
+
| `value` | `string` | — | Current value of the text input. Required. |
|
|
153
|
+
| `onChange` | `(value: string) => void` | — | Called when the text input value changes. Required. |
|
|
154
|
+
| `onSearch` | `(params: { selectValue?: string; inputValue: string }) => void` | — | Called on search button click or Enter key. |
|
|
155
|
+
| `showSelect` | `boolean` | `false` | Show the category Select alongside the input. |
|
|
156
|
+
| `options` | `SearchBarOption[]` | — | Category options for the Select. Required when `showSelect` is `true`. |
|
|
157
|
+
| `placeholder` | `string` | — | Placeholder text for the input. Takes priority over the option-level `placeholder`. |
|
|
158
|
+
|
|
159
|
+
> **Note**: Also accepts all `Box` props (`width`, `sx`, `className`, `style`, etc.).
|
|
160
|
+
|
|
161
|
+
### SearchBarOption
|
|
162
|
+
|
|
163
|
+
| Field | Type | Required | Description |
|
|
164
|
+
| ------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| `label` | `string` | ✓ | Display text shown in the Select dropdown. |
|
|
166
|
+
| `value` | `string` | ✓ | Identifier passed as `selectValue` in the `onSearch` callback. |
|
|
167
|
+
| `placeholder` | `string` | — | Hint text shown in the input while this category is selected (e.g. `"e.g. PROC-1234"`). Overridden by the top-level `placeholder` prop. |
|
|
168
|
+
|
|
169
|
+
## Best Practices
|
|
170
|
+
|
|
171
|
+
- Always provide `options` when `showSelect` is `true` — the Select renders nothing if `options` is omitted.
|
|
172
|
+
- Add a `placeholder` to each option to show the expected input format for that category.
|
|
173
|
+
- Use the top-level `placeholder` prop only when a single hint applies regardless of category.
|
|
174
|
+
- Prefer handling `onSearch` over `onChange` for server-side queries — fire the request once on explicit submission rather than on every keystroke.
|
|
175
|
+
|
|
176
|
+
## Accessibility
|
|
177
|
+
|
|
178
|
+
- The search button has `aria-label="Search"` and the clear button has `aria-label="Clear"`.
|
|
179
|
+
- The text input is a native `<input>` element; pressing Enter triggers `onSearch`.
|
|
180
|
+
- The clear button uses `onMouseDown` with `e.preventDefault()` to prevent the input from losing focus when clicked.
|