@neoptocom/neopto-ui 1.4.4 → 1.5.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/dist/index.cjs CHANGED
@@ -265,28 +265,81 @@ function Card({
265
265
  );
266
266
  }
267
267
  var Input = React3__namespace.forwardRef(
268
- ({ className, disabled, variant = "default", ...props }, ref) => {
269
- const isInline = variant === "inline";
270
- return /* @__PURE__ */ jsxRuntime.jsx(
271
- "input",
268
+ ({
269
+ className,
270
+ disabled,
271
+ variant = "default",
272
+ label,
273
+ fieldsetProps,
274
+ legendProps,
275
+ error = false,
276
+ ...props
277
+ }, ref) => {
278
+ const isInlineVariant = variant === "inline";
279
+ const shouldUseInlineStyles = isInlineVariant || Boolean(label);
280
+ const isError = error && !disabled;
281
+ const inputClasses = [
282
+ "w-full bg-transparent outline-none transition-colors",
283
+ shouldUseInlineStyles ? "h-9" : "h-12 px-4 rounded-full",
284
+ "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]"
285
+ ];
286
+ if (!shouldUseInlineStyles) {
287
+ inputClasses.push("border");
288
+ }
289
+ if (disabled) {
290
+ inputClasses.push("text-[#3F424F]", "cursor-not-allowed");
291
+ if (!shouldUseInlineStyles) {
292
+ inputClasses.push("border-[#3F424F]");
293
+ }
294
+ } else {
295
+ inputClasses.push("text-[var(--muted-fg)]", "focus:text-[var(--fg)]");
296
+ if (!shouldUseInlineStyles) {
297
+ inputClasses.push(
298
+ isError ? "border-[var(--destructive)]" : "border-[var(--muted-fg)]",
299
+ isError ? "hover:border-[var(--destructive)]" : "hover:border-[var(--border)]",
300
+ isError ? "focus:border-[var(--destructive)]" : "focus:border-[var(--color-brand)]"
301
+ );
302
+ }
303
+ }
304
+ if (className) {
305
+ inputClasses.push(className);
306
+ }
307
+ const inputClassName = inputClasses.join(" ");
308
+ const inputElement = /* @__PURE__ */ jsxRuntime.jsx("input", { ref, disabled, className: inputClassName, ...props });
309
+ if (!label) {
310
+ return inputElement;
311
+ }
312
+ const { className: fieldsetClassNameProp = "", ...restFieldsetProps } = fieldsetProps ?? {};
313
+ const { className: legendClassNameProp = "", ...restLegendProps } = legendProps ?? {};
314
+ const fieldsetClassName = [
315
+ "w-full min-w-0 rounded-full border bg-[var(--surface)] transition-colors h-14",
316
+ isError ? "border-[var(--destructive)]" : "border-[var(--border)]",
317
+ isError ? "focus-within:border-[var(--destructive)]" : "focus-within:border-[var(--color-brand)]",
318
+ disabled ? "opacity-60 cursor-not-allowed" : "",
319
+ fieldsetClassNameProp
320
+ ].filter(Boolean).join(" ");
321
+ const legendColorClass = disabled ? "text-[var(--muted-fg)]" : isError ? "text-[var(--destructive)]" : "text-[var(--muted-fg)]";
322
+ const legendClassNameCombined = [
323
+ "ml-4 px-1 text-sm leading-none relative font-normal select-none",
324
+ legendColorClass,
325
+ legendClassNameProp
326
+ ].filter(Boolean).join(" ");
327
+ return /* @__PURE__ */ jsxRuntime.jsxs(
328
+ "fieldset",
272
329
  {
273
- ref,
274
- disabled,
275
- className: [
276
- "w-full bg-transparent outline-none transition-colors",
277
- isInline ? "" : "h-12 px-4 rounded-full",
278
- "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]",
279
- !isInline && "border",
280
- disabled ? "text-[#3F424F] cursor-not-allowed" + (isInline ? "" : " border-[#3F424F]") : [
281
- "text-[var(--muted-fg)]",
282
- isInline ? "" : "border-[var(--muted-fg)]",
283
- isInline ? "" : "hover:border-[var(--border)]",
284
- "focus:text-[var(--fg)]",
285
- isInline ? "" : "focus:border-[var(--color-brand)]"
286
- ].join(" "),
287
- className
288
- ].join(" "),
289
- ...props
330
+ ...restFieldsetProps,
331
+ className: fieldsetClassName,
332
+ children: [
333
+ /* @__PURE__ */ jsxRuntime.jsx(
334
+ "legend",
335
+ {
336
+ ...restLegendProps,
337
+ className: legendClassNameCombined,
338
+ children: label
339
+ }
340
+ ),
341
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex pl-5 pr-3 pb-1 h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full", children: inputElement }) })
342
+ ]
290
343
  }
291
344
  );
292
345
  }
package/dist/index.d.cts CHANGED
@@ -59,13 +59,29 @@ type CardProps = React.HTMLAttributes<HTMLDivElement> & {
59
59
  };
60
60
  declare function Card({ children, className, style, showDecorations, variant, elevated, lightImage, darkImage, ...props }: CardProps): react_jsx_runtime.JSX.Element;
61
61
 
62
- type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> & {
62
+ type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
63
63
  /** Input visual variant */
64
64
  variant?: "default" | "inline";
65
+ /** Optional floating label (renders a fieldset wrapper when provided) */
66
+ label?: string;
67
+ /** Additional props for the surrounding fieldset when label is set */
68
+ fieldsetProps?: React.FieldsetHTMLAttributes<HTMLFieldSetElement>;
69
+ /** Additional props for the legend when label is set */
70
+ legendProps?: React.HTMLAttributes<HTMLLegendElement>;
71
+ /** Flag to visually mark the input as errored */
72
+ error?: boolean;
65
73
  };
66
74
  declare const Input: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
67
75
  /** Input visual variant */
68
76
  variant?: "default" | "inline";
77
+ /** Optional floating label (renders a fieldset wrapper when provided) */
78
+ label?: string;
79
+ /** Additional props for the surrounding fieldset when label is set */
80
+ fieldsetProps?: React.FieldsetHTMLAttributes<HTMLFieldSetElement>;
81
+ /** Additional props for the legend when label is set */
82
+ legendProps?: React.HTMLAttributes<HTMLLegendElement>;
83
+ /** Flag to visually mark the input as errored */
84
+ error?: boolean;
69
85
  } & React.RefAttributes<HTMLInputElement>>;
70
86
 
71
87
  type TextareaProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> & {
package/dist/index.d.ts CHANGED
@@ -59,13 +59,29 @@ type CardProps = React.HTMLAttributes<HTMLDivElement> & {
59
59
  };
60
60
  declare function Card({ children, className, style, showDecorations, variant, elevated, lightImage, darkImage, ...props }: CardProps): react_jsx_runtime.JSX.Element;
61
61
 
62
- type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> & {
62
+ type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
63
63
  /** Input visual variant */
64
64
  variant?: "default" | "inline";
65
+ /** Optional floating label (renders a fieldset wrapper when provided) */
66
+ label?: string;
67
+ /** Additional props for the surrounding fieldset when label is set */
68
+ fieldsetProps?: React.FieldsetHTMLAttributes<HTMLFieldSetElement>;
69
+ /** Additional props for the legend when label is set */
70
+ legendProps?: React.HTMLAttributes<HTMLLegendElement>;
71
+ /** Flag to visually mark the input as errored */
72
+ error?: boolean;
65
73
  };
66
74
  declare const Input: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
67
75
  /** Input visual variant */
68
76
  variant?: "default" | "inline";
77
+ /** Optional floating label (renders a fieldset wrapper when provided) */
78
+ label?: string;
79
+ /** Additional props for the surrounding fieldset when label is set */
80
+ fieldsetProps?: React.FieldsetHTMLAttributes<HTMLFieldSetElement>;
81
+ /** Additional props for the legend when label is set */
82
+ legendProps?: React.HTMLAttributes<HTMLLegendElement>;
83
+ /** Flag to visually mark the input as errored */
84
+ error?: boolean;
69
85
  } & React.RefAttributes<HTMLInputElement>>;
70
86
 
71
87
  type TextareaProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> & {
package/dist/index.js CHANGED
@@ -244,28 +244,81 @@ function Card({
244
244
  );
245
245
  }
246
246
  var Input = React3.forwardRef(
247
- ({ className, disabled, variant = "default", ...props }, ref) => {
248
- const isInline = variant === "inline";
249
- return /* @__PURE__ */ jsx(
250
- "input",
247
+ ({
248
+ className,
249
+ disabled,
250
+ variant = "default",
251
+ label,
252
+ fieldsetProps,
253
+ legendProps,
254
+ error = false,
255
+ ...props
256
+ }, ref) => {
257
+ const isInlineVariant = variant === "inline";
258
+ const shouldUseInlineStyles = isInlineVariant || Boolean(label);
259
+ const isError = error && !disabled;
260
+ const inputClasses = [
261
+ "w-full bg-transparent outline-none transition-colors",
262
+ shouldUseInlineStyles ? "h-9" : "h-12 px-4 rounded-full",
263
+ "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]"
264
+ ];
265
+ if (!shouldUseInlineStyles) {
266
+ inputClasses.push("border");
267
+ }
268
+ if (disabled) {
269
+ inputClasses.push("text-[#3F424F]", "cursor-not-allowed");
270
+ if (!shouldUseInlineStyles) {
271
+ inputClasses.push("border-[#3F424F]");
272
+ }
273
+ } else {
274
+ inputClasses.push("text-[var(--muted-fg)]", "focus:text-[var(--fg)]");
275
+ if (!shouldUseInlineStyles) {
276
+ inputClasses.push(
277
+ isError ? "border-[var(--destructive)]" : "border-[var(--muted-fg)]",
278
+ isError ? "hover:border-[var(--destructive)]" : "hover:border-[var(--border)]",
279
+ isError ? "focus:border-[var(--destructive)]" : "focus:border-[var(--color-brand)]"
280
+ );
281
+ }
282
+ }
283
+ if (className) {
284
+ inputClasses.push(className);
285
+ }
286
+ const inputClassName = inputClasses.join(" ");
287
+ const inputElement = /* @__PURE__ */ jsx("input", { ref, disabled, className: inputClassName, ...props });
288
+ if (!label) {
289
+ return inputElement;
290
+ }
291
+ const { className: fieldsetClassNameProp = "", ...restFieldsetProps } = fieldsetProps ?? {};
292
+ const { className: legendClassNameProp = "", ...restLegendProps } = legendProps ?? {};
293
+ const fieldsetClassName = [
294
+ "w-full min-w-0 rounded-full border bg-[var(--surface)] transition-colors h-14",
295
+ isError ? "border-[var(--destructive)]" : "border-[var(--border)]",
296
+ isError ? "focus-within:border-[var(--destructive)]" : "focus-within:border-[var(--color-brand)]",
297
+ disabled ? "opacity-60 cursor-not-allowed" : "",
298
+ fieldsetClassNameProp
299
+ ].filter(Boolean).join(" ");
300
+ const legendColorClass = disabled ? "text-[var(--muted-fg)]" : isError ? "text-[var(--destructive)]" : "text-[var(--muted-fg)]";
301
+ const legendClassNameCombined = [
302
+ "ml-4 px-1 text-sm leading-none relative font-normal select-none",
303
+ legendColorClass,
304
+ legendClassNameProp
305
+ ].filter(Boolean).join(" ");
306
+ return /* @__PURE__ */ jsxs(
307
+ "fieldset",
251
308
  {
252
- ref,
253
- disabled,
254
- className: [
255
- "w-full bg-transparent outline-none transition-colors",
256
- isInline ? "" : "h-12 px-4 rounded-full",
257
- "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]",
258
- !isInline && "border",
259
- disabled ? "text-[#3F424F] cursor-not-allowed" + (isInline ? "" : " border-[#3F424F]") : [
260
- "text-[var(--muted-fg)]",
261
- isInline ? "" : "border-[var(--muted-fg)]",
262
- isInline ? "" : "hover:border-[var(--border)]",
263
- "focus:text-[var(--fg)]",
264
- isInline ? "" : "focus:border-[var(--color-brand)]"
265
- ].join(" "),
266
- className
267
- ].join(" "),
268
- ...props
309
+ ...restFieldsetProps,
310
+ className: fieldsetClassName,
311
+ children: [
312
+ /* @__PURE__ */ jsx(
313
+ "legend",
314
+ {
315
+ ...restLegendProps,
316
+ className: legendClassNameCombined,
317
+ children: label
318
+ }
319
+ ),
320
+ /* @__PURE__ */ jsx("div", { className: "relative flex pl-5 pr-3 pb-1 h-full", children: /* @__PURE__ */ jsx("div", { className: "flex w-full", children: inputElement }) })
321
+ ]
269
322
  }
270
323
  );
271
324
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoptocom/neopto-ui",
3
- "version": "1.4.4",
3
+ "version": "1.5.0",
4
4
  "private": false,
5
5
  "description": "A modern React component library built with Tailwind CSS v4 and TypeScript. Features dark mode, design tokens, and comprehensive Storybook documentation. Requires Tailwind v4+.",
6
6
  "keywords": [
@@ -49,6 +49,7 @@
49
49
  "typecheck": "tsc --noEmit",
50
50
  "storybook": "storybook dev -p 6006",
51
51
  "build-storybook": "storybook build",
52
+ "storybook:docs": "storybook build --docs --output-dir storybook-static && cp storybook-static/index.json storybook-static/docs.json",
52
53
  "prepublishOnly": "npm run build && npm run typecheck",
53
54
  "changeset": "changeset",
54
55
  "version-packages": "changeset version",
@@ -58,9 +59,9 @@
58
59
  "react": ">=18",
59
60
  "react-dom": ">=18"
60
61
  },
61
- "dependencies": {},
62
62
  "devDependencies": {
63
63
  "@changesets/cli": "^2.27.8",
64
+ "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.2",
64
65
  "@storybook/addon-actions": "^8.1.0",
65
66
  "@storybook/addon-essentials": "^8.1.0",
66
67
  "@storybook/addon-interactions": "^8.1.0",
@@ -0,0 +1,60 @@
1
+ import { Meta, Canvas, Story, ArgsTable } from "@storybook/blocks";
2
+ import { Breadcrumb } from "./Breadcrumb";
3
+ import * as BreadcrumbStories from "./Breadcrumb.stories";
4
+
5
+ <Meta of={BreadcrumbStories} />
6
+
7
+ # Breadcrumb
8
+
9
+ The `Breadcrumb` component displays the current location within a hierarchy and optional navigation
10
+ links for parent levels. It supports icons, external links, and click handlers for in-app routing.
11
+
12
+ ## When to use
13
+
14
+ - Help users understand where they are in a nested content structure.
15
+ - Offer a quick path back to previous sections without duplicating navigation in headers or sidebars.
16
+ - Pair with routed or client-side navigation; items accept either `href` or `onClick`.
17
+
18
+ ## Basic usage
19
+
20
+ ```tsx
21
+ import { Breadcrumb } from "@neoptocom/neopto-ui";
22
+
23
+ const items = [
24
+ { label: "Home", href: "/" },
25
+ { label: "Docs", href: "/docs" },
26
+ { label: "Components" }
27
+ ];
28
+
29
+ <Breadcrumb items={items} showHomeIcon />;
30
+ ```
31
+
32
+ <Canvas>
33
+ <Story of={BreadcrumbStories.Playground} />
34
+ </Canvas>
35
+
36
+ <ArgsTable of={BreadcrumbStories.Playground} />
37
+
38
+ ## Design guidance
39
+
40
+ - Keep labels short; truncate or abbreviate long section names where possible.
41
+ - Provide icons sparingly to reinforce key contexts (e.g., folders vs. people).
42
+ - Use `showHomeIcon` to reinforce a “home” entry without duplicating the label.
43
+ - Avoid mixing `href` and `onClick` handlers on the same item.
44
+
45
+ ## Composing with state
46
+
47
+ Combine breadcrumb click handlers with your router or state machine to switch views without a full
48
+ page load. The interactive example demonstrates an in-app documents flow.
49
+
50
+ <Canvas>
51
+ <Story of={BreadcrumbStories.InteractiveNavigation} />
52
+ </Canvas>
53
+
54
+ ## Accessibility
55
+
56
+ - Breadcrumbs are wrapped in a `<nav aria-label="Breadcrumb">` for screen readers.
57
+ - The final item receives `aria-current="page"` automatically.
58
+ - Clickable items expose keyboard handlers, so always keep labels descriptive.
59
+
60
+
@@ -0,0 +1,78 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Breadcrumb, type BreadcrumbItem } from "./Breadcrumb";
4
+
5
+ const meta: Meta<typeof Breadcrumb> = {
6
+ title: "Components/Breadcrumb",
7
+ component: Breadcrumb,
8
+ tags: ["autodocs"],
9
+ parameters: {
10
+ layout: "centered"
11
+ },
12
+ args: {
13
+ items: [
14
+ { label: "Home", href: "/" },
15
+ { label: "Library", href: "/library" },
16
+ { label: "Data" }
17
+ ] satisfies BreadcrumbItem[]
18
+ }
19
+ };
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof Breadcrumb>;
23
+
24
+ export const Playground: Story = {};
25
+
26
+ export const WithIcons: Story = {
27
+ args: {
28
+ items: [
29
+ { label: "Dashboard", href: "/", icon: "dashboard" },
30
+ { label: "Projects", href: "/projects", icon: "folder" },
31
+ { label: "Neptune Launch", icon: "rocket_launch" }
32
+ ]
33
+ }
34
+ };
35
+
36
+ export const WithHomeIcon: Story = {
37
+ args: {
38
+ showHomeIcon: true
39
+ }
40
+ };
41
+
42
+ export const InteractiveNavigation: Story = {
43
+ render: (args) => {
44
+ const sections: BreadcrumbItem[] = [
45
+ { label: "Getting Started", href: "/docs/getting-started" },
46
+ { label: "Guides", href: "/docs/guides" },
47
+ { label: "Auth", icon: "lock" }
48
+ ];
49
+
50
+ const [activeSection, setActiveSection] = useState(sections[sections.length - 1]);
51
+
52
+ return (
53
+ <div className="flex flex-col gap-6 w-full max-w-3xl">
54
+ <Breadcrumb
55
+ {...args}
56
+ items={[
57
+ { label: "Docs", href: "/docs", icon: "menu_book" },
58
+ ...sections.map((section, index) => ({
59
+ ...section,
60
+ onClick: () => setActiveSection(section),
61
+ href: index === sections.length - 1 ? undefined : section.href
62
+ }))
63
+ ]}
64
+ />
65
+
66
+ <section className="rounded-lg border border-[var(--border)] bg-[var(--surface)] p-6 space-y-2">
67
+ <h2 className="text-xl font-semibold">{activeSection.label}</h2>
68
+ <p className="text-sm text-[var(--muted-fg)]">
69
+ Showcase how breadcrumbs can drive in-app navigation without reloading the page by
70
+ combining click handlers with your own state management.
71
+ </p>
72
+ </section>
73
+ </div>
74
+ );
75
+ }
76
+ };
77
+
78
+
@@ -0,0 +1,56 @@
1
+ import { Meta, Canvas, Story, ArgsTable } from "@storybook/blocks";
2
+ import { Button } from "./Button";
3
+ import * as ButtonStories from "./Button.stories";
4
+
5
+ <Meta of={ButtonStories} />
6
+
7
+ # Button
8
+
9
+ Buttons trigger primary user actions. They support three visual variants, three sizes, optional full
10
+ width layout, and icon affordances.
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import { Button } from "@neoptocom/neopto-ui";
16
+
17
+ <Button variant="primary" size="md">
18
+ Save changes
19
+ </Button>;
20
+ ```
21
+
22
+ <Canvas>
23
+ <Story of={ButtonStories.Playground} />
24
+ </Canvas>
25
+
26
+ <ArgsTable of={ButtonStories.Playground} />
27
+
28
+ ## Variants
29
+
30
+ Use variants to communicate hierarchy:
31
+
32
+ - `primary`: main call to action.
33
+ - `secondary`: supporting actions with visual emphasis.
34
+ - `ghost`: low emphasis actions within already prominent surfaces.
35
+
36
+ <Canvas>
37
+ <Story of={ButtonStories.Variants} />
38
+ </Canvas>
39
+
40
+ ## Layout
41
+
42
+ - Aim for one primary button per view; demote additional actions to secondary/ghost.
43
+ - Combine icons with concise labels for quicker scanning.
44
+ - Apply `fullWidth` for responsive mobile layouts or wizard footers.
45
+
46
+ <Canvas>
47
+ <Story of={ButtonStories.FullWidthCallToAction} />
48
+ </Canvas>
49
+
50
+ ## Accessibility
51
+
52
+ - Buttons use semantic `<button>` elements with focus styles.
53
+ - Provide descriptive text even when using icons to ensure screen readers announce intent.
54
+ - Disable buttons sparingly—pair with helper text when an action is unavailable.
55
+
56
+
@@ -1,24 +1,25 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
- import { Button } from "../components/Button";
3
- import Icon from "../components/Icon";
4
- import Typo from "../components/Typo";
2
+ import Icon from "./Icon";
3
+ import Typo from "./Typo";
4
+ import { Button } from "./Button";
5
5
 
6
6
  const meta: Meta<typeof Button> = {
7
7
  title: "Components/Button",
8
8
  component: Button,
9
+ tags: ["autodocs"],
9
10
  args: {
10
- children: "Button",
11
+ children: "Primary action",
11
12
  variant: "primary",
12
13
  size: "md",
13
14
  disabled: false
14
15
  },
15
16
  argTypes: {
16
17
  variant: {
17
- control: "radio",
18
+ control: "inline-radio",
18
19
  options: ["primary", "secondary", "ghost"]
19
20
  },
20
21
  size: {
21
- control: "radio",
22
+ control: "inline-radio",
22
23
  options: ["sm", "md", "lg"]
23
24
  },
24
25
  fullWidth: {
@@ -39,13 +40,19 @@ export const Variants: Story = {
39
40
  render: () => (
40
41
  <div className="flex flex-wrap items-center gap-4">
41
42
  <Button variant="primary">
42
- <Typo variant="title-sm" bold="semibold">Primary</Typo>
43
+ <Typo variant="title-sm" bold="semibold">
44
+ Primary
45
+ </Typo>
43
46
  </Button>
44
47
  <Button variant="secondary">
45
- <Typo variant="title-sm" bold="semibold">Secondary</Typo>
48
+ <Typo variant="title-sm" bold="semibold">
49
+ Secondary
50
+ </Typo>
46
51
  </Button>
47
52
  <Button variant="ghost">
48
- <Typo variant="title-sm" bold="semibold">Ghost</Typo>
53
+ <Typo variant="title-sm" bold="semibold">
54
+ Ghost
55
+ </Typo>
49
56
  </Button>
50
57
  </div>
51
58
  )
@@ -55,29 +62,19 @@ export const Sizes: Story = {
55
62
  render: () => (
56
63
  <div className="flex flex-wrap items-center gap-4">
57
64
  <Button size="sm">
58
- <Typo variant="title-sm" bold="semibold">Small</Typo>
65
+ <Typo variant="title-sm" bold="semibold">
66
+ Small
67
+ </Typo>
59
68
  </Button>
60
69
  <Button size="md">
61
- <Typo variant="title-sm" bold="semibold">Medium</Typo>
70
+ <Typo variant="title-sm" bold="semibold">
71
+ Medium
72
+ </Typo>
62
73
  </Button>
63
74
  <Button size="lg">
64
- <Typo variant="title-sm" bold="semibold">Large</Typo>
65
- </Button>
66
- </div>
67
- )
68
- };
69
-
70
- export const States: Story = {
71
- render: () => (
72
- <div className="flex flex-wrap items-center gap-4">
73
- <Button>
74
- <Typo variant="title-sm" bold="semibold">Default</Typo>
75
- </Button>
76
- <Button disabled>
77
- <Typo variant="title-sm" bold="semibold">Disabled</Typo>
78
- </Button>
79
- <Button fullWidth>
80
- <Typo variant="title-sm" bold="semibold">Full Width</Typo>
75
+ <Typo variant="title-sm" bold="semibold">
76
+ Large
77
+ </Typo>
81
78
  </Button>
82
79
  </div>
83
80
  )
@@ -88,16 +85,26 @@ export const WithIcons: Story = {
88
85
  <div className="flex flex-wrap items-center gap-4">
89
86
  <Button>
90
87
  <Icon name="add" />
91
- <Typo variant="title-sm" bold="semibold">Add Item</Typo>
88
+ <span>Add Item</span>
92
89
  </Button>
93
90
  <Button variant="secondary">
94
- <Icon name="delete" />
95
- <Typo variant="title-sm" bold="semibold">Delete</Typo>
91
+ <Icon name="download" />
92
+ <span>Download</span>
96
93
  </Button>
97
94
  <Button variant="ghost">
98
95
  <Icon name="settings" />
99
- <Typo variant="title-sm" bold="semibold">Settings</Typo>
96
+ <span>Settings</span>
100
97
  </Button>
101
98
  </div>
102
99
  )
103
100
  };
101
+
102
+ export const FullWidthCallToAction: Story = {
103
+ args: {
104
+ fullWidth: true,
105
+ size: "lg",
106
+ children: "Start free trial"
107
+ }
108
+ };
109
+
110
+
@@ -0,0 +1,56 @@
1
+ import { Meta, Canvas, Story, ArgsTable } from "@storybook/blocks";
2
+ import { Card } from "./Card";
3
+ import * as CardStories from "./Card.stories";
4
+
5
+ <Meta of={CardStories} />
6
+
7
+ # Card
8
+
9
+ Cards provide glassmorphic containers for grouping content. They offer decorative borders, elevated
10
+ shadows, and an app-background variant that mirrors hero surfaces.
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import { Card } from "@neoptocom/neopto-ui";
16
+
17
+ <Card>
18
+ <h3>Weekly summary</h3>
19
+ <p>Use cards to separate content into digestible surfaces.</p>
20
+ </Card>;
21
+ ```
22
+
23
+ <Canvas>
24
+ <Story of={CardStories.Playground} />
25
+ </Canvas>
26
+
27
+ <ArgsTable of={CardStories.Playground} />
28
+
29
+ ## Variants
30
+
31
+ - `showDecorations` adds gradient strokes suited for marketing hero cards.
32
+ - `variant="app-background"` injects the NeoPTO hero artwork and auto-switches with theme.
33
+ - `elevated` applies a high-emphasis drop shadow—combine with default or app backgrounds.
34
+
35
+ <Canvas>
36
+ <Story of={CardStories.AppBackground} />
37
+ </Canvas>
38
+
39
+ ## Layout tips
40
+
41
+ - Allow cards to breathe: default padding is `p-6`, override via the `className` prop for custom
42
+ spacing.
43
+ - Stack cards in responsive grids to create dashboards or settings panels.
44
+ - Avoid nesting too many cards; use muted surfaces inside when grouping secondary information.
45
+
46
+ <Canvas>
47
+ <Story of={CardStories.DashboardLayout} />
48
+ </Canvas>
49
+
50
+ ## Accessibility
51
+
52
+ Cards render as semantic `<div>` elements. Provide meaningful headings and maintain contrast ratios
53
+ when overlaying text on app backgrounds. For interactive cards, wrap focusable elements instead of
54
+ attaching click handlers to the card root.
55
+
56
+