likvid-ui 0.1.0 → 2.0.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.
Files changed (29) hide show
  1. package/dist/components/LiquidButton/ButtonLoader.d.ts +1 -0
  2. package/dist/components/LiquidButton/ButtonLoader.js +5 -0
  3. package/dist/components/LiquidButton/LiquidButton.d.ts +37 -0
  4. package/dist/components/LiquidButton/LiquidButton.js +45 -0
  5. package/dist/components/LiquidButton/index.d.ts +2 -0
  6. package/dist/components/LiquidButton/index.js +2 -0
  7. package/dist/components/LiquidButton/liquidButton.module.css +128 -0
  8. package/dist/components/LiquidContainer/LiquidContainer.d.ts +45 -0
  9. package/dist/components/LiquidContainer/LiquidContainer.js +46 -0
  10. package/dist/components/LiquidContainer/index.d.ts +1 -0
  11. package/dist/components/LiquidContainer/index.js +1 -0
  12. package/dist/components/LiquidContainer/liquidContainer.module.css +44 -0
  13. package/dist/components/LiquidRadio/LiquidRadio.d.ts +16 -0
  14. package/dist/components/LiquidRadio/LiquidRadio.js +65 -0
  15. package/dist/components/LiquidRadio/index.d.ts +2 -0
  16. package/dist/components/LiquidRadio/index.js +1 -0
  17. package/dist/components/LiquidRadio/liquidRadio.module.css +155 -0
  18. package/dist/components/NavBarBase/NavBarBase.d.ts +14 -0
  19. package/dist/components/NavBarBase/NavBarBase.js +74 -0
  20. package/dist/components/NavBarBase/index.d.ts +2 -0
  21. package/dist/components/NavBarBase/index.js +1 -0
  22. package/dist/components/NavBarBase/navBarBase.helpers.d.ts +1 -0
  23. package/dist/components/NavBarBase/navBarBase.helpers.js +1 -0
  24. package/dist/components/NavBarBase/navBarBase.module.css +167 -0
  25. package/dist/index.d.ts +6 -1
  26. package/dist/index.js +4 -1
  27. package/dist/utils/cx.d.ts +3 -0
  28. package/dist/utils/cx.js +1 -0
  29. package/package.json +24 -10
