From 5084bce5f7048beacbc1efee050864467b958155 Mon Sep 17 00:00:00 2001 From: Navid Shad Date: Sun, 31 Aug 2025 19:57:33 +0400 Subject: [PATCH 01/16] feat: Add badge label support to IconButton component - Introduced a new `label` prop for the IconButton component to display text in badge mode. - Updated the IconButton.vue template to conditionally render the label based on the `badge` and `label` props. - Enhanced Storybook stories to demonstrate badge mode with labels, including size and color variants. - Added documentation for the new label feature in the IconButton component. --- src/elements/IconButton.stories.ts | 136 +++++++++++++++++++++++++++++ src/elements/IconButton.vue | 26 ++++++ 2 files changed, 162 insertions(+) diff --git a/src/elements/IconButton.stories.ts b/src/elements/IconButton.stories.ts index d42ac4f..4cccb44 100644 --- a/src/elements/IconButton.stories.ts +++ b/src/elements/IconButton.stories.ts @@ -80,6 +80,10 @@ const meta = { description: "Enable badge mode (non-interactive): no click events and default cursor", }, + label: { + control: "text", + description: "Label text to display when in badge mode", + }, }, args: { color: "default", @@ -484,3 +488,135 @@ export const BadgeVariants: Story = { }); }, }; + +export const BadgeLabelSizes: Story = { + render: (args) => ({ + components: { IconButton }, + setup() { + return { args }; + }, + template: ` +
+

IconButton Badge with Labels - Size Variants

+
+
+ + XS +
+
+ + SM +
+
+ + MD +
+
+ + LG +
+
+ + XL +
+
+

+ Badge mode with labels at different sizes - note how label text scales with icon size +

+
+ `, + }), + args: { + rounded: "full", + badge: true, + icon: "IconStar", + }, + parameters: { + docs: { + description: { + story: "Demonstrates how labels scale with different badge sizes.", + }, + }, + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step("Verify badge label sizes story renders correctly", async () => { + // Verify the story renders without errors + const title = canvas.getByText("IconButton Badge with Labels - Size Variants"); + expect(title).toBeInTheDocument(); + + // Verify we have some IconButton elements + const iconButtons = canvas.getAllByRole("generic"); + expect(iconButtons.length).toBeGreaterThan(0); + }); + }, +}; + +export const BadgeWithLabelsVariants: Story = { + render: (args) => ({ + components: { IconButton }, + setup() { + return { args }; + }, + template: ` +
+

IconButton Badge with Labels - Color Variants

+
+
+ + Primary +
+
+ + Success +
+
+ + Warning +
+
+ + Danger +
+
+ + Info +
+
+ + Secondary +
+
+

+ Badge mode with labels in different colors - perfect for status indicators and tags +

+
+ `, + }), + args: { + rounded: "full", + size: "md", + badge: true, + }, + parameters: { + docs: { + description: { + story: "Multiple badge color examples with labels.", + }, + }, + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step("Verify badge with labels variants story renders correctly", async () => { + // Verify the story renders without errors + const title = canvas.getByText("IconButton Badge with Labels - Color Variants"); + expect(title).toBeInTheDocument(); + + // Verify we have some IconButton elements + const iconButtons = canvas.getAllByRole("generic"); + expect(iconButtons.length).toBeGreaterThan(0); + }); + }, +}; diff --git a/src/elements/IconButton.vue b/src/elements/IconButton.vue index 75b5158..a713631 100644 --- a/src/elements/IconButton.vue +++ b/src/elements/IconButton.vue @@ -21,6 +21,9 @@ computedColor, computedRounded, + + // Badge mode with label adjustments + props.badge && props.label ? 'px-3 py-2 gap-2' : '', ]" :disabled="disabled || cardDisabled || isLoading" > @@ -45,6 +48,14 @@ :class="[computedSize]" /> + + + + {{ props.label }} + @@ -78,6 +89,10 @@ interface IconButtonProps { * Enable badge mode - removes click functionality and pointer cursor */ badge?: boolean; + /** + * Label text to display when in badge mode + */ + label?: string; } const cardDisabled = inject("cardDisabled", false); @@ -151,6 +166,17 @@ const computedSize = computed(() => { return sizes[props.size || "lg"]; }); +const computedLabelSize = computed(() => { + const labelSizes = { + xs: "text-xs", + sm: "text-sm", + md: "text-base", + lg: "text-lg", + xl: "text-xl", + }; + return labelSizes[props.size || "lg"]; +}); + const onClick = () => { if (isClickable.value) { emit("click"); From e5dce7143397135cc04d4e088edc7ce25f1833a5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 1 Sep 2025 12:29:14 +0000 Subject: [PATCH 02/16] chore(release): 1.17.0-dev.36 [skip ci] # [1.17.0-dev.36](https://github.com/codebridger/lib-vue-components/compare/dev-1.17.0-dev.35...dev-1.17.0-dev.36) (2025-09-01) ### Features * Add badge label support to IconButton component ([5084bce](https://github.com/codebridger/lib-vue-components/commit/5084bce5f7048beacbc1efee050864467b958155)) --- CHANGELOG-DEV.md | 7 +++++++ package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index d119fd9..094daf8 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -1,3 +1,10 @@ +# [1.17.0-dev.36](https://github.com/codebridger/lib-vue-components/compare/dev-1.17.0-dev.35...dev-1.17.0-dev.36) (2025-09-01) + + +### Features + +* Add badge label support to IconButton component ([5084bce](https://github.com/codebridger/lib-vue-components/commit/5084bce5f7048beacbc1efee050864467b958155)) + # [1.17.0-dev.35](https://github.com/codebridger/lib-vue-components/compare/dev-1.17.0-dev.34...dev-1.17.0-dev.35) (2025-08-30) diff --git a/package.json b/package.json index 47f7cf3..12d2d16 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "publishConfig": { "registry": "https://npm.pkg.github.com" }, - "version": "1.17.0-dev.35", + "version": "1.17.0-dev.36", "type": "module", "files": [ "dist", @@ -127,4 +127,4 @@ "vitest": "^3.2.4", "vue-tsc": "^0.40.4" } -} \ No newline at end of file +} From 41ccd326aeed2fbce0cb58853d21ffc66385d470 Mon Sep 17 00:00:00 2001 From: Navid Shad Date: Mon, 1 Sep 2025 16:47:59 +0400 Subject: [PATCH 03/16] feat: #86eunvbgj Add InputGroup component with Storybook documentation - Introduced a new InputGroup component that allows for flexible input layouts with optional left and right addons. - Implemented the component's template and logic to support various input types and sizes. - Created comprehensive Storybook stories to demonstrate the InputGroup's features, including different states, sizes, and usage with other input components. - Added accessibility features and documentation for the InputGroup component. --- src/form/InputGroup.stories.ts | 479 +++++++++++++++++++++++++++++++++ src/form/InputGroup.vue | 99 +++++++ src/form/components.ts | 2 + 3 files changed, 580 insertions(+) create mode 100644 src/form/InputGroup.stories.ts create mode 100644 src/form/InputGroup.vue diff --git a/src/form/InputGroup.stories.ts b/src/form/InputGroup.stories.ts new file mode 100644 index 0000000..45621fc --- /dev/null +++ b/src/form/InputGroup.stories.ts @@ -0,0 +1,479 @@ +import type { Meta, StoryObj } from "@storybook/vue3"; +import { expect, within, userEvent } from "@storybook/test"; +import InputGroup from "./InputGroup.vue"; +import Input from "./Input.vue"; +import TextArea from "./TextArea.vue"; +import Select from "./Select.vue"; +import Button from "../elements/Button.vue"; +import { ref } from "vue"; +import Icon from "../icon/Icon.vue"; + +const componentDocs = ` +# InputGroup Component + +A flexible wrapper component that allows you to add content before and after any input field. This component provides a consistent layout for input groups with addons. + +## Features +- Left and right addons via named slots +- Works with any input component (Input, TextArea, Select, etc.) +- Multiple size variants (sm, md, lg) +- Theme support (light/dark) +- RTL support +- Form validation states +- Accessible with proper ARIA attributes + +## Usage +The InputGroup component uses slots for layout: +- \`leftAddon\`: Content displayed before the input +- \`rightAddon\`: Content displayed after the input +- \`default\`: The main input component + +## Accessibility +- Proper label association with input IDs +- Keyboard navigation support +- Screen reader friendly with appropriate ARIA attributes +- High contrast support in both light and dark themes +`; + +const meta = { + title: "Form/InputGroup", + component: InputGroup, + tags: ["autodocs"], + argTypes: { + label: { + control: "text", + description: "Label of input group", + }, + required: { + control: "boolean", + description: "Required state", + }, + error: { + control: "boolean", + description: "Error state", + }, + errorMessage: { + control: "text", + description: "Error message text", + }, + size: { + control: "select", + options: ["sm", "md", "lg"], + description: "Size of the input group", + }, + id: { + control: "text", + description: "Input ID attribute for label association", + }, + }, + args: { + label: "Input Group", + required: false, + error: false, + errorMessage: "", + size: "md", + id: "", + }, + parameters: { + docs: { + description: { + component: componentDocs, + }, + source: { + type: "code", + }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// Basic stories +export const Default: Story = { + render: (args) => ({ + components: { InputGroup, Input }, + setup() { + return { args }; + }, + template: ` + + + + + + `, + }), + parameters: { + docs: { + source: { + type: "code", + }, + }, + }, +}; + +export const TextAddons: Story = { + render: (args) => ({ + components: { InputGroup, Input }, + setup() { + return { args }; + }, + template: ` +
+ + + + + + + + + + + + + + + + + + + + +
+ `, + }), + parameters: { + docs: { + source: { + type: "code", + }, + }, + }, +}; + +export const IconAddons: Story = { + render: (args) => ({ + components: { InputGroup, Input, Icon }, + setup() { + return { args }; + }, + template: ` +
+ + + + + + + + + + + + + + + + +
+ `, + }), + parameters: { + docs: { + source: { + type: "code", + }, + }, + }, +}; + +export const ButtonAddons: Story = { + render: (args) => ({ + components: { InputGroup, Input, Button }, + setup() { + return { args }; + }, + template: ` +
+ + + + + + + + + + + + + + + +
+ `, + }), + parameters: { + docs: { + source: { + type: "code", + }, + }, + }, +}; + +export const Sizes: Story = { + render: (args) => ({ + components: { InputGroup, Input }, + setup() { + return { args }; + }, + template: ` +
+ + + + + + + + + + + + + + + + + +
+ `, + }), + parameters: { + docs: { + source: { + type: "code", + }, + }, + }, +}; + +export const States: Story = { + render: (args) => ({ + components: { InputGroup, Input }, + setup() { + return { args }; + }, + template: ` +
+ + + + + + + + + + + + + + + + + + + +
+ `, + }), + parameters: { + docs: { + source: { + type: "code", + }, + }, + }, +}; + +export const Interactive: Story = { + render: (args) => ({ + components: { InputGroup, Input }, + setup() { + const value = ref(""); + const handleChange = (newValue: string) => { + value.value = newValue; + }; + return { args, value, handleChange }; + }, + template: ` +
+ + + + + + +
+ Current value: {{ value || '(empty)' }} +
+
+ `, + }), + parameters: { + docs: { + source: { + type: "code", + }, + }, + }, +}; + +export const WithTextArea: Story = { + render: (args) => ({ + components: { InputGroup, TextArea }, + setup() { + return { args }; + }, + template: ` +
+ + +