@roy-ui/ui 0.0.2

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.
@@ -0,0 +1,74 @@
1
+ .gradient-btn {
2
+ position: relative;
3
+ display: inline-block;
4
+ padding: 13px 24px;
5
+ border-radius: 8px;
6
+ border: none;
7
+ background: linear-gradient(325deg, #0044ff 0%, #2ccfff 55%, #0044ff 90%);
8
+ background-size: 280% auto;
9
+ background-position: left center;
10
+ color: #ffffff;
11
+ font-size: 14px;
12
+ font-weight: 600;
13
+ letter-spacing: -0.005em;
14
+ cursor: pointer;
15
+ transition:
16
+ background-position 0.7s ease,
17
+ transform 0.2s ease,
18
+ box-shadow 0.25s ease;
19
+ box-shadow:
20
+ 0 0 20px rgba(71, 184, 255, 0.45),
21
+ 0 5px 12px -2px rgba(58, 125, 233, 0.3),
22
+ inset 4px 4px 8px rgba(175, 230, 255, 0.4),
23
+ inset -4px -4px 8px rgba(19, 95, 216, 0.35);
24
+ }
25
+
26
+ .gradient-btn--full {
27
+ width: 100%;
28
+ }
29
+
30
+ .gradient-btn:hover:not(:disabled) {
31
+ background-position: right top;
32
+ transform: translateY(-1px);
33
+ box-shadow:
34
+ 0 0 28px rgba(71, 184, 255, 0.6),
35
+ 0 6px 14px -2px rgba(58, 125, 233, 0.4),
36
+ inset 4px 4px 8px rgba(175, 230, 255, 0.45),
37
+ inset -4px -4px 8px rgba(19, 95, 216, 0.4);
38
+ }
39
+
40
+ .gradient-btn:active:not(:disabled) {
41
+ transform: translateY(0);
42
+ }
43
+
44
+ .gradient-btn:focus-visible {
45
+ outline: 2px solid #2ccfff;
46
+ outline-offset: 2px;
47
+ }
48
+
49
+ .gradient-btn:disabled {
50
+ opacity: 0.7;
51
+ cursor: not-allowed;
52
+ }
53
+
54
+ .gradient-btn--loading {
55
+ cursor: progress;
56
+ }
57
+
58
+ .gradient-btn__spinner {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ line-height: 0;
63
+ }
64
+
65
+ .gradient-btn__spinner svg {
66
+ animation: gradient-btn__spin 0.75s linear infinite;
67
+ color: #ffffff;
68
+ }
69
+
70
+ @keyframes gradient-btn__spin {
71
+ to {
72
+ transform: rotate(360deg);
73
+ }
74
+ }
@@ -0,0 +1,116 @@
1
+ .royui-madeby {
2
+ /* Theme surface — override on .royui-madeby or :root for global retheme. */
3
+ --royui-madeby-bg: rgba(12, 12, 14, 0.86);
4
+ --royui-madeby-bg-hover: rgba(16, 16, 19, 0.92);
5
+ --royui-madeby-border: rgba(255, 255, 255, 0.08);
6
+ --royui-madeby-border-hover: rgba(255, 255, 255, 0.18);
7
+ --royui-madeby-prefix: rgba(255, 255, 255, 0.5);
8
+ --royui-madeby-name: rgba(255, 255, 255, 0.96);
9
+ --royui-madeby-name-hover: #4ec6ff;
10
+ --royui-madeby-shadow:
11
+ 0 1px 2px rgba(0, 0, 0, 0.18),
12
+ 0 6px 16px -2px rgba(0, 0, 0, 0.28);
13
+ --royui-madeby-shadow-hover:
14
+ 0 1px 2px rgba(0, 0, 0, 0.22),
15
+ 0 12px 26px -4px rgba(0, 0, 0, 0.34);
16
+ --royui-madeby-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
17
+
18
+ /* Author-name typography (overridable). */
19
+ --royui-madeby-name-font: inherit;
20
+ --royui-madeby-name-weight: 500;
21
+
22
+ position: fixed;
23
+ z-index: 100;
24
+ display: inline-flex;
25
+ align-items: center;
26
+ gap: 5px;
27
+ padding: 7px 13px;
28
+ background: var(--royui-madeby-bg);
29
+ backdrop-filter: saturate(180%) blur(14px);
30
+ -webkit-backdrop-filter: saturate(180%) blur(14px);
31
+ border: 1px solid var(--royui-madeby-border);
32
+ border-radius: 999px;
33
+ font-size: 12.5px;
34
+ line-height: 1.4;
35
+ letter-spacing: -0.005em;
36
+ text-decoration: none;
37
+ color: var(--royui-madeby-prefix);
38
+ box-shadow: var(--royui-madeby-shadow);
39
+ transition:
40
+ border-color 0.2s var(--royui-madeby-ease),
41
+ background 0.2s var(--royui-madeby-ease),
42
+ box-shadow 0.25s var(--royui-madeby-ease),
43
+ transform 0.2s var(--royui-madeby-ease);
44
+ user-select: none;
45
+ white-space: nowrap;
46
+ cursor: pointer;
47
+ }
48
+
49
+ .royui-madeby:hover {
50
+ background: var(--royui-madeby-bg-hover);
51
+ border-color: var(--royui-madeby-border-hover);
52
+ box-shadow: var(--royui-madeby-shadow-hover);
53
+ transform: translateY(-1px);
54
+ }
55
+
56
+ .royui-madeby:hover .royui-madeby__name {
57
+ color: var(--royui-madeby-name-hover);
58
+ }
59
+
60
+ .royui-madeby:focus-visible {
61
+ outline: 2px solid var(--royui-madeby-name-hover);
62
+ outline-offset: 3px;
63
+ }
64
+
65
+ .royui-madeby:active {
66
+ transform: translateY(0);
67
+ box-shadow: var(--royui-madeby-shadow);
68
+ }
69
+
70
+ .royui-madeby__icon {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ margin-right: 2px;
75
+ line-height: 0;
76
+ }
77
+
78
+ .royui-madeby__prefix {
79
+ color: var(--royui-madeby-prefix);
80
+ font-weight: 400;
81
+ }
82
+
83
+ .royui-madeby__name {
84
+ color: var(--royui-madeby-name);
85
+ font-weight: var(--royui-madeby-name-weight);
86
+ font-family: var(--royui-madeby-name-font);
87
+ font-style: italic;
88
+ transition: color 0.18s var(--royui-madeby-ease);
89
+ }
90
+
91
+ /* Positions */
92
+ .royui-madeby--bottom-right { bottom: 18px; right: 18px; }
93
+ .royui-madeby--bottom-left { bottom: 18px; left: 18px; }
94
+ .royui-madeby--top-right { top: 18px; right: 18px; }
95
+ .royui-madeby--top-left { top: 18px; left: 18px; }
96
+
97
+ @media (max-width: 540px) {
98
+ .royui-madeby {
99
+ padding: 6px 12px;
100
+ font-size: 12px;
101
+ }
102
+ .royui-madeby--bottom-right,
103
+ .royui-madeby--bottom-left { bottom: 12px; }
104
+ .royui-madeby--bottom-right { right: 12px; }
105
+ .royui-madeby--bottom-left { left: 12px; }
106
+ .royui-madeby--top-right,
107
+ .royui-madeby--top-left { top: 12px; }
108
+ .royui-madeby--top-right { right: 12px; }
109
+ .royui-madeby--top-left { left: 12px; }
110
+ }
111
+
112
+ @media (prefers-reduced-motion: reduce) {
113
+ .royui-madeby,
114
+ .royui-madeby__name { transition: none; }
115
+ .royui-madeby:hover { transform: none; }
116
+ }
@@ -0,0 +1,136 @@
1
+ .royui-popover {
2
+ /* Theming surface — override on .royui-popover (or :root) to retheme. */
3
+ --royui-popover-bg: #ffffff;
4
+ --royui-popover-title: rgb(20, 20, 20);
5
+ --royui-popover-body: rgb(58, 58, 56);
6
+ --royui-popover-trigger-idle: rgb(142, 139, 131);
7
+ --royui-popover-trigger-hover: rgb(58, 58, 56);
8
+ --royui-popover-trigger-bg: rgb(244, 243, 238);
9
+ --royui-popover-trigger-active: rgb(20, 20, 20);
10
+ --royui-popover-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
11
+ --royui-popover-dur: 180ms;
12
+ --royui-popover-radius: 12px;
13
+ /* Unified shadow stack — also used to draw a hairline ring around the
14
+ entire popover silhouette (panel + tail) via filter: drop-shadow. */
15
+ --royui-popover-ring: 0 0 0 0.5px rgba(20, 20, 20, 0.08);
16
+ --royui-popover-shadow-near: 0 1px 2px rgba(0, 0, 0, 0.05);
17
+ --royui-popover-shadow-mid: 0 6px 14px rgba(0, 0, 0, 0.08);
18
+ --royui-popover-shadow-far: 0 22px 44px rgba(0, 0, 0, 0.14);
19
+
20
+ position: relative;
21
+ display: inline-block;
22
+ }
23
+
24
+ /* ── Default `i` trigger ───────────────────────────────── */
25
+
26
+ .royui-popover__trigger {
27
+ display: inline-flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ width: 20px;
31
+ height: 20px;
32
+ padding: 0;
33
+ border: 0;
34
+ border-radius: 999px;
35
+ background: transparent;
36
+ color: var(--royui-popover-trigger-idle);
37
+ cursor: pointer;
38
+ transition:
39
+ color 150ms var(--royui-popover-ease),
40
+ background 150ms var(--royui-popover-ease);
41
+ }
42
+
43
+ .royui-popover__trigger:hover {
44
+ color: var(--royui-popover-trigger-hover);
45
+ background: var(--royui-popover-trigger-bg);
46
+ }
47
+
48
+ .royui-popover__trigger:focus-visible {
49
+ outline: 2px solid var(--royui-popover-trigger-active);
50
+ outline-offset: 2px;
51
+ }
52
+
53
+ .royui-popover__trigger--on {
54
+ color: var(--royui-popover-trigger-active);
55
+ background: var(--royui-popover-trigger-bg);
56
+ }
57
+
58
+ /* ── Panel ─────────────────────────────────────────────── */
59
+
60
+ .royui-popover__panel {
61
+ position: absolute;
62
+ z-index: 30;
63
+ top: calc(100% + 8px);
64
+ background: var(--royui-popover-bg);
65
+ border-radius: var(--royui-popover-radius);
66
+ padding: 14px 16px;
67
+ font-size: 14px;
68
+ line-height: 1.55;
69
+ color: var(--royui-popover-body);
70
+ /* drop-shadow follows the combined silhouette of panel + ::before tail,
71
+ so the hairline ring and shadow wrap around the tail as one shape — no
72
+ visible seam where they meet. */
73
+ filter:
74
+ drop-shadow(0 0 0.5px rgba(20, 20, 20, 0.18))
75
+ drop-shadow(0 2px 4px rgba(0, 0, 0, 0.05))
76
+ drop-shadow(0 14px 36px rgba(0, 0, 0, 0.16));
77
+ animation: royui-popover-in var(--royui-popover-dur) var(--royui-popover-ease);
78
+ }
79
+
80
+ .royui-popover__panel--sm { width: 280px; }
81
+ .royui-popover__panel--md { width: 360px; }
82
+ .royui-popover__panel--lg { width: 420px; }
83
+
84
+ .royui-popover__panel--right { left: auto; right: 0; }
85
+ .royui-popover__panel--left { right: auto; left: 0; }
86
+
87
+ /* Tail — pure CSS triangle (no rotated square, no border-meets-border seam).
88
+ Its base extends 2px INSIDE the panel so the panel's bg seamlessly overlaps
89
+ the triangle's base, and the drop-shadow filter wraps panel + tail as one
90
+ merged silhouette. Positioned 14px from the corner — flush at the edge. */
91
+ .royui-popover__panel::before {
92
+ content: '';
93
+ position: absolute;
94
+ top: -7px;
95
+ width: 0;
96
+ height: 0;
97
+ border-left: 7px solid transparent;
98
+ border-right: 7px solid transparent;
99
+ border-bottom: 9px solid var(--royui-popover-bg);
100
+ }
101
+ .royui-popover__panel--right::before { right: 14px; }
102
+ .royui-popover__panel--left::before { left: 14px; }
103
+
104
+ .royui-popover__title {
105
+ font-size: 14px;
106
+ font-weight: 600;
107
+ letter-spacing: -0.005em;
108
+ color: var(--royui-popover-title);
109
+ margin-bottom: 4px;
110
+ }
111
+
112
+ .royui-popover__body {
113
+ font-size: 14px;
114
+ line-height: 1.55;
115
+ color: var(--royui-popover-body);
116
+ }
117
+
118
+ .royui-popover__body strong {
119
+ color: var(--royui-popover-title);
120
+ font-weight: 600;
121
+ }
122
+
123
+ @keyframes royui-popover-in {
124
+ from {
125
+ opacity: 0;
126
+ transform: translateY(-4px) scale(0.98);
127
+ }
128
+ to {
129
+ opacity: 1;
130
+ transform: none;
131
+ }
132
+ }
133
+
134
+ @media (prefers-reduced-motion: reduce) {
135
+ .royui-popover__panel { animation: none; }
136
+ }
@@ -0,0 +1,6 @@
1
+ .royui-textmorph {
2
+ display: inline-block;
3
+ white-space: pre;
4
+ /* Preserve spaces during typing so intermediate strings like
5
+ "pnpm @royui/ui" (mid-deletion) render their gap correctly. */
6
+ }
@@ -0,0 +1,70 @@
1
+ import * as react from 'react';
2
+ import { ButtonHTMLAttributes, ReactNode, AnchorHTMLAttributes, CSSProperties, HTMLAttributes } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+
5
+ interface GradientButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
6
+ loading?: boolean;
7
+ loadingLabel?: ReactNode;
8
+ fullWidth?: boolean;
9
+ children: ReactNode;
10
+ }
11
+ declare const GradientButton: react.ForwardRefExoticComponent<GradientButtonProps & react.RefAttributes<HTMLButtonElement>>;
12
+
13
+ interface PopoverProps {
14
+ children: ReactNode;
15
+ title?: string;
16
+ align?: 'left' | 'right';
17
+ width?: 'sm' | 'md' | 'lg' | number;
18
+ label?: string;
19
+ defaultOpen?: boolean;
20
+ renderTrigger?: (api: {
21
+ isOpen: boolean;
22
+ toggle: () => void;
23
+ }) => ReactNode;
24
+ }
25
+ declare function Popover({ children, title, align, width, label, defaultOpen, renderTrigger, }: PopoverProps): react_jsx_runtime.JSX.Element;
26
+
27
+ type MadeByPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
28
+ interface MadeByProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'prefix'> {
29
+ /** Display name shown after the prefix. */
30
+ name: string;
31
+ /** URL opened when the badge is clicked. */
32
+ href: string;
33
+ /** Prefix copy. Defaults to "Made by". */
34
+ prefix?: ReactNode;
35
+ /** Viewport anchor. Defaults to "bottom-right". */
36
+ position?: MadeByPosition;
37
+ /** Optional leading slot — avatar, mark, emoji, dot. */
38
+ icon?: ReactNode;
39
+ /** Custom font-family for the author name. */
40
+ nameFont?: string;
41
+ /** Font style for the author name. Defaults to "italic". */
42
+ nameStyle?: CSSProperties['fontStyle'];
43
+ }
44
+ declare const MadeBy: react.ForwardRefExoticComponent<MadeByProps & react.RefAttributes<HTMLAnchorElement>>;
45
+
46
+ interface TextMorphProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
47
+ /** The current text. When this prop changes, the component diff-types
48
+ from the previously displayed text to the new value. */
49
+ value: string;
50
+ /** Optional renderer for the current intermediate text — handy for
51
+ syntax highlighting, gradient spans, or wrapping each word. Receives
52
+ the partial string at every keystroke during the animation. */
53
+ renderText?: (current: string) => ReactNode;
54
+ /** Per-character typing delay range in ms. Default [30, 60]. */
55
+ typeDelay?: [min: number, max: number];
56
+ /** Per-character backspace delay range in ms. Default [18, 30]. */
57
+ backspaceDelay?: [min: number, max: number];
58
+ /** Characters that get an additional delay (harder to type on a real
59
+ keyboard — punctuation, brackets, symbols). Default /[\/{}\-_@]/. */
60
+ hardChars?: RegExp;
61
+ /** Extra delay range for hard chars in ms. Default [30, 65]. */
62
+ hardCharExtraDelay?: [min: number, max: number];
63
+ /** Pause between backspace phase and typing phase, in ms. Default 70. */
64
+ pauseMs?: number;
65
+ /** Skip animation entirely and just swap text. */
66
+ disabled?: boolean;
67
+ }
68
+ declare const TextMorph: react.ForwardRefExoticComponent<TextMorphProps & react.RefAttributes<HTMLSpanElement>>;
69
+
70
+ export { GradientButton, type GradientButtonProps, MadeBy, type MadeByPosition, type MadeByProps, Popover, type PopoverProps, TextMorph, type TextMorphProps };
package/dist/index.js ADDED
@@ -0,0 +1,289 @@
1
+ "use client";
2
+ import { forwardRef, useState, useRef, useEffect } from 'react';
3
+ import './GradientButton-TX2GJRIQ.css';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+ import './Popover-LSYVKT4M.css';
6
+ import './MadeBy-JCYGHWSD.css';
7
+ import './TextMorph-RX2BX25F.css';
8
+
9
+ // src/components/gradient-button/GradientButton.tsx
10
+ var DefaultSpinner = () => /* @__PURE__ */ jsx("span", { className: "gradient-btn__spinner", "aria-hidden": "true", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", children: [
11
+ /* @__PURE__ */ jsx(
12
+ "circle",
13
+ {
14
+ cx: "12",
15
+ cy: "12",
16
+ r: "9",
17
+ stroke: "currentColor",
18
+ strokeOpacity: "0.3",
19
+ strokeWidth: "2.5"
20
+ }
21
+ ),
22
+ /* @__PURE__ */ jsx(
23
+ "path",
24
+ {
25
+ d: "M21 12a9 9 0 0 0-9-9",
26
+ stroke: "currentColor",
27
+ strokeWidth: "2.5",
28
+ strokeLinecap: "round"
29
+ }
30
+ )
31
+ ] }) });
32
+ var GradientButton = forwardRef(
33
+ ({
34
+ loading = false,
35
+ loadingLabel,
36
+ fullWidth = true,
37
+ disabled,
38
+ className = "",
39
+ children,
40
+ type = "button",
41
+ ...rest
42
+ }, ref) => {
43
+ const classes = [
44
+ "gradient-btn",
45
+ fullWidth ? "gradient-btn--full" : "",
46
+ loading ? "gradient-btn--loading" : "",
47
+ className
48
+ ].filter(Boolean).join(" ");
49
+ return /* @__PURE__ */ jsx(
50
+ "button",
51
+ {
52
+ ref,
53
+ type,
54
+ disabled: disabled || loading,
55
+ className: classes,
56
+ "aria-busy": loading || void 0,
57
+ ...rest,
58
+ children: loading ? loadingLabel ?? /* @__PURE__ */ jsx(DefaultSpinner, {}) : children
59
+ }
60
+ );
61
+ }
62
+ );
63
+ GradientButton.displayName = "GradientButton";
64
+ var InfoIcon = () => /* @__PURE__ */ jsxs(
65
+ "svg",
66
+ {
67
+ width: "14",
68
+ height: "14",
69
+ viewBox: "0 0 24 24",
70
+ fill: "none",
71
+ stroke: "currentColor",
72
+ strokeWidth: "1.5",
73
+ strokeLinecap: "round",
74
+ strokeLinejoin: "round",
75
+ "aria-hidden": true,
76
+ children: [
77
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
78
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-5M12 8h.01" })
79
+ ]
80
+ }
81
+ );
82
+ function Popover({
83
+ children,
84
+ title,
85
+ align = "right",
86
+ width = "md",
87
+ label = "Open menu",
88
+ defaultOpen = false,
89
+ renderTrigger
90
+ }) {
91
+ const [open, setOpen] = useState(defaultOpen);
92
+ const wrap = useRef(null);
93
+ const toggle = () => setOpen((o) => !o);
94
+ useEffect(() => {
95
+ if (!open) return;
96
+ function onDown(e) {
97
+ if (wrap.current && !wrap.current.contains(e.target)) {
98
+ setOpen(false);
99
+ }
100
+ }
101
+ function onKey(e) {
102
+ if (e.key === "Escape") setOpen(false);
103
+ }
104
+ document.addEventListener("mousedown", onDown);
105
+ document.addEventListener("keydown", onKey);
106
+ return () => {
107
+ document.removeEventListener("mousedown", onDown);
108
+ document.removeEventListener("keydown", onKey);
109
+ };
110
+ }, [open]);
111
+ const trigger = renderTrigger ? renderTrigger({ isOpen: open, toggle }) : /* @__PURE__ */ jsx(
112
+ "button",
113
+ {
114
+ type: "button",
115
+ onClick: toggle,
116
+ "aria-label": label,
117
+ "aria-expanded": open,
118
+ className: `royui-popover__trigger ${open ? "royui-popover__trigger--on" : ""}`,
119
+ children: /* @__PURE__ */ jsx(InfoIcon, {})
120
+ }
121
+ );
122
+ const widthClass = typeof width === "string" ? `royui-popover__panel--${width}` : "";
123
+ const customWidth = typeof width === "number" ? { width: `${width}px` } : void 0;
124
+ const alignClass = `royui-popover__panel--${align}`;
125
+ return /* @__PURE__ */ jsxs("div", { ref: wrap, className: "royui-popover", children: [
126
+ trigger,
127
+ open && /* @__PURE__ */ jsxs(
128
+ "div",
129
+ {
130
+ className: `royui-popover__panel ${widthClass} ${alignClass}`,
131
+ style: customWidth,
132
+ role: "dialog",
133
+ children: [
134
+ title && /* @__PURE__ */ jsx("div", { className: "royui-popover__title", children: title }),
135
+ /* @__PURE__ */ jsx("div", { className: "royui-popover__body", children })
136
+ ]
137
+ }
138
+ )
139
+ ] });
140
+ }
141
+ var MadeBy = forwardRef(
142
+ ({
143
+ name,
144
+ href,
145
+ prefix = "Made by",
146
+ position = "bottom-right",
147
+ icon,
148
+ nameFont,
149
+ nameStyle,
150
+ className = "",
151
+ target = "_blank",
152
+ rel = "noopener noreferrer",
153
+ ...rest
154
+ }, ref) => {
155
+ const classes = [
156
+ "royui-madeby",
157
+ `royui-madeby--${position}`,
158
+ className
159
+ ].filter(Boolean).join(" ");
160
+ const nameStyles = {};
161
+ if (nameFont) nameStyles.fontFamily = nameFont;
162
+ if (nameStyle) nameStyles.fontStyle = nameStyle;
163
+ return /* @__PURE__ */ jsxs(
164
+ "a",
165
+ {
166
+ ref,
167
+ href,
168
+ target,
169
+ rel,
170
+ className: classes,
171
+ ...rest,
172
+ children: [
173
+ icon && /* @__PURE__ */ jsx("span", { className: "royui-madeby__icon", "aria-hidden": true, children: icon }),
174
+ /* @__PURE__ */ jsx("span", { className: "royui-madeby__prefix", children: prefix }),
175
+ /* @__PURE__ */ jsx(
176
+ "span",
177
+ {
178
+ className: "royui-madeby__name",
179
+ style: Object.keys(nameStyles).length ? nameStyles : void 0,
180
+ children: name
181
+ }
182
+ )
183
+ ]
184
+ }
185
+ );
186
+ }
187
+ );
188
+ MadeBy.displayName = "MadeBy";
189
+ function sleep(ms) {
190
+ return new Promise((resolve) => setTimeout(resolve, ms));
191
+ }
192
+ function findDiff(from, to) {
193
+ let p = 0;
194
+ const maxP = Math.min(from.length, to.length);
195
+ while (p < maxP && from[p] === to[p]) p++;
196
+ let s = 0;
197
+ while (s < from.length - p && s < to.length - p && from[from.length - 1 - s] === to[to.length - 1 - s]) {
198
+ s++;
199
+ }
200
+ return {
201
+ prefix: from.slice(0, p),
202
+ suffix: from.slice(from.length - s),
203
+ oldMid: from.slice(p, from.length - s),
204
+ newMid: to.slice(p, to.length - s)
205
+ };
206
+ }
207
+ function rand(min, max) {
208
+ return min + Math.random() * (max - min);
209
+ }
210
+ var TextMorph = forwardRef(
211
+ function TextMorph2({
212
+ value,
213
+ renderText,
214
+ typeDelay = [30, 60],
215
+ backspaceDelay = [18, 30],
216
+ hardChars = /[\/{}\-_@]/,
217
+ hardCharExtraDelay = [30, 65],
218
+ pauseMs = 70,
219
+ disabled = false,
220
+ className = "",
221
+ ...rest
222
+ }, ref) {
223
+ const [displayed, setDisplayed] = useState(value);
224
+ const tokenRef = useRef(0);
225
+ const reducedRef = useRef(false);
226
+ const prevValueRef = useRef(value);
227
+ const displayedRef = useRef(value);
228
+ useEffect(() => {
229
+ if (typeof window !== "undefined") {
230
+ reducedRef.current = window.matchMedia(
231
+ "(prefers-reduced-motion: reduce)"
232
+ ).matches;
233
+ }
234
+ }, []);
235
+ useEffect(() => {
236
+ displayedRef.current = displayed;
237
+ }, [displayed]);
238
+ useEffect(() => {
239
+ if (value === prevValueRef.current) return;
240
+ const source = displayedRef.current;
241
+ prevValueRef.current = value;
242
+ if (disabled || reducedRef.current) {
243
+ setDisplayed(value);
244
+ return;
245
+ }
246
+ const myToken = ++tokenRef.current;
247
+ (async () => {
248
+ const { prefix, suffix, oldMid, newMid } = findDiff(source, value);
249
+ for (let i = oldMid.length - 1; i >= 0; i--) {
250
+ if (myToken !== tokenRef.current) return;
251
+ setDisplayed(prefix + oldMid.slice(0, i) + suffix);
252
+ await sleep(rand(backspaceDelay[0], backspaceDelay[1]));
253
+ }
254
+ if (myToken !== tokenRef.current) return;
255
+ await sleep(pauseMs);
256
+ for (let i = 1; i <= newMid.length; i++) {
257
+ if (myToken !== tokenRef.current) return;
258
+ setDisplayed(prefix + newMid.slice(0, i) + suffix);
259
+ const ch = newMid.charAt(i - 1);
260
+ const base = rand(typeDelay[0], typeDelay[1]);
261
+ const extra = hardChars.test(ch) ? rand(hardCharExtraDelay[0], hardCharExtraDelay[1]) : 0;
262
+ await sleep(base + extra);
263
+ }
264
+ })();
265
+ }, [
266
+ value,
267
+ disabled,
268
+ typeDelay,
269
+ backspaceDelay,
270
+ hardChars,
271
+ hardCharExtraDelay,
272
+ pauseMs
273
+ ]);
274
+ return /* @__PURE__ */ jsx(
275
+ "span",
276
+ {
277
+ ref,
278
+ className: `royui-textmorph ${className}`.trim(),
279
+ "aria-live": "polite",
280
+ ...rest,
281
+ children: renderText ? renderText(displayed) : displayed
282
+ }
283
+ );
284
+ }
285
+ );
286
+
287
+ export { GradientButton, MadeBy, Popover, TextMorph };
288
+ //# sourceMappingURL=index.js.map
289
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/gradient-button/GradientButton.tsx","../src/components/popover/Popover.tsx","../src/components/made-by/MadeBy.tsx","../src/components/text-morph/TextMorph.tsx"],"names":["jsxs","jsx","forwardRef","TextMorph","useState","useRef","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';\nimport './GradientButton.css';\n\nexport interface GradientButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n loading?: boolean;\n loadingLabel?: ReactNode;\n fullWidth?: boolean;\n children: ReactNode;\n}\n\nconst DefaultSpinner = () => (\n <span className=\"gradient-btn__spinner\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\">\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"9\"\n stroke=\"currentColor\"\n strokeOpacity=\"0.3\"\n strokeWidth=\"2.5\"\n />\n <path\n d=\"M21 12a9 9 0 0 0-9-9\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </span>\n);\n\nexport const GradientButton = forwardRef<HTMLButtonElement, GradientButtonProps>(\n (\n {\n loading = false,\n loadingLabel,\n fullWidth = true,\n disabled,\n className = '',\n children,\n type = 'button',\n ...rest\n },\n ref,\n ) => {\n const classes = [\n 'gradient-btn',\n fullWidth ? 'gradient-btn--full' : '',\n loading ? 'gradient-btn--loading' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <button\n ref={ref}\n type={type}\n disabled={disabled || loading}\n className={classes}\n aria-busy={loading || undefined}\n {...rest}\n >\n {loading ? (loadingLabel ?? <DefaultSpinner />) : children}\n </button>\n );\n },\n);\n\nGradientButton.displayName = 'GradientButton';\n","'use client';\n\nimport {\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport './Popover.css';\n\nexport interface PopoverProps {\n children: ReactNode;\n title?: string;\n align?: 'left' | 'right';\n width?: 'sm' | 'md' | 'lg' | number;\n label?: string;\n defaultOpen?: boolean;\n renderTrigger?: (api: { isOpen: boolean; toggle: () => void }) => ReactNode;\n}\n\nconst InfoIcon = () => (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 16v-5M12 8h.01\" />\n </svg>\n);\n\nexport function Popover({\n children,\n title,\n align = 'right',\n width = 'md',\n label = 'Open menu',\n defaultOpen = false,\n renderTrigger,\n}: PopoverProps) {\n const [open, setOpen] = useState(defaultOpen);\n const wrap = useRef<HTMLDivElement>(null);\n const toggle = () => setOpen((o) => !o);\n\n useEffect(() => {\n if (!open) return;\n function onDown(e: MouseEvent) {\n if (wrap.current && !wrap.current.contains(e.target as Node)) {\n setOpen(false);\n }\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDown);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDown);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n const trigger = renderTrigger ? (\n renderTrigger({ isOpen: open, toggle })\n ) : (\n <button\n type=\"button\"\n onClick={toggle}\n aria-label={label}\n aria-expanded={open}\n className={`royui-popover__trigger ${open ? 'royui-popover__trigger--on' : ''}`}\n >\n <InfoIcon />\n </button>\n );\n\n const widthClass =\n typeof width === 'string' ? `royui-popover__panel--${width}` : '';\n const customWidth =\n typeof width === 'number' ? { width: `${width}px` } : undefined;\n const alignClass = `royui-popover__panel--${align}`;\n\n return (\n <div ref={wrap} className=\"royui-popover\">\n {trigger}\n {open && (\n <div\n className={`royui-popover__panel ${widthClass} ${alignClass}`}\n style={customWidth}\n role=\"dialog\"\n >\n {title && <div className=\"royui-popover__title\">{title}</div>}\n <div className=\"royui-popover__body\">{children}</div>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport {\n forwardRef,\n type AnchorHTMLAttributes,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport './MadeBy.css';\n\nexport type MadeByPosition =\n | 'bottom-right'\n | 'bottom-left'\n | 'top-right'\n | 'top-left';\n\nexport interface MadeByProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'prefix'> {\n /** Display name shown after the prefix. */\n name: string;\n /** URL opened when the badge is clicked. */\n href: string;\n /** Prefix copy. Defaults to \"Made by\". */\n prefix?: ReactNode;\n /** Viewport anchor. Defaults to \"bottom-right\". */\n position?: MadeByPosition;\n /** Optional leading slot — avatar, mark, emoji, dot. */\n icon?: ReactNode;\n /** Custom font-family for the author name. */\n nameFont?: string;\n /** Font style for the author name. Defaults to \"italic\". */\n nameStyle?: CSSProperties['fontStyle'];\n}\n\nexport const MadeBy = forwardRef<HTMLAnchorElement, MadeByProps>(\n (\n {\n name,\n href,\n prefix = 'Made by',\n position = 'bottom-right',\n icon,\n nameFont,\n nameStyle,\n className = '',\n target = '_blank',\n rel = 'noopener noreferrer',\n ...rest\n },\n ref,\n ) => {\n const classes = [\n 'royui-madeby',\n `royui-madeby--${position}`,\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const nameStyles: CSSProperties = {};\n if (nameFont) nameStyles.fontFamily = nameFont;\n if (nameStyle) nameStyles.fontStyle = nameStyle;\n\n return (\n <a\n ref={ref}\n href={href}\n target={target}\n rel={rel}\n className={classes}\n {...rest}\n >\n {icon && (\n <span className=\"royui-madeby__icon\" aria-hidden>\n {icon}\n </span>\n )}\n <span className=\"royui-madeby__prefix\">{prefix}</span>\n <span\n className=\"royui-madeby__name\"\n style={Object.keys(nameStyles).length ? nameStyles : undefined}\n >\n {name}\n </span>\n </a>\n );\n },\n);\n\nMadeBy.displayName = 'MadeBy';\n","'use client';\n\nimport {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport './TextMorph.css';\n\nexport interface TextMorphProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {\n /** The current text. When this prop changes, the component diff-types\n from the previously displayed text to the new value. */\n value: string;\n /** Optional renderer for the current intermediate text — handy for\n syntax highlighting, gradient spans, or wrapping each word. Receives\n the partial string at every keystroke during the animation. */\n renderText?: (current: string) => ReactNode;\n /** Per-character typing delay range in ms. Default [30, 60]. */\n typeDelay?: [min: number, max: number];\n /** Per-character backspace delay range in ms. Default [18, 30]. */\n backspaceDelay?: [min: number, max: number];\n /** Characters that get an additional delay (harder to type on a real\n keyboard — punctuation, brackets, symbols). Default /[\\/{}\\-_@]/. */\n hardChars?: RegExp;\n /** Extra delay range for hard chars in ms. Default [30, 65]. */\n hardCharExtraDelay?: [min: number, max: number];\n /** Pause between backspace phase and typing phase, in ms. Default 70. */\n pauseMs?: number;\n /** Skip animation entirely and just swap text. */\n disabled?: boolean;\n}\n\nfunction sleep(ms: number) {\n return new Promise<void>((resolve) => setTimeout(resolve, ms));\n}\n\nfunction findDiff(from: string, to: string) {\n let p = 0;\n const maxP = Math.min(from.length, to.length);\n while (p < maxP && from[p] === to[p]) p++;\n let s = 0;\n while (\n s < from.length - p &&\n s < to.length - p &&\n from[from.length - 1 - s] === to[to.length - 1 - s]\n ) {\n s++;\n }\n return {\n prefix: from.slice(0, p),\n suffix: from.slice(from.length - s),\n oldMid: from.slice(p, from.length - s),\n newMid: to.slice(p, to.length - s),\n };\n}\n\nfunction rand(min: number, max: number) {\n return min + Math.random() * (max - min);\n}\n\nexport const TextMorph = forwardRef<HTMLSpanElement, TextMorphProps>(\n function TextMorph(\n {\n value,\n renderText,\n typeDelay = [30, 60],\n backspaceDelay = [18, 30],\n hardChars = /[\\/{}\\-_@]/,\n hardCharExtraDelay = [30, 65],\n pauseMs = 70,\n disabled = false,\n className = '',\n ...rest\n },\n ref,\n ) {\n const [displayed, setDisplayed] = useState(value);\n const tokenRef = useRef(0);\n const reducedRef = useRef(false);\n const prevValueRef = useRef(value);\n const displayedRef = useRef(value);\n\n useEffect(() => {\n if (typeof window !== 'undefined') {\n reducedRef.current = window.matchMedia(\n '(prefers-reduced-motion: reduce)',\n ).matches;\n }\n }, []);\n\n // Keep a ref of the currently shown text so the animation always\n // starts from the latest frame (even if interrupted mid-typing).\n useEffect(() => {\n displayedRef.current = displayed;\n }, [displayed]);\n\n useEffect(() => {\n if (value === prevValueRef.current) return;\n const source = displayedRef.current;\n prevValueRef.current = value;\n\n if (disabled || reducedRef.current) {\n setDisplayed(value);\n return;\n }\n\n const myToken = ++tokenRef.current;\n\n (async () => {\n const { prefix, suffix, oldMid, newMid } = findDiff(source, value);\n\n for (let i = oldMid.length - 1; i >= 0; i--) {\n if (myToken !== tokenRef.current) return;\n setDisplayed(prefix + oldMid.slice(0, i) + suffix);\n await sleep(rand(backspaceDelay[0], backspaceDelay[1]));\n }\n\n if (myToken !== tokenRef.current) return;\n await sleep(pauseMs);\n\n for (let i = 1; i <= newMid.length; i++) {\n if (myToken !== tokenRef.current) return;\n setDisplayed(prefix + newMid.slice(0, i) + suffix);\n const ch = newMid.charAt(i - 1);\n const base = rand(typeDelay[0], typeDelay[1]);\n const extra = hardChars.test(ch)\n ? rand(hardCharExtraDelay[0], hardCharExtraDelay[1])\n : 0;\n await sleep(base + extra);\n }\n })();\n }, [\n value,\n disabled,\n typeDelay,\n backspaceDelay,\n hardChars,\n hardCharExtraDelay,\n pauseMs,\n ]);\n\n return (\n <span\n ref={ref}\n className={`royui-textmorph ${className}`.trim()}\n aria-live=\"polite\"\n {...rest}\n >\n {renderText ? renderText(displayed) : displayed}\n </span>\n );\n },\n);\n"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@roy-ui/ui",
3
+ "version": "0.0.2",
4
+ "description": "Open source React component library",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/DibbayajyotiRoy/RoyUI.git",
10
+ "directory": "packages/ui"
11
+ },
12
+ "homepage": "https://github.com/DibbayajyotiRoy/RoyUI#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/DibbayajyotiRoy/RoyUI/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "provenance": true
19
+ },
20
+ "sideEffects": [
21
+ "**/*.css"
22
+ ],
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "typecheck": "tsc --noEmit"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=18",
42
+ "react-dom": ">=18"
43
+ },
44
+ "devDependencies": {
45
+ "@types/react": "^18.3.12",
46
+ "@types/react-dom": "^18.3.1",
47
+ "react": "^18.3.1",
48
+ "react-dom": "^18.3.1",
49
+ "tsup": "^8.3.5",
50
+ "typescript": "^5.6.3"
51
+ }
52
+ }