@@ -0,0 +1 @@
1
+ export declare const ButtonLoader: import("react").MemoExoticComponent<() => import("react").JSX.Element>;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import s from './liquidButton.module.css';
4
+ export const ButtonLoader = memo(() => (_jsx("span", { role: 'status', "aria-label": '\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430', className: s.loader })));
5
+ ButtonLoader.displayName = 'ButtonLoader';
@@ -0,0 +1,37 @@
1
+ import { type ComponentPropsWithoutRef, type CSSProperties, type MouseEventHandler, type ReactNode, type Ref } from 'react';
2
+ type T_shape = 'pill' | 'widget';
3
+ type T_variant = 'glass' | 'accent' | 'accent-dark';
4
+ type T_tag = keyof JSX.IntrinsicElements;
5
+ interface I_commonProps {
6
+ children?: ReactNode;
7
+ title?: string | null;
8
+ callback?: () => void;
9
+ onClick?: MouseEventHandler<Element>;
10
+ isActive?: boolean;
11
+ disabled?: boolean;
12
+ isLoading?: boolean;
13
+ className?: string;
14
+ width?: '100%' | 'max-content';
15
+ shape?: T_shape;
16
+ variant?: T_variant;
17
+ style?: CSSProperties;
18
+ }
19
+ type T_hrefProps<T_as extends T_tag> = T_as extends 'a' ? {
20
+ href: string;
21
+ } : {
22
+ href?: never;
23
+ };
24
+ type T_props<T_as extends T_tag = 'button'> = I_commonProps & T_hrefProps<T_as> & Omit<ComponentPropsWithoutRef<T_as>, keyof I_commonProps | 'as' | 'href' | 'ref'> & {
25
+ as?: T_as;
26
+ };
27
+ type T_polymorphic = <T_as extends T_tag = 'button'>(props: T_props<T_as> & {
28
+ ref?: Ref<HTMLElement>;
29
+ }) => ReactNode;
30
+ export declare const LiquidButton: T_polymorphic & {
31
+ a: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<I_commonProps & {
32
+ href: string;
33
+ } & Omit<Omit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref">, "ref" | "href" | "as" | keyof I_commonProps> & {
34
+ as?: "a" | undefined;
35
+ } & import("react").RefAttributes<HTMLElement>>>;
36
+ };
37
+ export {};
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createElement, forwardRef, memo } from 'react';
3
+ import { cx } from '../../utils/cx';
4
+ import { ButtonLoader } from './ButtonLoader';
5
+ import s from './liquidButton.module.css';
6
+ const SHAPE_CLASS = {
7
+ pill: s.shapePill,
8
+ widget: s.shapeWidget
9
+ };
10
+ const VARIANT_CLASS = {
11
+ glass: undefined,
12
+ accent: s.variantAccent,
13
+ 'accent-dark': s.variantAccentDark
14
+ };
15
+ const BaseButton = memo(forwardRef(function LiquidButton(props, ref) {
16
+ const { as = 'button', children, className, callback, disabled = false, isActive = false, isLoading = false, shape = 'pill', style, title, variant = 'glass', width, onClick, ...rest } = props;
17
+ const isDisabled = disabled || isLoading;
18
+ const isLink = as === 'a';
19
+ const handlePress = (event) => {
20
+ if (isDisabled) {
21
+ if (isLink)
22
+ event.preventDefault();
23
+ return;
24
+ }
25
+ callback?.();
26
+ onClick?.(event);
27
+ };
28
+ return createElement(as, {
29
+ ...rest,
30
+ ref,
31
+ disabled: as === 'button' ? isDisabled : undefined,
32
+ 'aria-disabled': isDisabled && !isLink ? true : undefined,
33
+ onClick: handlePress,
34
+ style: width ? { ...style, width } : style,
35
+ className: cx(s.wrapper, SHAPE_CLASS[shape], VARIANT_CLASS[variant], isActive && s.wrapperActive, isDisabled && s.disabled, className)
36
+ }, isLoading ? (_jsx("span", { className: s.loading, children: _jsx(ButtonLoader, {}) })) : (title ? _jsx("p", { children: title }) : children));
37
+ }));
38
+ BaseButton.displayName = 'LiquidButton';
39
+ const LinkButton = memo(forwardRef(function LiquidButtonLink(props, ref) {
40
+ return _jsx(BaseButton, { ...props, as: 'a', ref: ref });
41
+ }));
42
+ LinkButton.displayName = 'LiquidButton.a';
43
+ export const LiquidButton = Object.assign(BaseButton, {
44
+ a: LinkButton
45
+ });
@@ -0,0 +1,2 @@
1
+ export { LiquidButton } from './LiquidButton';
2
+ export { ButtonLoader } from './ButtonLoader';
@@ -0,0 +1,2 @@
1
+ export { LiquidButton } from './LiquidButton';
2
+ export { ButtonLoader } from './ButtonLoader';
@@ -0,0 +1,128 @@
1
+ :where(.wrapper) {
2
+ --button-radius: var(--radius-pull);
3
+
4
+ position: relative;
5
+ width: max-content;
6
+ height: max-content;
7
+ border: none;
8
+ appearance: none;
9
+ font: inherit;
10
+ color: inherit;
11
+ text-decoration: none;
12
+ cursor: pointer;
13
+ background: linear-gradient(
14
+ to bottom right,
15
+ transparent 30%,
16
+ white
17
+ );
18
+ backdrop-filter: blur(2px);
19
+ box-shadow: 0 0 20px -2px #5d5c5c40;
20
+ padding: 0.875rem 1rem;
21
+ border-radius: var(--button-radius);
22
+ transition: 0.1s ease-in-out;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ }
27
+
28
+ :where(.shapePill) {
29
+ --button-radius: var(--radius-pull);
30
+ corner-shape: initial !important;
31
+ }
32
+
33
+ :where(.shapeWidget) {
34
+ --button-radius: var(--radius-widget);
35
+ corner-shape: squircle !important;
36
+ }
37
+
38
+ :where(.shapeWidget)::before {
39
+ corner-shape: squircle;
40
+ }
41
+
42
+ :where(.wrapper):not(:disabled):not(.disabled):active {
43
+ transform: scale(1.1);
44
+ }
45
+
46
+ :where(.wrapper):disabled,
47
+ :where(.wrapper).disabled {
48
+ opacity: 0.7;
49
+ cursor: not-allowed;
50
+ }
51
+
52
+ :where(.wrapper):disabled:active,
53
+ :where(.wrapper).disabled:active {
54
+ transform: none;
55
+ animation: liquidButtonShake 0.1s ease-in-out;
56
+ }
57
+
58
+ @keyframes liquidButtonShake {
59
+ 0%, 100% {
60
+ transform: translateX(0);
61
+ }
62
+
63
+ 25% {
64
+ transform: translateX(-3px);
65
+ }
66
+
67
+ 50% {
68
+ transform: translateX(3px);
69
+ }
70
+
71
+ 75% {
72
+ transform: translateX(-2px);
73
+ }
74
+ }
75
+
76
+ :where(.wrapper)::before {
77
+ content: '';
78
+ position: absolute;
79
+ inset: 0;
80
+ border-radius: var(--button-radius) !important;
81
+ box-shadow: inset 1px 1px 1px 0 white, inset -1px -1px 1px 0 white;
82
+ z-index: 0;
83
+ }
84
+
85
+ :where(.variantAccent) {
86
+ background: var(--color-main);
87
+ color: white;
88
+ }
89
+
90
+ :where(.variantAccentDark) {
91
+ background: var(--color-main-dark, var(--color-main));
92
+ color: white;
93
+ }
94
+
95
+ :where(.wrapperActive) {
96
+ background: #000000a3;
97
+ color: white;
98
+ }
99
+
100
+ :where(.wrapper) p {
101
+ margin: 0;
102
+ font-size: 0.875rem;
103
+ font-weight: 500;
104
+ transition: 0.1s ease-in-out;
105
+ }
106
+
107
+ :where(.loading) {
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: center;
111
+ height: 1.19em;
112
+ }
113
+
114
+ :where(.loader) {
115
+ display: inline-block;
116
+ width: 1em;
117
+ height: 1em;
118
+ border: 2px solid rgba(128, 128, 128, 0.3);
119
+ border-top-color: currentColor;
120
+ border-radius: 50%;
121
+ animation: loaderSpin 0.8s linear infinite;
122
+ }
123
+
124
+ @keyframes loaderSpin {
125
+ to {
126
+ transform: rotate(360deg);
127
+ }
128
+ }
@@ -0,0 +1,45 @@
1
+ import { type ReactNode } from 'react';
2
+ type T_shape = 'widget' | 'pill';
3
+ interface I_shapeProps {
4
+ shape?: T_shape;
5
+ classNameShadow?: string;
6
+ }
7
+ export declare const LiquidContainer: {
8
+ div: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & I_shapeProps & {
9
+ children: ReactNode;
10
+ } & import("react").RefAttributes<HTMLDivElement>>>;
11
+ section: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & I_shapeProps & {
12
+ children: ReactNode;
13
+ } & import("react").RefAttributes<HTMLElement>>>;
14
+ li: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & I_shapeProps & {
15
+ children: ReactNode;
16
+ } & import("react").RefAttributes<HTMLLIElement>>>;
17
+ ul: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLUListElement>, HTMLUListElement>, "ref"> & I_shapeProps & {
18
+ children: ReactNode;
19
+ } & import("react").RefAttributes<HTMLUListElement>>>;
20
+ ol: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").OlHTMLAttributes<HTMLOListElement>, HTMLOListElement>, "ref"> & I_shapeProps & {
21
+ children: ReactNode;
22
+ } & import("react").RefAttributes<HTMLOListElement>>>;
23
+ figure: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & I_shapeProps & {
24
+ children: ReactNode;
25
+ } & import("react").RefAttributes<HTMLElement>>>;
26
+ form: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, "ref"> & I_shapeProps & {
27
+ children: ReactNode;
28
+ } & import("react").RefAttributes<HTMLFormElement>>>;
29
+ fieldset: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").FieldsetHTMLAttributes<HTMLFieldSetElement>, HTMLFieldSetElement>, "ref"> & I_shapeProps & {
30
+ children: ReactNode;
31
+ } & import("react").RefAttributes<HTMLFieldSetElement>>>;
32
+ label: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, "ref"> & I_shapeProps & {
33
+ children: ReactNode;
34
+ } & import("react").RefAttributes<HTMLLabelElement>>>;
35
+ button: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & I_shapeProps & {
36
+ children: ReactNode;
37
+ } & import("react").RefAttributes<HTMLButtonElement>>>;
38
+ a: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref"> & I_shapeProps & {
39
+ children: ReactNode;
40
+ } & import("react").RefAttributes<HTMLAnchorElement>>>;
41
+ span: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & I_shapeProps & {
42
+ children: ReactNode;
43
+ } & import("react").RefAttributes<HTMLSpanElement>>>;
44
+ };
45
+ export {};
@@ -0,0 +1,46 @@
1
+ import { createElement, forwardRef, memo } from 'react';
2
+ import { cx } from '../../utils/cx';
3
+ import s from './liquidContainer.module.css';
4
+ const VARIANTS = {
5
+ div: { defaultShape: 'widget' },
6
+ section: { defaultShape: 'widget' },
7
+ li: { defaultShape: 'widget' },
8
+ ul: { defaultShape: 'widget' },
9
+ ol: { defaultShape: 'widget' },
10
+ figure: { defaultShape: 'widget' },
11
+ form: { defaultShape: 'widget' },
12
+ fieldset: { defaultShape: 'widget' },
13
+ label: { defaultShape: 'pill' },
14
+ button: { defaultShape: 'pill', extraClass: s.wrapperButton, defaultProps: { type: 'button' } },
15
+ a: { defaultShape: 'pill' },
16
+ span: { defaultShape: 'pill' }
17
+ };
18
+ const SHAPE_CLASS = {
19
+ widget: undefined,
20
+ pill: s.wrapperPill
21
+ };
22
+ function createVariant(variant) {
23
+ const { defaultShape, extraClass, defaultProps } = VARIANTS[variant];
24
+ const Component = forwardRef(({ children, className, classNameShadow, shape = defaultShape, ...rest }, ref) => (createElement(variant, {
25
+ ...defaultProps,
26
+ ...rest,
27
+ ref,
28
+ className: cx(s.wrapper, SHAPE_CLASS[shape], extraClass, className)
29
+ }, createElement('div', { className: cx(s.shadow, classNameShadow), 'aria-hidden': true }), children)));
30
+ Component.displayName = `LiquidContainer.${variant}`;
31
+ return memo(Component);
32
+ }
33
+ export const LiquidContainer = {
34
+ div: createVariant('div'),
35
+ section: createVariant('section'),
36
+ li: createVariant('li'),
37
+ ul: createVariant('ul'),
38
+ ol: createVariant('ol'),
39
+ figure: createVariant('figure'),
40
+ form: createVariant('form'),
41
+ fieldset: createVariant('fieldset'),
42
+ label: createVariant('label'),
43
+ button: createVariant('button'),
44
+ a: createVariant('a'),
45
+ span: createVariant('span')
46
+ };
@@ -0,0 +1 @@
1
+ export { LiquidContainer } from './LiquidContainer';
@@ -0,0 +1 @@
1
+ export { LiquidContainer } from './LiquidContainer';
@@ -0,0 +1,44 @@
1
+ :where(.wrapper) {
2
+ position: relative;
3
+ backdrop-filter: blur(2px);
4
+ box-shadow: 0 0 40px -5px #5353531f;
5
+ border-radius: var(--radius-widget);
6
+ corner-shape: squircle !important;
7
+ transition: background 0.1s;
8
+ }
9
+
10
+ :where(.wrapper) .shadow {
11
+ position: absolute;
12
+ inset: 0;
13
+ box-shadow: inset 1px 1px 1px 0 white, inset -1px -1px 1px 0 white;
14
+ z-index: -1;
15
+ border-radius: var(--radius-widget);
16
+ corner-shape: squircle !important;
17
+ background: linear-gradient(
18
+ to bottom right,
19
+ transparent 30%,
20
+ white
21
+ );
22
+ }
23
+
24
+ :where(.wrapperPill) {
25
+ border-radius: var(--radius-pull);
26
+ corner-shape: initial !important;
27
+ }
28
+
29
+ :where(.wrapperPill) .shadow {
30
+ border-radius: var(--radius-pull);
31
+ corner-shape: initial !important;
32
+ }
33
+
34
+ :where(.wrapperButton) {
35
+ border: none;
36
+ padding: 0;
37
+ margin: 0;
38
+ background: transparent;
39
+ font: inherit;
40
+ color: inherit;
41
+ cursor: pointer;
42
+ text-align: inherit;
43
+ appearance: none;
44
+ }
@@ -0,0 +1,16 @@
1
+ import { ReactNode } from 'react';
2
+ import 'swiper/css';
3
+ export interface I_liquidRadioItem<T extends string = string> {
4
+ value: T;
5
+ label: string;
6
+ href: string;
7
+ }
8
+ interface I_liquidRadioProps<T extends string = string> {
9
+ items: readonly I_liquidRadioItem<T>[];
10
+ value: T;
11
+ onChange: (value: T) => void;
12
+ ariaLabel?: string;
13
+ className?: string;
14
+ }
15
+ export declare const LiquidRadio: <T extends string>(props: I_liquidRadioProps<T>) => ReactNode;
16
+ export {};
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import cn from 'classnames';
6
+ import classNames from 'classnames';
7
+ import { Swiper, SwiperSlide } from 'swiper/react';
8
+ import s from './liquidRadio.module.css';
9
+ import 'swiper/css';
10
+ export const LiquidRadio = memo(({ items, value, onChange, ariaLabel = 'Switch', className }) => {
11
+ const activeIndex = useMemo(() => Math.max(0, items.findIndex(item => item.value === value)), [items, value]);
12
+ const slidesCount = items.length * 2 - 1;
13
+ const slidesCountMap = Array.from({ length: slidesCount });
14
+ const slidesActive = items.length - 1;
15
+ const itemsLength = items.length;
16
+ const swiperRef = useRef(null);
17
+ const shadowRef = useRef(null);
18
+ const indexActiveRef = useRef(null);
19
+ const [isTouching, setIsTouching] = useState(false);
20
+ const handleChangeByActiveSlide = useCallback(() => {
21
+ const idx = indexActiveRef.current;
22
+ if (idx == null || idx < 0)
23
+ return;
24
+ const item = items[idx];
25
+ if (!item)
26
+ return;
27
+ onChange(item.value);
28
+ }, [items, onChange]);
29
+ const onNavigate = (clickIndex) => {
30
+ const activeSlideIndex = swiperRef.current?.activeIndex ?? 0;
31
+ const desiredSlideIndex = itemsLength - clickIndex + activeSlideIndex;
32
+ swiperRef.current?.slideTo(desiredSlideIndex, 300);
33
+ };
34
+ const handleTouchStart = () => {
35
+ setIsTouching(true);
36
+ };
37
+ const handleTouchEnd = () => {
38
+ setIsTouching(false);
39
+ handleChangeByActiveSlide();
40
+ };
41
+ const onProgress = useCallback((swiper, prog) => {
42
+ if (!shadowRef.current)
43
+ return;
44
+ const slidesLength = swiper.slides.length;
45
+ const perView = Number(swiper.params.slidesPerView) || 1;
46
+ const maxIndex = Math.max(0, slidesLength - perView);
47
+ const exactIndex = prog * maxIndex;
48
+ const hoverIndex = Math.round(exactIndex);
49
+ indexActiveRef.current = itemsLength - 1 - hoverIndex;
50
+ shadowRef.current.style.left = `${100 - prog * 100}%`;
51
+ }, [itemsLength]);
52
+ useEffect(() => {
53
+ const swiper = swiperRef.current;
54
+ if (!swiper)
55
+ return;
56
+ if (activeIndex < 0)
57
+ return;
58
+ const nextSlide = itemsLength - 1 - activeIndex;
59
+ swiper.slideTo(nextSlide, 200);
60
+ indexActiveRef.current = activeIndex;
61
+ }, [activeIndex, itemsLength]);
62
+ return (_jsxs("div", { "aria-label": ariaLabel, role: 'radiogroup', className: cn(s.wrapper, isTouching && s.wrapperPress, className), style: { gridTemplateColumns: `repeat(${itemsLength}, 1fr)` }, onPointerDown: handleTouchStart, onPointerUp: handleTouchEnd, onPointerCancel: handleTouchEnd, children: [_jsx("div", { className: cn(s.shadowWrapper, isTouching && s.shadowWrapperPress), children: _jsx("div", { className: s.shadow, ref: shadowRef }) }), _jsx(Swiper, { className: cn(s.swiper, isTouching && s.swiperPress), slidesPerView: itemsLength, onSwiper: sw => {
63
+ swiperRef.current = sw;
64
+ }, speed: 300, resistanceRatio: 0, initialSlide: itemsLength - 1 - Math.max(activeIndex, 0), onProgress: onProgress, children: slidesCountMap.map((_, index) => (_jsx(SwiperSlide, { className: s.slide, onPointerDown: () => onNavigate(index + 1), children: slidesActive === index ? (_jsx("div", { className: cn(s.activeSlide, isTouching && s.activeSlidePress) })) : null }, index))) }), items.map((item, index) => (_jsx(Link, { href: item.href, prefetch: true, role: 'radio', "aria-checked": !isTouching && index === activeIndex, className: classNames(s.item, { [s.itemActive]: !isTouching && index === activeIndex }), children: _jsx("p", { className: s.title, children: item.label }) }, item.value)))] }));
65
+ });
@@ -0,0 +1,2 @@
1
+ export { LiquidRadio } from './LiquidRadio';
2
+ export type { I_liquidRadioItem } from './LiquidRadio';
@@ -0,0 +1 @@
1
+ export { LiquidRadio } from './LiquidRadio';
@@ -0,0 +1,155 @@
1
+ .wrapper {
2
+ user-select: none;
3
+ -webkit-user-select: none;
4
+ -webkit-user-drag: none;
5
+ -webkit-touch-callout: none;
6
+ -webkit-tap-highlight-color: transparent;
7
+
8
+ position: relative;
9
+ display: grid;
10
+ border-radius: var(--radius-pull) !important;
11
+ z-index: 1;
12
+
13
+ background: rgba(250, 250, 250, 0.75);
14
+
15
+ box-shadow: 0 0 40px -5px #5353531f;
16
+ transition: transform 0.15s ease-out, background 0.1s;
17
+ padding: 0 3px;
18
+
19
+ backdrop-filter: blur(2px);
20
+
21
+ width: 100%;
22
+ }
23
+
24
+ .wrapper:before {
25
+ content: '';
26
+ position: absolute;
27
+ inset: 0;
28
+ border-radius: var(--radius-pull) !important;
29
+ box-shadow: inset 1.5px 1px 1px 0 white, inset -1.5px -1px 1px 0 white;
30
+ z-index: 0;
31
+
32
+ }
33
+
34
+ .wrapperPress {
35
+ transform: scale(1.05);
36
+ }
37
+
38
+ .shadowWrapper {
39
+ position: absolute;
40
+ inset: 0;
41
+ border-radius: var(--radius-pull);
42
+ overflow: hidden;
43
+ opacity: 0;
44
+ transition: 0.3s;
45
+ }
46
+
47
+ .shadowWrapperPress {
48
+ opacity: 1;
49
+ }
50
+
51
+ .shadow {
52
+ position: absolute;
53
+ left: 50%;
54
+ top: 50%;
55
+ transform: translate(-50%, -50%);
56
+ background: rgba(250, 250, 250, 0.25);
57
+ box-shadow: 0 0 200px 150px rgba(250, 250, 250, 0.9);
58
+ width: 0;
59
+ height: 0;
60
+ opacity: 1;
61
+ border-radius: var(--radius-pull);
62
+ }
63
+
64
+ .item {
65
+ position: relative;
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 1px;
69
+ align-items: center;
70
+ justify-content: center;
71
+ padding: 0.75rem 0.75rem;
72
+ width: 100%;
73
+ pointer-events: none;
74
+ border: none;
75
+ background: transparent;
76
+ text-decoration: none;
77
+ overflow: hidden;
78
+ cursor: default;
79
+ font: inherit;
80
+ }
81
+
82
+ .title {
83
+ position: relative;
84
+ margin: 0;
85
+ font-size: 0.875rem;
86
+ font-weight: 500;
87
+ transition: 0.2s, color 0s !important;
88
+ text-align: center;
89
+ width: 100%;
90
+ }
91
+
92
+ .swiper {
93
+ border-radius: var(--radius-pull);
94
+ position: absolute !important;
95
+ inset: 0 calc(-1 * var(--site-horizontal-padding-mobile));
96
+ padding:
97
+ 0
98
+ calc(var(--site-horizontal-padding-mobile) + 3px)
99
+ !important;
100
+
101
+ z-index: 0 !important;
102
+ }
103
+
104
+ .swiperMirror {
105
+ pointer-events: none;
106
+ }
107
+
108
+ .swiperPress {
109
+ inset:
110
+ -10px
111
+ calc(-1 * var(--site-horizontal-padding-mobile))
112
+ -10px
113
+ calc(-1 * var(--site-horizontal-padding-mobile))
114
+ !important;
115
+
116
+ z-index: 1 !important;
117
+ }
118
+
119
+ .slide {
120
+ position: relative;
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ }
125
+
126
+ .activeSlide {
127
+ position: absolute;
128
+ inset: 3px 0;
129
+ border-radius: var(--radius-pull);
130
+ transition: 0.15s;
131
+ background: #0000001a;
132
+ }
133
+
134
+ .activeSlidePress {
135
+ inset: 6px -7px;
136
+ box-shadow: 0 2px 3px #53535326;
137
+ }
138
+
139
+ .activeSlidePress {
140
+ background: transparent;
141
+ /*backdrop-filter: saturate(400%);*/
142
+ }
143
+
144
+ .activeSlide:after {
145
+ content: '';
146
+ position: absolute;
147
+ inset: 0;
148
+ border-radius: var(--radius-pull);
149
+ opacity: 0.5;
150
+ transition: 0.1s;
151
+ }
152
+
153
+ .activeSlidePress:after {
154
+ box-shadow: inset 1px 1px 1px 0 white, inset -1px -1px 1px 0 white;
155
+ }
@@ -0,0 +1,14 @@
1
+ import { ReactNode } from 'react';
2
+ import 'swiper/css';
3
+ export interface I_mobileNavTab {
4
+ title: string;
5
+ path: string;
6
+ icon: (color: string) => ReactNode;
7
+ isActive: (pathname: string) => boolean;
8
+ onPress?: () => void;
9
+ }
10
+ interface I_props {
11
+ tabs: I_mobileNavTab[];
12
+ }
13
+ export declare const NavBarBase: import("react").MemoExoticComponent<({ tabs }: I_props) => import("react").JSX.Element>;
14
+ export {};
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { usePathname, useRouter } from 'next/navigation';
6
+ import cn from 'classnames';
7
+ import classNames from 'classnames';
8
+ import { Swiper, SwiperSlide } from 'swiper/react';
9
+ import s from './navBarBase.module.css';
10
+ import 'swiper/css';
11
+ export const NavBarBase = memo(({ tabs }) => {
12
+ const router = useRouter();
13
+ const pathname = usePathname();
14
+ //
15
+ const activeTabIndex = useMemo(() => tabs.findIndex(tab => tab.isActive(pathname)), [tabs, pathname]);
16
+ const slidesCount = tabs.length * 2 - 1;
17
+ const slidesCountMap = Array.from({ length: slidesCount });
18
+ const slidesActive = tabs.length - 1;
19
+ const tabsLength = tabs.length;
20
+ const swiperRef = useRef(null);
21
+ const shadowRef = useRef(null);
22
+ const indexActiveRef = useRef(null);
23
+ //
24
+ const [isTouching, setIsTouching] = useState(false);
25
+ const handleNavigateByActiveSlide = useCallback(() => {
26
+ const swiper = swiperRef.current;
27
+ if (!swiper)
28
+ return;
29
+ const idx = indexActiveRef.current;
30
+ if (idx == null || idx < 0)
31
+ return;
32
+ const tab = tabs[idx];
33
+ if (!tab?.path)
34
+ return;
35
+ tab.onPress?.();
36
+ router.push(tab.path);
37
+ }, [tabs, router]);
38
+ const onNavigate = (clickIndex) => {
39
+ const activeIndex = swiperRef.current?.activeIndex ?? 0;
40
+ const desiredSlideIndex = tabsLength - clickIndex + activeIndex;
41
+ swiperRef.current?.slideTo(desiredSlideIndex, 300);
42
+ };
43
+ const handleNavTouchStart = () => {
44
+ setIsTouching(true);
45
+ };
46
+ const handleNavTouchEnd = () => {
47
+ setIsTouching(false);
48
+ handleNavigateByActiveSlide();
49
+ };
50
+ const onProgress = useCallback((swiper, prog) => {
51
+ if (!shadowRef.current)
52
+ return;
53
+ const slidesLength = swiper.slides.length;
54
+ const perView = Number(swiper.params.slidesPerView) || 1;
55
+ const maxIndex = Math.max(0, slidesLength - perView);
56
+ const exactIndex = prog * maxIndex;
57
+ const hoverIndex = Math.round(exactIndex);
58
+ indexActiveRef.current = tabsLength - 1 - hoverIndex;
59
+ shadowRef.current.style.left = `${100 - prog * 100}%`;
60
+ }, [tabsLength]);
61
+ useEffect(() => {
62
+ const swiper = swiperRef.current;
63
+ if (!swiper)
64
+ return;
65
+ if (activeTabIndex < 0)
66
+ return;
67
+ const nextSlide = tabsLength - 1 - activeTabIndex;
68
+ swiper.slideTo(nextSlide, 200);
69
+ indexActiveRef.current = activeTabIndex;
70
+ }, [activeTabIndex, tabsLength]);
71
+ return (_jsxs("nav", { "aria-label": 'Mobile Navigation', className: cn(s.wrapper, isTouching && s.wrapperPress), style: { gridTemplateColumns: `repeat(${tabsLength}, 1fr)` }, onPointerDown: handleNavTouchStart, onPointerUp: handleNavTouchEnd, onPointerCancel: handleNavTouchEnd, children: [_jsx("div", { className: cn(s.shadowWrapper, isTouching && s.shadowWrapperPress), children: _jsx("div", { className: s.shadow, ref: shadowRef }) }), _jsx(Swiper, { className: cn(s.swiper, isTouching && s.swiperPress), slidesPerView: tabsLength, onSwiper: sw => {
72
+ swiperRef.current = sw;
73
+ }, speed: 300, resistanceRatio: 0, initialSlide: tabsLength - 1 - Math.max(activeTabIndex, 0), onProgress: onProgress, children: slidesCountMap.map((_, index) => (_jsx(SwiperSlide, { className: s.slide, onPointerDown: () => onNavigate(index + 1), children: slidesActive === index ? (_jsx("div", { className: cn(s.activeSlide, isTouching && s.activeSlidePress) })) : null }, index))) }), tabs.map((item, index) => (_jsxs(Link, { className: classNames(s.item, { [s.itemActive]: !isTouching && index === activeTabIndex }), href: item.path, prefetch: true, onClick: () => item.onPress?.(), children: [_jsx("div", { style: { position: 'relative' }, children: _jsx("span", { className: s.itemIcon, children: item?.icon((index === activeTabIndex && !isTouching) ? 'var(--color-main)' : '#6d4c4c') }) }), _jsx("p", { className: s.title, children: item.title })] }, index)))] }));
74
+ });
@@ -0,0 +1,2 @@
1
+ export { NavBarBase } from './NavBarBase';
2
+ export type { I_mobileNavTab } from './navBarBase.helpers';
@@ -0,0 +1 @@
1
+ export { NavBarBase } from './NavBarBase';
@@ -0,0 +1 @@
1
+ export type { I_mobileNavTab } from './NavBarBase';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,167 @@
1
+ .wrapper {
2
+ user-select: none;
3
+ -webkit-user-select: none;
4
+ -webkit-user-drag: none;
5
+ -webkit-touch-callout: none;
6
+ -webkit-tap-highlight-color: transparent;
7
+
8
+ position: relative;
9
+ display: grid;
10
+ border-radius: var(--radius-pull) !important;
11
+ z-index: 1;
12
+ width: calc(100% - (var(--site-horizontal-padding-mobile) * 2));
13
+
14
+ background: rgba(250, 250, 250, 0.75);
15
+
16
+ inset: 0 var(--site-horizontal-padding-mobile);
17
+ box-shadow: 0 0 40px -5px #5353531f;
18
+ transition: transform 0.15s ease-out, background 0.1s;
19
+ padding: 0 3px;
20
+
21
+ backdrop-filter: blur(2px);
22
+ }
23
+
24
+ .wrapper:before {
25
+ content: '';
26
+ position: absolute;
27
+ inset: 0;
28
+ border-radius: var(--radius-pull) !important;
29
+ box-shadow: inset 1.5px 1px 1px 0 white, inset -1.5px -1px 1px 0 white;
30
+ z-index: 0;
31
+
32
+ }
33
+
34
+ .wrapperPress {
35
+ transform: scale(1.05);
36
+ }
37
+
38
+ .shadowWrapper {
39
+ position: absolute;
40
+ inset: 0;
41
+ border-radius: var(--radius-pull);
42
+ overflow: hidden;
43
+ opacity: 0;
44
+ transition: 0.3s;
45
+ }
46
+
47
+ .shadowWrapperPress {
48
+ opacity: 1;
49
+ }
50
+
51
+ .shadow {
52
+ position: absolute;
53
+ left: 50%;
54
+ top: 50%;
55
+ transform: translate(-50%, -50%);
56
+ background: rgba(250, 250, 250, 0.25);
57
+ box-shadow: 0 0 200px 150px rgba(250, 250, 250, 0.9);
58
+ width: 0;
59
+ height: 0;
60
+ opacity: 1;
61
+ border-radius: var(--radius-pull);
62
+ }
63
+
64
+ .item {
65
+ position: relative;
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 1px;
69
+ align-items: center;
70
+ padding: 0.5rem 0 0.6rem 0;
71
+ width: 100%;
72
+ pointer-events: none;
73
+ text-decoration: none;
74
+ overflow: hidden;
75
+ }
76
+
77
+ .itemIcon {
78
+ display: flex;
79
+ aspect-ratio: 1/1;
80
+ justify-content: center;
81
+ height: 1.75rem;
82
+ transform-origin: center bottom;
83
+ will-change: transform;
84
+ }
85
+
86
+ .item p {
87
+ position: relative;
88
+ margin: 0;
89
+ font-size: 0.6rem;
90
+ font-weight: 500;
91
+ transition: 0.2s, color 0s !important;
92
+ white-space: nowrap;
93
+ text-overflow: ellipsis;
94
+ overflow: hidden;
95
+ text-align: center;
96
+ color: #6d4c4c;
97
+ width: 100%;
98
+ }
99
+
100
+ .itemActive p {
101
+ color: var(--color-main);
102
+ }
103
+
104
+ .swiper {
105
+ border-radius: var(--radius-pull);
106
+ position: absolute !important;
107
+ inset: 0 calc(-1 * var(--site-horizontal-padding-mobile));
108
+ padding:
109
+ 0
110
+ calc(var(--site-horizontal-padding-mobile) + 3px)
111
+ !important;
112
+
113
+ z-index: 0 !important;
114
+ }
115
+
116
+ .swiperMirror {
117
+ pointer-events: none;
118
+ }
119
+
120
+ .swiperPress {
121
+ inset:
122
+ -10px
123
+ calc(-1 * var(--site-horizontal-padding-mobile))
124
+ -10px
125
+ calc(-1 * var(--site-horizontal-padding-mobile))
126
+ !important;
127
+
128
+ z-index: 1 !important;
129
+ }
130
+
131
+ .slide {
132
+ position: relative;
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ }
137
+
138
+ .activeSlide {
139
+ position: absolute;
140
+ inset: 3px 0;
141
+ border-radius: var(--radius-pull);
142
+ transition: 0.15s;
143
+ background: #0000001a;
144
+ }
145
+
146
+ .activeSlidePress {
147
+ inset: 6px -7px;
148
+ box-shadow: 0 2px 3px #53535326;
149
+ }
150
+
151
+ .activeSlidePress {
152
+ background: transparent;
153
+ backdrop-filter: saturate(400%);
154
+ }
155
+
156
+ .activeSlide:after {
157
+ content: '';
158
+ position: absolute;
159
+ inset: 0;
160
+ border-radius: var(--radius-pull);
161
+ opacity: 0.5;
162
+ transition: 0.1s;
163
+ }
164
+
165
+ .activeSlidePress:after {
166
+ box-shadow: inset 1px 1px 1px 0 white, inset -1px -1px 1px 0 white;
167
+ }
package/dist/index.d.ts CHANGED
@@ -1 +1,6 @@
1
- export declare const likvidUi = "likvid-ui";
1
+ export { LiquidContainer } from './components/LiquidContainer';
2
+ export { LiquidButton, ButtonLoader } from './components/LiquidButton';
3
+ export { NavBarBase } from './components/NavBarBase';
4
+ export type { I_mobileNavTab } from './components/NavBarBase';
5
+ export { LiquidRadio } from './components/LiquidRadio';
6
+ export type { I_liquidRadioItem } from './components/LiquidRadio';
package/dist/index.js CHANGED
@@ -1 +1,4 @@
1
- export const likvidUi = 'likvid-ui';
1
+ export { LiquidContainer } from './components/LiquidContainer';
2
+ export { LiquidButton, ButtonLoader } from './components/LiquidButton';
3
+ export { NavBarBase } from './components/NavBarBase';
4
+ export { LiquidRadio } from './components/LiquidRadio';
@@ -0,0 +1,3 @@
1
+ type ClassValue = string | false | null | undefined;
2
+ export declare const cx: (...classes: ClassValue[]) => string;
3
+ export {};
@@ -0,0 +1 @@
1
+ export const cx = (...classes) => classes.filter(Boolean).join(' ');
package/package.json CHANGED
@@ -1,20 +1,17 @@
1
1
  {
2
2
  "name": "likvid-ui",
3
- "version": "0.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Likvid UI — glass-кит React-компонентов в стиле Apple Liquid Glass",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "scripts": {
9
- "build": "tsc -p tsconfig.build.json",
10
- "prepublish": "yarn build",
11
- "prepublishOnly": "yarn build",
12
- "whoami": "npm whoami",
13
- "check": "npm pack --dry-run",
14
- "pub": "yarn publish",
15
- "pub:patch": "npm version patch --no-git-tag-version && yarn publish",
16
- "pub:minor": "npm version minor --no-git-tag-version && yarn publish",
17
- "pub:major": "npm version major --no-git-tag-version && yarn publish"
9
+ "build": "tsc -p tsconfig.build.json && node scripts/copy-css.mjs",
10
+ "prepack": "yarn build",
11
+ "pub": "npm publish",
12
+ "pub:patch": "npm version patch --no-git-tag-version && npm publish",
13
+ "pub:minor": "npm version minor --no-git-tag-version && npm publish",
14
+ "pub:major": "npm version major --no-git-tag-version && npm publish"
18
15
  },
19
16
  "exports": {
20
17
  ".": {
@@ -34,7 +31,24 @@
34
31
  ],
35
32
  "license": "MIT",
36
33
  "packageManager": "yarn@1.22.22",
34
+ "peerDependencies": {
35
+ "next": ">=13",
36
+ "react": ">=18",
37
+ "react-dom": ">=18",
38
+ "swiper": ">=11"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "next": {
42
+ "optional": true
43
+ }
44
+ },
37
45
  "devDependencies": {
46
+ "@types/react": "^18.3.12",
47
+ "@types/react-dom": "^18.3.1",
48
+ "swiper": "^11.2.10",
38
49
  "typescript": "^5.6.3"
50
+ },
51
+ "dependencies": {
52
+ "classnames": "^2.5.1"
39
53
  }
40
54
  }