@snapdragonsnursery/react-components 1.11.0 → 1.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapdragonsnursery/react-components",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -247,28 +247,7 @@ export function DateRangePicker({
247
247
  Done
248
248
  </Button>
249
249
  </div>
250
- {internalRange?.from && (
251
- <div className="absolute top-2 right-2 flex gap-1">
252
- <button
253
- onClick={() => {
254
- setInternalRange(null)
255
- isSelectingRange.current = false
256
- onSelect(null)
257
- }}
258
- className="text-xs text-muted-foreground hover:text-foreground bg-background px-2 py-1 rounded border"
259
- >
260
- Clear
261
- </button>
262
- {!internalRange?.to && (
263
- <button
264
- onClick={() => setIsOpen(false)}
265
- className="text-xs text-muted-foreground hover:text-foreground bg-background px-2 py-1 rounded border"
266
- >
267
- Cancel
268
- </button>
269
- )}
270
- </div>
271
- )}
250
+ {/* Removed top-right overlay controls to avoid overlap with calendar navigation */}
272
251
  </div>
273
252
  </PopoverContent>
274
253
  </Popover>
@@ -0,0 +1,126 @@
1
+ // FloatingActionButton.jsx
2
+ // ---------------------------------
3
+ // A shadcn-styled Floating Action Button (FAB).
4
+ // - Single fluid button that contains both icon and (optional) label
5
+ // - Label reveal is controlled by showLabel: 'hover' | 'always' | 'never'
6
+ // - Subtle icon and padding animation on hover when showLabel='hover'
7
+ // - Supports fixed positioning to screen corners
8
+ //
9
+ // Example usage:
10
+ //
11
+ // import { FloatingActionButton } from "@snapdragonsnursery/react-components";
12
+ // import { Plus } from "lucide-react";
13
+ //
14
+ // export default function Example() {
15
+ // return (
16
+ // <FloatingActionButton
17
+ // icon={Plus}
18
+ // label="Create"
19
+ // onClick={() => console.log("Create clicked")}
20
+ // showLabel="hover"
21
+ // position="bottom-right"
22
+ // />
23
+ // );
24
+ // }
25
+ //
26
+ // Notes:
27
+ // - Provide ariaLabel if label is omitted.
28
+ // - Uses Tailwind CSS + shadcn design tokens (bg-primary, text-primary-foreground, etc.).
29
+
30
+ import * as React from "react";
31
+ import { cn } from "../../lib/utils";
32
+ import { Button } from "./button.jsx";
33
+
34
+ const POSITION_CLASSES = {
35
+ "bottom-right": "fixed bottom-5 right-5",
36
+ "bottom-left": "fixed bottom-5 left-5",
37
+ "top-right": "fixed top-5 right-5",
38
+ "top-left": "fixed top-5 left-5",
39
+ none: "",
40
+ };
41
+
42
+ function FloatingActionButton({
43
+ icon: Icon,
44
+ label = "",
45
+ onClick,
46
+ showLabel,
47
+ // Deprecated: expandOnHover kept for backwards compatibility. If provided and showLabel is undefined,
48
+ // we map: true -> 'hover', false -> 'never'.
49
+ expandOnHover,
50
+ position = "bottom-right",
51
+ className,
52
+ disabled = false,
53
+ ariaLabel,
54
+ positionClassName,
55
+ colorClassName,
56
+ ...props
57
+ }) {
58
+ const resolvedShowLabel = React.useMemo(() => {
59
+ if (showLabel) return showLabel;
60
+ if (typeof expandOnHover === "boolean") {
61
+ return expandOnHover ? "hover" : "never";
62
+ }
63
+ return label ? "hover" : "never";
64
+ }, [showLabel, expandOnHover, label]);
65
+
66
+ const containerClasses = cn(
67
+ positionClassName || (POSITION_CLASSES[position ?? "none"] ?? POSITION_CLASSES.none),
68
+ className
69
+ );
70
+
71
+ const baseButton = cn(
72
+ "rounded-full shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 leading-none",
73
+ colorClassName || "bg-primary hover:bg-primary/90 text-primary-foreground"
74
+ );
75
+
76
+ const shapeClasses =
77
+ resolvedShowLabel === "never"
78
+ ? "h-14 w-14 inline-flex items-center justify-center gap-0 p-0"
79
+ : resolvedShowLabel === "always"
80
+ ? "h-14 inline-flex items-center justify-center gap-0 px-4"
81
+ : // hover mode
82
+ "group h-14 inline-flex items-center gap-0 justify-center min-w-[3.5rem] px-0 transition-all duration-300 ease-out hover:px-4";
83
+
84
+ const buttonClasses = cn(baseButton, shapeClasses);
85
+
86
+ const iconClasses = cn(
87
+ "h-6 w-6 block shrink-0 pointer-events-none transition-transform duration-200 ease-out",
88
+ resolvedShowLabel === "hover" ? "group-hover:scale-110 group-hover:rotate-3" : ""
89
+ );
90
+
91
+ const labelClasses = cn(
92
+ "leading-none whitespace-nowrap",
93
+ resolvedShowLabel === "always"
94
+ ? "ml-2"
95
+ : resolvedShowLabel === "hover"
96
+ ? "max-w-0 overflow-hidden opacity-0 scale-95 group-hover:max-w-[14rem] group-hover:opacity-100 group-hover:scale-100 group-hover:ml-2 transition-all duration-300 ease-out"
97
+ : "sr-only"
98
+ );
99
+
100
+ return (
101
+ <div className={containerClasses} data-testid="fab-container">
102
+ <Button
103
+ type="button"
104
+ data-slot="floating-action-button"
105
+ className={buttonClasses}
106
+ size={resolvedShowLabel === "never" ? "icon" : undefined}
107
+ aria-label={ariaLabel || label || "Action"}
108
+ title={ariaLabel || label || "Action"}
109
+ onClick={onClick}
110
+ disabled={disabled}
111
+ {...props}
112
+ >
113
+ {Icon ? <Icon className={iconClasses} aria-hidden="true" /> : null}
114
+ {label ? (
115
+ <span className={labelClasses} data-testid={resolvedShowLabel !== "never" ? "fab-label" : undefined}>
116
+ {label}
117
+ </span>
118
+ ) : null}
119
+ </Button>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ export default FloatingActionButton;
125
+
126
+
@@ -0,0 +1,46 @@
1
+ // Basic tests for FloatingActionButton
2
+ // - Renders icon and label behaviour depending on expandOnHover
3
+
4
+ import React from 'react'
5
+ import { render, screen, fireEvent } from '@testing-library/react'
6
+ import FloatingActionButton from './floating-action-button'
7
+
8
+ function DummyIcon(props) {
9
+ return <svg aria-hidden="true" data-testid="dummy-icon" {...props} />
10
+ }
11
+
12
+ describe('FloatingActionButton (library)', () => {
13
+ it('renders with icon', () => {
14
+ render(<FloatingActionButton icon={DummyIcon} ariaLabel="Action" />)
15
+ expect(screen.getByTestId('dummy-icon')).toBeInTheDocument()
16
+ })
17
+
18
+ it('expands label on hover when showLabel="hover"', () => {
19
+ render(<FloatingActionButton icon={DummyIcon} label="Create" showLabel="hover" />)
20
+ // Label exists in DOM (hidden initially via CSS)
21
+ const label = screen.getByTestId('fab-label')
22
+ expect(label).toBeInTheDocument()
23
+ // Simulate hover on container
24
+ const container = screen.getByTestId('fab-container')
25
+ fireEvent.mouseOver(container)
26
+ // Ensure label text is present
27
+ expect(label).toHaveTextContent('Create')
28
+ })
29
+
30
+ it('hides label visually when showLabel="never"', () => {
31
+ render(<FloatingActionButton icon={DummyIcon} label="Search" showLabel="never" ariaLabel="Search" />)
32
+ // No test id for label since not rendered
33
+ const button = screen.getByRole('button', { name: /search/i })
34
+ expect(button).toBeInTheDocument()
35
+ })
36
+
37
+ it('calls onClick when clicked', () => {
38
+ const onClick = jest.fn()
39
+ render(<FloatingActionButton icon={DummyIcon} ariaLabel="Action" onClick={onClick} />)
40
+ const button = screen.getByRole('button', { name: /action/i })
41
+ fireEvent.click(button)
42
+ expect(onClick).toHaveBeenCalledTimes(1)
43
+ })
44
+ })
45
+
46
+
package/src/index.d.ts CHANGED
@@ -116,6 +116,23 @@ export const BreadcrumbPage: React.ComponentType<any>
116
116
  export const BreadcrumbSeparator: React.ComponentType<any>
117
117
  export const BreadcrumbEllipsis: React.ComponentType<any>
118
118
 
119
+ // Floating Action Button
120
+ export interface FloatingActionButtonProps {
121
+ icon?: React.ComponentType<any>
122
+ label?: string
123
+ onClick?: () => void
124
+ showLabel?: 'hover' | 'always' | 'never'
125
+ // @deprecated use showLabel instead. If provided: true => 'hover', false => 'never'
126
+ expandOnHover?: boolean
127
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'none'
128
+ className?: string
129
+ positionClassName?: string
130
+ colorClassName?: string
131
+ disabled?: boolean
132
+ ariaLabel?: string
133
+ }
134
+ export const FloatingActionButton: React.ComponentType<FloatingActionButtonProps>
135
+
119
136
  // Switchers
120
137
  export interface SwitcherItem {
121
138
  id?: string | number
package/src/index.js CHANGED
@@ -74,3 +74,6 @@ export {
74
74
  BreadcrumbSeparator,
75
75
  BreadcrumbEllipsis,
76
76
  } from "./components/ui/breadcrumb.jsx";
77
+
78
+ // Floating Action Button
79
+ export { default as FloatingActionButton } from "./components/ui/floating-action-button.jsx";