diff --git a/apps/storybook/.storybook/preview.ts b/apps/storybook/.storybook/preview.ts index b0c7224444..4cd3571c04 100644 --- a/apps/storybook/.storybook/preview.ts +++ b/apps/storybook/.storybook/preview.ts @@ -1,5 +1,6 @@ import type { Preview } from "@storybook/react-vite"; import React from "react"; +import "../../../packages/survey-ui/src/styles/globals.css"; import { I18nProvider } from "../../web/lingodotdev/client"; import "../../web/modules/ui/globals.css"; diff --git a/packages/survey-ui/package.json b/packages/survey-ui/package.json index b266ecb8c8..4524ebeac9 100644 --- a/packages/survey-ui/package.json +++ b/packages/survey-ui/package.json @@ -40,12 +40,14 @@ }, "dependencies": { "@radix-ui/react-checkbox": "1.3.1", + "@radix-ui/react-label": "2.1.1", "@radix-ui/react-progress": "1.1.8", "@radix-ui/react-radio-group": "1.3.6", "@radix-ui/react-slot": "1.2.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "tailwind-merge": "^2.5.5" + "tailwind-merge": "^2.5.5", + "lucide-react": "0.555.0" }, "devDependencies": { "@formbricks/config-typescript": "workspace:*", diff --git a/packages/survey-ui/src/components/alert.stories.tsx b/packages/survey-ui/src/components/alert.stories.tsx new file mode 100644 index 0000000000..60a3257d98 --- /dev/null +++ b/packages/survey-ui/src/components/alert.stories.tsx @@ -0,0 +1,66 @@ +import { type Meta, type StoryObj } from "@storybook/react"; +import { TriangleAlertIcon } from "lucide-react"; +import { Alert, AlertDescription, AlertTitle } from "./alert"; + +const meta: Meta = { + title: "UI-package/Alert", + component: Alert, + tags: ["autodocs"], + parameters: { + layout: "centered", + }, + argTypes: { + variant: { + control: "select", + options: ["default", "destructive"], + description: "Style variant of the alert", + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + Alert Title + This is a default alert message. + + ), +}; + +export const Destructive: Story = { + render: () => ( + + Error + Something went wrong. Please try again. + + ), +}; + +export const DestructiveWithIcon: Story = { + render: () => ( + + + Error + Something went wrong. Please try again. + + ), +}; + +export const WithTitleOnly: Story = { + render: () => ( + + Important Notice + + ), +}; + +export const WithDescriptionOnly: Story = { + render: () => ( + + This alert only has a description. + + ), +}; diff --git a/packages/survey-ui/src/components/alert.tsx b/packages/survey-ui/src/components/alert.tsx new file mode 100644 index 0000000000..544544fa9e --- /dev/null +++ b/packages/survey-ui/src/components/alert.tsx @@ -0,0 +1,54 @@ +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; +import { cn } from "../lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps): React.JSX.Element { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element { + return ( +
+ ); +} + +function AlertDescription({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription }; diff --git a/packages/survey-ui/src/components/button.stories.tsx b/packages/survey-ui/src/components/button.stories.tsx index df4a0194c3..5205d66d52 100644 --- a/packages/survey-ui/src/components/button.stories.tsx +++ b/packages/survey-ui/src/components/button.stories.tsx @@ -1,7 +1,23 @@ -import { type Meta, type StoryObj } from "@storybook/react"; +import type { Decorator, Meta, StoryObj } from "@storybook/react"; +import React from "react"; import { Button } from "./button"; -const meta: Meta = { +// Styling options for the StylingPlayground story +interface StylingOptions { + buttonHeight: string; + buttonWidth: string; + buttonFontSize: string; + buttonBorderRadius: string; + buttonBgColor: string; + buttonTextColor: string; + buttonPaddingX: string; + buttonPaddingY: string; +} + +type ButtonProps = React.ComponentProps; +type StoryProps = ButtonProps & StylingOptions; + +const meta: Meta = { title: "UI-package/Button", component: Button, tags: ["autodocs"], @@ -11,17 +27,19 @@ const meta: Meta = { argTypes: { variant: { control: "select", - options: ["default", "destructive", "outline", "secondary", "ghost", "link", "custom"], + options: ["default", "destructive", "outline", "secondary", "ghost", "link"], description: "Visual style variant of the button", + table: { category: "Component Props" }, }, size: { control: "select", - options: ["default", "sm", "lg", "icon", "icon-sm", "icon-lg"], + options: ["default", "sm", "lg", "icon"], description: "Size of the button", + table: { category: "Component Props" }, }, - style: { - control: "object", - description: "Custom style for the button (only works with variant 'custom')", + disabled: { + control: "boolean", + table: { category: "Component Props" }, }, asChild: { table: { disable: true }, @@ -33,7 +51,115 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; + +// Decorator to apply CSS variables from story args +const withCSSVariables: Decorator = (Story, context) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- Storybook's Decorator type doesn't properly infer args type + const args = context.args as StoryProps; + const { + buttonHeight, + buttonWidth, + buttonFontSize, + buttonBorderRadius, + buttonBgColor, + buttonTextColor, + buttonPaddingX, + buttonPaddingY, + } = args; + + const cssVarStyle = { + "--fb-button-height": buttonHeight, + "--fb-button-width": buttonWidth, + "--fb-button-font-size": buttonFontSize, + "--fb-button-border-radius": buttonBorderRadius, + "--fb-button-bg-color": buttonBgColor, + "--fb-button-text-color": buttonTextColor, + "--fb-button-padding-x": buttonPaddingX, + "--fb-button-padding-y": buttonPaddingY, + } as React.CSSProperties; + + return ( +
+ +
+ ); +}; + +export const StylingPlayground: Story = { + args: { + children: "Custom Button", + // Default styling values + buttonHeight: "40px", + buttonWidth: "auto", + buttonFontSize: "14px", + buttonBorderRadius: "0.5rem", + buttonBgColor: "#3b82f6", + buttonTextColor: "#ffffff", + buttonPaddingX: "16px", + buttonPaddingY: "8px", + }, + argTypes: { + // Button Styling (CSS Variables) - Only for this story + buttonHeight: { + control: "text", + table: { + category: "Button Styling", + defaultValue: { summary: "40px" }, + }, + }, + buttonWidth: { + control: "text", + table: { + category: "Button Styling", + defaultValue: { summary: "auto" }, + }, + }, + buttonFontSize: { + control: "text", + table: { + category: "Button Styling", + defaultValue: { summary: "14px" }, + }, + }, + buttonBorderRadius: { + control: "text", + table: { + category: "Button Styling", + defaultValue: { summary: "var(--fb-border-radius)" }, + }, + }, + buttonBgColor: { + control: "color", + table: { + category: "Button Styling", + defaultValue: { summary: "var(--fb-brand-color)" }, + }, + }, + buttonTextColor: { + control: "color", + table: { + category: "Button Styling", + defaultValue: { summary: "var(--fb-brand-text-color)" }, + }, + }, + buttonPaddingX: { + control: "text", + table: { + category: "Button Styling", + defaultValue: { summary: "16px" }, + }, + }, + buttonPaddingY: { + control: "text", + table: { + category: "Button Styling", + defaultValue: { summary: "8px" }, + }, + }, + }, + decorators: [withCSSVariables], +}; export const Default: Story = { args: { @@ -103,14 +229,3 @@ export const Disabled: Story = { children: "Disabled Button", }, }; - -export const WithCustomStyle: Story = { - args: { - style: { - fontWeight: "bold", - backgroundColor: "red", - }, - variant: "custom", - children: "Custom Style Button", - }, -}; diff --git a/packages/survey-ui/src/components/button.tsx b/packages/survey-ui/src/components/button.tsx index 6c3ce1c2e3..95753104f0 100644 --- a/packages/survey-ui/src/components/button.tsx +++ b/packages/survey-ui/src/components/button.tsx @@ -16,7 +16,6 @@ const buttonVariants = cva( secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", - custom: "", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", @@ -32,17 +31,29 @@ const buttonVariants = cva( } ); +// Default styles driven by CSS variables +export const cssVarStyles: React.CSSProperties = { + height: "var(--fb-button-height)", + width: "var(--fb-button-width)", + fontSize: "var(--fb-button-font-size)", + borderRadius: "var(--fb-button-border-radius)", + backgroundColor: "var(--fb-button-bg-color)", + color: "var(--fb-button-text-color)", + paddingLeft: "var(--fb-button-padding-x)", + paddingRight: "var(--fb-button-padding-x)", + paddingTop: "var(--fb-button-padding-y)", + paddingBottom: "var(--fb-button-padding-y)", +}; + function Button({ className, variant, size, asChild = false, - style, ...props }: React.ComponentProps<"button"> & VariantProps & { asChild?: boolean; - style?: React.CSSProperties; }): React.JSX.Element { const Comp = asChild ? Slot : "button"; @@ -50,8 +61,8 @@ function Button({ ); diff --git a/packages/survey-ui/src/components/checkbox.stories.tsx b/packages/survey-ui/src/components/checkbox.stories.tsx new file mode 100644 index 0000000000..c4404db4c3 --- /dev/null +++ b/packages/survey-ui/src/components/checkbox.stories.tsx @@ -0,0 +1,90 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { Checkbox } from "./checkbox"; +import { Label } from "./label"; + +const meta: Meta = { + title: "UI-package/Checkbox", + component: Checkbox, + parameters: { + layout: "centered", + docs: { + description: { + component: + "A checkbox component built with Radix UI primitives. Supports checked, unchecked, and indeterminate states with full accessibility support.", + }, + }, + }, + tags: ["autodocs"], + argTypes: { + checked: { + control: { type: "boolean" }, + description: "The controlled checked state of the checkbox", + }, + disabled: { + control: { type: "boolean" }, + description: "Whether the checkbox is disabled", + }, + required: { + control: { type: "boolean" }, + description: "Whether the checkbox is required", + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + "aria-label": "Checkbox", + }, +}; + +export const Checked: Story = { + args: { + checked: true, + "aria-label": "Checked checkbox", + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + "aria-label": "Disabled checkbox", + }, +}; + +export const DisabledChecked: Story = { + args: { + disabled: true, + checked: true, + "aria-label": "Disabled checked checkbox", + }, +}; + +export const WithLabel: Story = { + render: () => ( +
+ + +
+ ), +}; + +export const WithLabelChecked: Story = { + render: () => ( +
+ + +
+ ), +}; + +export const WithLabelDisabled: Story = { + render: () => ( +
+ + +
+ ), +}; diff --git a/packages/survey-ui/src/components/checkbox.tsx b/packages/survey-ui/src/components/checkbox.tsx new file mode 100644 index 0000000000..d57318bbe5 --- /dev/null +++ b/packages/survey-ui/src/components/checkbox.tsx @@ -0,0 +1,29 @@ +"use client"; + +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { CheckIcon } from "lucide-react"; +import * as React from "react"; +import { cn } from "../lib/utils"; + +function Checkbox({ + className, + ...props +}: React.ComponentProps): React.JSX.Element { + return ( + + + + + + ); +} + +export { Checkbox }; diff --git a/packages/survey-ui/src/components/input.stories.tsx b/packages/survey-ui/src/components/input.stories.tsx new file mode 100644 index 0000000000..2f4fdd98e2 --- /dev/null +++ b/packages/survey-ui/src/components/input.stories.tsx @@ -0,0 +1,323 @@ +import type { Decorator, Meta, StoryObj } from "@storybook/react"; +import React from "react"; +import { Input, type InputProps } from "./input"; + +// Styling options for the StylingPlayground story +interface StylingOptions { + inputWidth: string; + inputHeight: string; + inputBgColor: string; + inputBorderColor: string; + inputBorderRadius: string; + inputFontFamily: string; + inputFontSize: string; + inputFontWeight: string; + inputColor: string; + inputPlaceholderColor: string; + inputPaddingX: string; + inputPaddingY: string; + inputShadow: string; +} + +type StoryProps = InputProps & Partial; + +const meta: Meta = { + title: "UI-package/Input", + component: Input, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + type: { + control: { type: "select" }, + options: ["text", "email", "password", "number", "tel", "url", "search", "file"], + table: { category: "Component Props" }, + }, + placeholder: { + control: "text", + table: { category: "Component Props" }, + }, + disabled: { + control: "boolean", + table: { category: "Component Props" }, + }, + errorMessage: { + control: "text", + table: { category: "Component Props" }, + }, + dir: { + control: { type: "select" }, + options: ["ltr", "rtl"], + table: { category: "Component Props" }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// Decorator to apply CSS variables from story args +const withCSSVariables: Decorator = (Story, context) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- Storybook's Decorator type doesn't properly infer args type + const args = context.args as StoryProps; + const { + inputWidth, + inputHeight, + inputBgColor, + inputBorderColor, + inputBorderRadius, + inputFontFamily, + inputFontSize, + inputFontWeight, + inputColor, + inputPlaceholderColor, + inputPaddingX, + inputPaddingY, + inputShadow, + } = args; + + const cssVarStyle: React.CSSProperties & Record = { + "--fb-input-width": inputWidth, + "--fb-input-height": inputHeight, + "--fb-input-bg-color": inputBgColor, + "--fb-input-border-color": inputBorderColor, + "--fb-input-border-radius": inputBorderRadius, + "--fb-input-font-family": inputFontFamily, + "--fb-input-font-size": inputFontSize, + "--fb-input-font-weight": inputFontWeight, + "--fb-input-color": inputColor, + "--fb-input-placeholder-color": inputPlaceholderColor, + "--fb-input-padding-x": inputPaddingX, + "--fb-input-padding-y": inputPaddingY, + "--fb-input-shadow": inputShadow, + }; + + return ( +
+ +
+ ); +}; + +export const StylingPlayground: Story = { + args: { + placeholder: "Enter text...", + // Default styling values + inputWidth: "400px", + inputHeight: "2.5rem", + inputBgColor: "#ffffff", + inputBorderColor: "#e2e8f0", + inputBorderRadius: "0.5rem", + inputFontFamily: "system-ui, sans-serif", + inputFontSize: "0.875rem", + inputFontWeight: "400", + inputColor: "#1e293b", + inputPlaceholderColor: "#94a3b8", + inputPaddingX: "0.75rem", + inputPaddingY: "0.5rem", + inputShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", + }, + argTypes: { + // Input Styling (CSS Variables) - Only for this story + inputWidth: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "100%" }, + }, + }, + inputHeight: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "2.25rem" }, + }, + }, + inputBgColor: { + control: "color", + table: { + category: "Input Styling", + defaultValue: { summary: "transparent" }, + }, + }, + inputBorderColor: { + control: "color", + table: { + category: "Input Styling", + defaultValue: { summary: "var(--input)" }, + }, + }, + inputBorderRadius: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "0.5rem" }, + }, + }, + inputFontFamily: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "inherit" }, + }, + }, + inputFontSize: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "0.875rem" }, + }, + }, + inputFontWeight: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "400" }, + }, + }, + inputColor: { + control: "color", + table: { + category: "Input Styling", + defaultValue: { summary: "var(--foreground)" }, + }, + }, + inputPlaceholderColor: { + control: "color", + table: { + category: "Input Styling", + defaultValue: { summary: "var(--muted-foreground)" }, + }, + }, + inputPaddingX: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "0.75rem" }, + }, + }, + inputPaddingY: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "0.25rem" }, + }, + }, + inputShadow: { + control: "text", + table: { + category: "Input Styling", + defaultValue: { summary: "0 1px 2px 0 rgb(0 0 0 / 0.05)" }, + }, + }, + }, + decorators: [withCSSVariables], +}; + +export const Default: Story = { + args: { + placeholder: "Enter text...", + }, +}; + +export const WithValue: Story = { + args: { + defaultValue: "Sample text", + placeholder: "Enter text...", + }, +}; + +export const Email: Story = { + args: { + type: "email", + placeholder: "email@example.com", + }, +}; + +export const Password: Story = { + args: { + type: "password", + placeholder: "Enter password", + }, +}; + +export const NumberInput: Story = { + args: { + type: "number", + placeholder: "0", + }, +}; + +export const WithError: Story = { + args: { + placeholder: "Enter your email", + defaultValue: "invalid-email", + errorMessage: "Please enter a valid email address", + }, +}; + +export const Disabled: Story = { + args: { + placeholder: "Disabled input", + disabled: true, + }, +}; + +export const DisabledWithValue: Story = { + args: { + defaultValue: "Cannot edit this", + disabled: true, + }, +}; + +export const FileUpload: Story = { + args: { + type: "file", + }, +}; + +export const FileUploadWithRTL: Story = { + args: { + type: "file", + dir: "rtl", + }, +}; + +export const FileUploadWithError: Story = { + args: { + type: "file", + errorMessage: "Please upload a valid file", + }, +}; + +export const FileUploadWithErrorAndRTL: Story = { + args: { + type: "file", + errorMessage: "Please upload a valid file", + dir: "rtl", + }, +}; + +export const RTL: Story = { + args: { + dir: "rtl", + placeholder: "أدخل النص هنا", + defaultValue: "نص تجريبي", + }, +}; + +export const FullWidth: Story = { + args: { + placeholder: "Full width input", + className: "w-96", + }, +}; + +export const WithErrorAndRTL: Story = { + args: { + dir: "rtl", + placeholder: "أدخل بريدك الإلكتروني", + errorMessage: "هذا الحقل مطلوب", + }, +}; diff --git a/packages/survey-ui/src/components/input.tsx b/packages/survey-ui/src/components/input.tsx new file mode 100644 index 0000000000..06359969a1 --- /dev/null +++ b/packages/survey-ui/src/components/input.tsx @@ -0,0 +1,72 @@ +import { AlertCircle } from "lucide-react"; +import * as React from "react"; +import { cn } from "../lib/utils"; + +interface InputProps extends React.ComponentProps<"input"> { + /** Text direction for RTL language support */ + dir?: "ltr" | "rtl"; + /** Error message to display above the input */ + errorMessage?: string; +} + +function Input({ className, type, errorMessage, dir, ...props }: InputProps): React.JSX.Element { + const hasError = Boolean(errorMessage); + + // Default styles driven by CSS variables + const cssVarStyles: React.CSSProperties = { + width: "var(--fb-input-width)", + height: "var(--fb-input-height)", + backgroundColor: "var(--fb-input-bg-color)", + borderColor: "var(--fb-input-border-color)", + borderRadius: "var(--fb-input-border-radius)", + fontFamily: "var(--fb-input-font-family)", + fontSize: "var(--fb-input-font-size)", + fontWeight: "var(--fb-input-font-weight)" as React.CSSProperties["fontWeight"], + color: "var(--fb-input-color)", + paddingLeft: "var(--fb-input-padding-x)", + paddingRight: "var(--fb-input-padding-x)", + paddingTop: "var(--fb-input-padding-y)", + paddingBottom: "var(--fb-input-padding-y)", + boxShadow: "var(--fb-input-shadow)", + }; + + return ( +
+ {errorMessage ? ( +
+ + {errorMessage} +
+ ) : null} + +
+ ); +} + +export { Input }; +export type { InputProps }; diff --git a/packages/survey-ui/src/components/label.stories.tsx b/packages/survey-ui/src/components/label.stories.tsx new file mode 100644 index 0000000000..401e52ada9 --- /dev/null +++ b/packages/survey-ui/src/components/label.stories.tsx @@ -0,0 +1,396 @@ +import type { Decorator, Meta, StoryObj } from "@storybook/react"; +import React from "react"; +import { Checkbox } from "./checkbox"; +import { Input } from "./input"; +import { Label, type LabelProps } from "./label"; +import { Textarea } from "./textarea"; + +// Styling options for the StylingPlayground stories +interface HeadlineStylingOptions { + headlineFontFamily: string; + headlineFontWeight: string; + headlineFontSize: string; + headlineColor: string; + headlineOpacity: string; +} + +interface DescriptionStylingOptions { + descriptionFontFamily: string; + descriptionFontWeight: string; + descriptionFontSize: string; + descriptionColor: string; + descriptionOpacity: string; +} + +interface DefaultStylingOptions { + labelFontFamily: string; + labelFontWeight: string; + labelFontSize: string; + labelColor: string; + labelOpacity: string; +} + +type StoryProps = LabelProps & + Partial; + +const meta: Meta = { + title: "UI-package/Label", + component: Label, + parameters: { + layout: "centered", + docs: { + description: { + component: + "A label component built with Radix UI primitives. Provides accessible labeling for form controls with proper association and styling.", + }, + }, + }, + tags: ["autodocs"], + argTypes: { + variant: { + control: "select", + options: ["default", "headline", "description"], + description: "Visual style variant of the label", + table: { category: "Component Props" }, + }, + htmlFor: { + control: { type: "text" }, + description: "The id of the form control this label is associated with", + table: { category: "Component Props" }, + }, + style: { + control: "object", + table: { category: "Component Props" }, + }, + }, + args: { + children: "Label text", + }, +}; + +export default meta; +type Story = StoryObj; + +// Decorator to apply CSS variables for headline variant +const withHeadlineCSSVariables: Decorator = (Story, context) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- Storybook's Decorator type doesn't properly infer args type + const args = context.args as StoryProps; + const { headlineFontFamily, headlineFontWeight, headlineFontSize, headlineColor, headlineOpacity } = args; + + const cssVarStyle: React.CSSProperties & Record = { + "--fb-question-headline-font-family": headlineFontFamily ?? undefined, + "--fb-question-headline-font-weight": headlineFontWeight ?? undefined, + "--fb-question-headline-font-size": headlineFontSize ?? undefined, + "--fb-question-headline-color": headlineColor ?? undefined, + "--fb-question-headline-opacity": headlineOpacity ?? undefined, + }; + + return ( +
+ +
+ ); +}; + +// Decorator to apply CSS variables for description variant +const withDescriptionCSSVariables: Decorator = (Story, context) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- Storybook's Decorator type doesn't properly infer args type + const args = context.args as StoryProps; + const { + descriptionFontFamily, + descriptionFontWeight, + descriptionFontSize, + descriptionColor, + descriptionOpacity, + } = args; + + const cssVarStyle: React.CSSProperties & Record = { + "--fb-question-description-font-family": descriptionFontFamily ?? undefined, + "--fb-question-description-font-weight": descriptionFontWeight ?? undefined, + "--fb-question-description-font-size": descriptionFontSize ?? undefined, + "--fb-question-description-color": descriptionColor ?? undefined, + "--fb-question-description-opacity": descriptionOpacity ?? undefined, + }; + + return ( +
+ +
+ ); +}; + +const withCustomCSSVariables: Decorator = (Story, context) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- Storybook's Decorator type doesn't properly infer args type + const args = context.args as StoryProps; + const { labelFontFamily, labelFontWeight, labelFontSize, labelColor, labelOpacity } = args; + + const cssVarStyle: React.CSSProperties & Record = { + "--fb-label-font-family": labelFontFamily ?? undefined, + "--fb-label-font-weight": labelFontWeight ?? undefined, + "--fb-label-font-size": labelFontSize ?? undefined, + "--fb-label-color": labelColor ?? undefined, + "--fb-label-opacity": labelOpacity ?? undefined, + }; + + return ( +
+ +
+ ); +}; + +export const Default: Story = { + args: {}, +}; + +export const WithInput: Story = { + render: () => ( +
+ + +
+ ), +}; + +export const WithCheckbox: Story = { + render: () => ( +
+ + +
+ ), +}; + +export const WithTextarea: Story = { + render: () => ( +
+ +