atlas-react-kit 0.8.1 → 0.9.0
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/COMPONENTS.md +13 -1
- package/DESIGN_RULES.md +4 -3
- package/README.md +17 -4
- package/dist/internal/tabs-styles.d.ts +13 -1
- package/dist/internal/tabs-styles.js +21 -1
- package/dist/markdown.d.ts +2 -2
- package/dist/markdown.js +2 -2
- package/dist/tabs.d.ts +3 -2
- package/dist/tabs.js +9 -7
- package/package.json +31 -7
- package/skills/atlas-react-kit/SKILL.md +3 -1
package/COMPONENTS.md
CHANGED
|
@@ -5,6 +5,18 @@ underlying element or Radix props, including `className`, alongside their option
|
|
|
5
5
|
Form helpers and `TableBodySkeleton` accept only their documented props. There is
|
|
6
6
|
no root component barrel.
|
|
7
7
|
|
|
8
|
+
Core entries need only the kit. These feature entries need an extra install
|
|
9
|
+
(same version ranges as the kit's optional peers). `Toaster` also reads the
|
|
10
|
+
theme from `next-themes`, so toast consumers install that too.
|
|
11
|
+
|
|
12
|
+
| Entry points | Extra install |
|
|
13
|
+
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |
|
|
14
|
+
| `calendar`, `date-picker`, `date-time-picker`, `form/date-picker-field`, `form/date-time-picker-field` | `npm i react-day-picker@^10.0.1 date-fns@^4.4.0` |
|
|
15
|
+
| `markdown`, `form/markdown-field` | `npm i react-markdown@^10.1.0 remark-gfm@^4.0.1` |
|
|
16
|
+
| `theme-provider` | `npm i next-themes@^0.4.6` |
|
|
17
|
+
| `toast` | `npm i sonner@^2.0.7 next-themes@^0.4.6` |
|
|
18
|
+
| `next/tabs-nav-link` | `npm i next@^16.0.0` |
|
|
19
|
+
|
|
8
20
|
| Subpath | Main exports and options |
|
|
9
21
|
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
10
22
|
| `accordion` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`; Radix `type` `single` (with `collapsible`) or `multiple` |
|
|
@@ -43,7 +55,7 @@ no root component barrel.
|
|
|
43
55
|
| `switch` | `Switch`; settings that apply immediately; square track and thumb |
|
|
44
56
|
| `table` | Table, header, body, footer, row, head, cell, caption; root scroller is `min-w-0 overflow-x-auto` with a fade cue; cells are `px-2` below `sm` |
|
|
45
57
|
| `table-body-skeleton` | `TableBodySkeleton`; `columns`, optional `rows` (default 5) |
|
|
46
|
-
| `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `tabsListVariants`, `tabsTriggerVariants`; list `variant` is `default` or `line`; style `TabsNav` links with `tabsTriggerVariants()`; `TabsNav` / `TabsList` scroll horizontally only (`overflow-y` hidden, edge fade, active item into view) — do not wrap them in `overflow-x-auto`
|
|
58
|
+
| `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `tabsListVariants`, `tabsTriggerVariants`; list `variant` is `default` or `line`; style `TabsNav` links with `tabsTriggerVariants()`; `TabsNav` / `TabsList` scroll horizontally only (`overflow-y` hidden, `p-1` counted in the scroller height, edge fade, active item into view) — do not wrap them in `overflow-x-auto` |
|
|
47
59
|
| `toast` | `Toaster`, `toast`; mount one `Toaster` (`position`, `closeButton` (default on), `expand`, `visibleToasts`, `duration`), then `toast(message, options)`, `.success`, `.info`, `.warning`, `.error`, `.loading`, `.dismiss(id?)`. Options: `id` (replaces that toast), `description`, `duration`, `action` / `cancel` as `{ label, onClick }`. Kit-owned API: no toast library is exposed |
|
|
48
60
|
| `toggle` | `Toggle`, `toggleVariants`, `selectedToggleChrome`; `variant`, `size`. Pressed state is the selected wash, text, and border via `data-state=on`, `aria-pressed`, and `aria-checked` |
|
|
49
61
|
| `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size`. Items share `toggleVariants`, so single (radio) and multiple selections match a pressed `Toggle` |
|
package/DESIGN_RULES.md
CHANGED
|
@@ -105,9 +105,10 @@ Use `Tabs` with actual `TabsContent` panels. For URL view switches, use a labell
|
|
|
105
105
|
`TabsNav` with links and `aria-current="page"` on the active link. The optional
|
|
106
106
|
Next adapter provides `TabsNavLink` with this behavior. `TabsNav` and `TabsList`
|
|
107
107
|
scroll overflowing labels inside themselves (`overflow-x-auto overflow-y-hidden`,
|
|
108
|
-
with padding for the active border and focus ring
|
|
109
|
-
`Table`). Do not wrap them in another
|
|
110
|
-
`overflow-y: auto` and a stray
|
|
108
|
+
with padding for the active border and focus ring counted in the scroller
|
|
109
|
+
height, and an edge fade like `Table`). Do not wrap them in another
|
|
110
|
+
`overflow-x-auto` — that pairing forces `overflow-y: auto` and a stray
|
|
111
|
+
vertical scrollbar.
|
|
111
112
|
|
|
112
113
|
Use `FormTextField`, `FormSelectField`, `FormDatePickerField`,
|
|
113
114
|
`FormDateTimePickerField`, `FormMarkdownField`, and `FormFeedbackField` for
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ keeping locally (`InfoTip`, `ColorLegend`, `SearchPicker`, `Markdown`,
|
|
|
7
7
|
React 19 and Tailwind 4 are required.
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install --save-exact atlas-react-kit@0.
|
|
10
|
+
npm install --save-exact atlas-react-kit@0.9.0
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Import components by subpath:
|
|
@@ -27,6 +27,18 @@ export function Example() {
|
|
|
27
27
|
}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
Core entries need only the kit. These feature entries need an extra install
|
|
31
|
+
(same version ranges as the kit's optional peers). `Toaster` also reads the
|
|
32
|
+
theme from `next-themes`, so toast consumers install that too.
|
|
33
|
+
|
|
34
|
+
| Entry points | Extra install |
|
|
35
|
+
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |
|
|
36
|
+
| `calendar`, `date-picker`, `date-time-picker`, `form/date-picker-field`, `form/date-time-picker-field` | `npm i react-day-picker@^10.0.1 date-fns@^4.4.0` |
|
|
37
|
+
| `markdown`, `form/markdown-field` | `npm i react-markdown@^10.1.0 remark-gfm@^4.0.1` |
|
|
38
|
+
| `theme-provider` | `npm i next-themes@^0.4.6` |
|
|
39
|
+
| `toast` | `npm i sonner@^2.0.7 next-themes@^0.4.6` |
|
|
40
|
+
| `next/tabs-nav-link` | `npm i next@^16.0.0` |
|
|
41
|
+
|
|
30
42
|
In your global stylesheet, import the theme after Tailwind and register the
|
|
31
43
|
installed JavaScript directory. Paths in `@source` are relative to that stylesheet;
|
|
32
44
|
this example assumes it is one directory below the project root.
|
|
@@ -90,8 +102,9 @@ The public site is Storybook in `site/`. `npm run build --prefix site`
|
|
|
90
102
|
typechecks the stories, builds Storybook into `site/dist`, then checks the
|
|
91
103
|
static output. Vercel serves that directory; `/playground` and `/storybook`
|
|
92
104
|
redirect to `/`. The site and Storybook dependencies are not part of the npm
|
|
93
|
-
package.
|
|
94
|
-
|
|
105
|
+
package. Optional feature libraries (markdown, dates, toasts, theme, Next) are
|
|
106
|
+
peer dependencies and are required only when those entry points are imported.
|
|
107
|
+
The package does not bundle React.
|
|
95
108
|
|
|
96
109
|
Run `npm run dev --prefix site` (or `npm run storybook --prefix site`) for the
|
|
97
110
|
playground on port 6006. Existing examples live in `stories/` and are imported
|
|
@@ -116,7 +129,7 @@ artifact with the package owner's npm login:
|
|
|
116
129
|
```sh
|
|
117
130
|
mkdir -p release-artifacts
|
|
118
131
|
npm pack --pack-destination release-artifacts
|
|
119
|
-
npm publish ./release-artifacts/atlas-react-kit-0.
|
|
132
|
+
npm publish ./release-artifacts/atlas-react-kit-0.9.0.tgz --access public
|
|
120
133
|
```
|
|
121
134
|
|
|
122
135
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) for adding components, the browser
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
declare const tabsListVariants: (props?: ({
|
|
2
2
|
variant?: "default" | "line" | null | undefined;
|
|
3
3
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Horizontal scroller around a tab row. `p-1` leaves room for the active
|
|
6
|
+
* border and focus ring; the height is the `h-11` list plus that padding
|
|
7
|
+
* (`h-13` = 44px + 8px) so the list fits the content box. Putting padding
|
|
8
|
+
* on an overflow box without this height makes `scrollHeight` 4px taller
|
|
9
|
+
* than `clientHeight` (48 vs 44) and a vertical scrollbar appears if
|
|
10
|
+
* `overflow-y` is not hidden. Line variant adds `pb-2` for the underline
|
|
11
|
+
* (`h-14` = 44px + 4px + 8px).
|
|
12
|
+
*/
|
|
13
|
+
declare const tabsScrollerVariants: (props?: ({
|
|
14
|
+
variant?: "default" | "line" | null | undefined;
|
|
15
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
4
16
|
declare const tabsTriggerVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
5
|
-
export { tabsListVariants, tabsTriggerVariants };
|
|
17
|
+
export { tabsListVariants, tabsScrollerVariants, tabsTriggerVariants };
|
|
@@ -11,6 +11,26 @@ const tabsListVariants = cva('group/tabs-list inline-flex w-max min-w-full items
|
|
|
11
11
|
variant: "default",
|
|
12
12
|
},
|
|
13
13
|
});
|
|
14
|
+
/**
|
|
15
|
+
* Horizontal scroller around a tab row. `p-1` leaves room for the active
|
|
16
|
+
* border and focus ring; the height is the `h-11` list plus that padding
|
|
17
|
+
* (`h-13` = 44px + 8px) so the list fits the content box. Putting padding
|
|
18
|
+
* on an overflow box without this height makes `scrollHeight` 4px taller
|
|
19
|
+
* than `clientHeight` (48 vs 44) and a vertical scrollbar appears if
|
|
20
|
+
* `overflow-y` is not hidden. Line variant adds `pb-2` for the underline
|
|
21
|
+
* (`h-14` = 44px + 4px + 8px).
|
|
22
|
+
*/
|
|
23
|
+
const tabsScrollerVariants = cva("relative box-border w-full min-w-0 max-w-full overflow-x-auto overflow-y-hidden overscroll-x-contain p-1 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", {
|
|
24
|
+
variants: {
|
|
25
|
+
variant: {
|
|
26
|
+
default: 'group-data-[orientation="horizontal"]/tabs:h-13',
|
|
27
|
+
line: 'group-data-[orientation="horizontal"]/tabs:h-14 pb-2',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: "default",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
14
34
|
const tabsTriggerVariants = cva([
|
|
15
35
|
focusRing,
|
|
16
36
|
disabledState,
|
|
@@ -22,4 +42,4 @@ const tabsTriggerVariants = cva([
|
|
|
22
42
|
selectedState.active,
|
|
23
43
|
'after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation="horizontal"]/tabs:after:inset-x-0 group-data-[orientation="horizontal"]/tabs:after:bottom-[-5px] group-data-[orientation="horizontal"]/tabs:after:h-0.5 group-data-[orientation="vertical"]/tabs:after:inset-y-0 group-data-[orientation="vertical"]/tabs:after:-right-1 group-data-[orientation="vertical"]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state="active"]:after:opacity-100',
|
|
24
44
|
]);
|
|
25
|
-
export { tabsListVariants, tabsTriggerVariants };
|
|
45
|
+
export { tabsListVariants, tabsScrollerVariants, tabsTriggerVariants };
|
package/dist/markdown.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export type MarkdownProps = {
|
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
11
|
* GitHub-flavoured Markdown, token-styled and sanitized. No Next, no raw
|
|
12
|
-
* HTML, no typography plugin. `react-markdown` and `remark-gfm`
|
|
13
|
-
*
|
|
12
|
+
* HTML, no typography plugin. `react-markdown` and `remark-gfm` are optional
|
|
13
|
+
* peers imported only here so other subpaths do not pay for them.
|
|
14
14
|
*/
|
|
15
15
|
declare function Markdown({ content, className }: MarkdownProps): import("react").JSX.Element;
|
|
16
16
|
/** Alias used by apps that imported MarkdownContent. */
|
package/dist/markdown.js
CHANGED
|
@@ -51,8 +51,8 @@ const markdownComponents = {
|
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
53
|
* GitHub-flavoured Markdown, token-styled and sanitized. No Next, no raw
|
|
54
|
-
* HTML, no typography plugin. `react-markdown` and `remark-gfm`
|
|
55
|
-
*
|
|
54
|
+
* HTML, no typography plugin. `react-markdown` and `remark-gfm` are optional
|
|
55
|
+
* peers imported only here so other subpaths do not pay for them.
|
|
56
56
|
*/
|
|
57
57
|
function Markdown({ content, className }) {
|
|
58
58
|
return (_jsx("article", { "data-slot": "markdown", className: cn("flex flex-col gap-3 text-foreground", className), children: _jsx(ReactMarkdown, { remarkPlugins: REMARK_PLUGINS, urlTransform: safeMarkdownUrl, components: markdownComponents, children: content }) }));
|
package/dist/tabs.d.ts
CHANGED
|
@@ -15,8 +15,9 @@ declare function TabsContent({ className, ...props }: React.ComponentProps<typeo
|
|
|
15
15
|
* `TabsNavLink` does this for you).
|
|
16
16
|
*
|
|
17
17
|
* Overflow is handled here: the row scrolls horizontally only, with padding
|
|
18
|
-
* for active borders and focus rings
|
|
19
|
-
*
|
|
18
|
+
* for active borders and focus rings counted in the scroller height (so the
|
|
19
|
+
* list cannot overflow on y), an edge fade while it overflows, and the
|
|
20
|
+
* active link scrolled into view on mount. Do not wrap it in
|
|
20
21
|
* `overflow-x-auto`.
|
|
21
22
|
*/
|
|
22
23
|
declare function TabsNav({ className, children, ...props }: React.ComponentProps<"nav"> & {
|
package/dist/tabs.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import {} from "class-variance-authority";
|
|
5
5
|
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
6
6
|
import { focusRing } from "./internal/styles.js";
|
|
7
|
-
import { tabsListVariants, tabsTriggerVariants, } from "./internal/tabs-styles.js";
|
|
7
|
+
import { tabsListVariants, tabsScrollerVariants, tabsTriggerVariants, } from "./internal/tabs-styles.js";
|
|
8
8
|
import { useHorizontalOverflow } from "./internal/use-horizontal-overflow.js";
|
|
9
9
|
import { cn } from "./utils.js";
|
|
10
10
|
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
@@ -13,9 +13,10 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
|
13
13
|
/**
|
|
14
14
|
* Horizontal scroller around a tab row. `overflow-x: auto` alone would
|
|
15
15
|
* compute `overflow-y` to `auto` as well (CSS overflow pairing), so the
|
|
16
|
-
* active tab's border and focus ring grow a vertical scrollbar.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* active tab's border and focus ring grow a vertical scrollbar. Pad for
|
|
17
|
+
* those strokes *inside* the box (`h-13` = `h-11` list + `p-1`), clip the
|
|
18
|
+
* y axis as a backstop, and fade the edges while the row overflows —
|
|
19
|
+
* same cue as `Table`.
|
|
19
20
|
*/
|
|
20
21
|
function TabsScroller({ variant = "default", children, }) {
|
|
21
22
|
const ref = React.useRef(null);
|
|
@@ -31,7 +32,7 @@ function TabsScroller({ variant = "default", children, }) {
|
|
|
31
32
|
behavior: "auto",
|
|
32
33
|
});
|
|
33
34
|
}, []);
|
|
34
|
-
return (_jsx("div", { ref: ref, "data-slot": "tabs-scroller", "data-variant": variant, tabIndex: overflowing ? 0 : undefined, className: cn(focusRing,
|
|
35
|
+
return (_jsx("div", { ref: ref, "data-slot": "tabs-scroller", "data-variant": variant, tabIndex: overflowing ? 0 : undefined, className: cn(focusRing, tabsScrollerVariants({ variant })), children: children }));
|
|
35
36
|
}
|
|
36
37
|
function TabsList({ className, variant = "default", ...props }) {
|
|
37
38
|
return (_jsx(TabsScroller, { variant: variant ?? "default", children: _jsx(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant }),
|
|
@@ -53,8 +54,9 @@ function TabsContent({ className, ...props }) {
|
|
|
53
54
|
* `TabsNavLink` does this for you).
|
|
54
55
|
*
|
|
55
56
|
* Overflow is handled here: the row scrolls horizontally only, with padding
|
|
56
|
-
* for active borders and focus rings
|
|
57
|
-
*
|
|
57
|
+
* for active borders and focus rings counted in the scroller height (so the
|
|
58
|
+
* list cannot overflow on y), an edge fade while it overflows, and the
|
|
59
|
+
* active link scrolled into view on mount. Do not wrap it in
|
|
58
60
|
* `overflow-x-auto`.
|
|
59
61
|
*/
|
|
60
62
|
function TabsNav({ className, children, ...props }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atlas-react-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Shared React components and Tailwind theme for Atlas",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -255,27 +255,45 @@
|
|
|
255
255
|
"access": "public"
|
|
256
256
|
},
|
|
257
257
|
"peerDependencies": {
|
|
258
|
+
"date-fns": "^4.4.0",
|
|
258
259
|
"next": "^16.0.0",
|
|
260
|
+
"next-themes": "^0.4.6",
|
|
259
261
|
"react": "^19.0.0",
|
|
262
|
+
"react-day-picker": "^10.0.1",
|
|
260
263
|
"react-dom": "^19.0.0",
|
|
264
|
+
"react-markdown": "^10.1.0",
|
|
265
|
+
"remark-gfm": "^4.0.1",
|
|
266
|
+
"sonner": "^2.0.7",
|
|
261
267
|
"tailwindcss": "^4.0.0"
|
|
262
268
|
},
|
|
263
269
|
"peerDependenciesMeta": {
|
|
270
|
+
"date-fns": {
|
|
271
|
+
"optional": true
|
|
272
|
+
},
|
|
264
273
|
"next": {
|
|
265
274
|
"optional": true
|
|
275
|
+
},
|
|
276
|
+
"next-themes": {
|
|
277
|
+
"optional": true
|
|
278
|
+
},
|
|
279
|
+
"react-day-picker": {
|
|
280
|
+
"optional": true
|
|
281
|
+
},
|
|
282
|
+
"react-markdown": {
|
|
283
|
+
"optional": true
|
|
284
|
+
},
|
|
285
|
+
"remark-gfm": {
|
|
286
|
+
"optional": true
|
|
287
|
+
},
|
|
288
|
+
"sonner": {
|
|
289
|
+
"optional": true
|
|
266
290
|
}
|
|
267
291
|
},
|
|
268
292
|
"dependencies": {
|
|
269
293
|
"class-variance-authority": "^0.7.1",
|
|
270
294
|
"clsx": "^2.1.1",
|
|
271
|
-
"date-fns": "^4.4.0",
|
|
272
295
|
"lucide-react": "^1.16.0",
|
|
273
|
-
"next-themes": "^0.4.6",
|
|
274
296
|
"radix-ui": "^1.4.3",
|
|
275
|
-
"react-day-picker": "^10.0.1",
|
|
276
|
-
"react-markdown": "^10.1.0",
|
|
277
|
-
"remark-gfm": "^4.0.1",
|
|
278
|
-
"sonner": "^2.0.7",
|
|
279
297
|
"tailwind-merge": "^3.6.0",
|
|
280
298
|
"tw-animate-css": "^1.4.0"
|
|
281
299
|
},
|
|
@@ -288,11 +306,17 @@
|
|
|
288
306
|
"@types/react": "^19.2.14",
|
|
289
307
|
"@types/react-dom": "^19.2.3",
|
|
290
308
|
"@vitest/coverage-v8": "4.1.11",
|
|
309
|
+
"date-fns": "^4.4.0",
|
|
291
310
|
"jsdom": "^29.1.1",
|
|
292
311
|
"next": "16.2.6",
|
|
312
|
+
"next-themes": "^0.4.6",
|
|
293
313
|
"prettier": "^3.8.1",
|
|
294
314
|
"react": "^19.2.4",
|
|
315
|
+
"react-day-picker": "^10.0.1",
|
|
295
316
|
"react-dom": "^19.2.4",
|
|
317
|
+
"react-markdown": "^10.1.0",
|
|
318
|
+
"remark-gfm": "^4.0.1",
|
|
319
|
+
"sonner": "^2.0.7",
|
|
296
320
|
"tailwindcss": "^4.2.1",
|
|
297
321
|
"typescript": "^6.0.3",
|
|
298
322
|
"vitest": "^4.1.9"
|
|
@@ -42,7 +42,9 @@ root (or a `dark` class on an ancestor). Toasts need one `<Toaster />` from
|
|
|
42
42
|
`atlas-react-kit/toast`; show them with `toast()` from the same module, using
|
|
43
43
|
only the options it declares. Never import a toast library such as `sonner`
|
|
44
44
|
directly: the kit owns the toast API, and a second copy never reaches its
|
|
45
|
-
toaster.
|
|
45
|
+
toaster. Markdown, dates, toasts, and `ThemeProvider` are optional peers —
|
|
46
|
+
install the extras listed in `COMPONENTS.md` when you import those entry
|
|
47
|
+
points. Fix missing setup rather than working
|
|
46
48
|
around unstyled components.
|
|
47
49
|
|
|
48
50
|
## 3. Pick the kit component for the job
|