@synerise/ds-tray 1.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ # 1.1.0 (2025-09-05)
7
+
8
+
9
+ ### Features
10
+
11
+ * **tray:** new component ([4f747b8](https://github.com/Synerise/synerise-design/commit/4f747b8ed29591ec8358d8b7c54d2d7d40374524))
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Synerise
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ ---
2
+ id: tray
3
+ title: Tray
4
+ ---
5
+
6
+ Tray UI Component
7
+
8
+ A slide-over panel (tray) rendered from the edge of the screen. Content and visibility are provided via the Tray context — the component itself only mounts/render when its id is opened by the provider.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm i @synerise/ds-tray
14
+ # or
15
+ yarn add @synerise/ds-tray
16
+ ```
17
+
18
+ ## Quick overview
19
+
20
+ - Mount a single `Tray` (or more) inside a `TrayProvider`.
21
+ - Open/close trays via the `useTray` hook which calls `openTray(id, TrayData)` / `closeTray(id)`.
22
+
23
+ Key source files:
24
+ - Component: [`Tray`](packages/components/tray/src/Tray.tsx)
25
+ - Props type: [`TrayProps`](packages/components/tray/src/Tray.types.ts)
26
+ - Provider & data shape: [`TrayProvider` / `TrayData`](packages/components/tray/src/components/TrayProvider.tsx)
27
+ - Hooks: [`useTray`](packages/components/tray/src/hooks/useTray.ts), [`useTrayContext`](packages/components/tray/src/hooks/useTrayContext.ts)
28
+ - Context: [`TrayContext`](packages/components/tray/src/contexts/TrayContext.ts)
29
+
30
+ ## Basic usage
31
+
32
+ 1) Wrap your app with the provider and mount a `Tray` with a stable id:
33
+
34
+ ```tsx
35
+ import React from 'react';
36
+ import { TrayProvider } from '@synerise/ds-tray';
37
+ import Tray from '@synerise/ds-tray';
38
+
39
+ const App = () => (
40
+ <TrayProvider>
41
+ {/* mount tray(s) that will be controlled via context */}
42
+ <Tray id="my-tray" />
43
+ {/* other app UI */}
44
+ </TrayProvider>
45
+ );
46
+ ```
47
+
48
+ 2) Open the tray from anywhere inside the provider using the hook:
49
+
50
+ ```tsx
51
+ import React from 'react';
52
+ import { useTray } from '@synerise/ds-tray';
53
+
54
+ const Demo = () => {
55
+ const { open, close } = useTray();
56
+
57
+ const openTray = () => {
58
+ open('my-tray', {
59
+ title: 'Details',
60
+ content: <div>Tray content</div>,
61
+ headerRightSide: <span>Extra</span>,
62
+ footer: <div><button onClick={() => close('my-tray')}>Close</button></div>,
63
+ onClose: (id) => console.log('closed', id),
64
+ });
65
+ };
66
+
67
+ return <button onClick={openTray}>Open tray</button>;
68
+ };
69
+ ```
70
+
71
+ ## API
72
+
73
+ Tray component props
74
+ | Property | Description | Type | Required | |
75
+ |-------------------------------|----------------------------------------------------|-----------------------------|----------|-----|
76
+ | `id` | Identifier used to bind the tray to provider state | `string | number` | yes |
77
+ | any other HTML div attributes | `className`, `style`, `data-*`, etc. | `HTMLDivElement` attributes | no | |
78
+
79
+
80
+ Tray data (passed to `open(id, data)`) — [`TrayData`](packages/components/tray/src/components/TrayProvider.tsx)
81
+ | Property | Description | Type | |
82
+ |-------------------|------------------------------------------------|--------------|------------------|
83
+ | `content` | Main content to render inside the tray | `ReactNode` | |
84
+ | `title` | Header title (node) | `ReactNode` | |
85
+ | `headerRightSide` | Node rendered in header to the right of title | `ReactNode` | |
86
+ | `footer` | Footer node rendered at bottom of tray | `ReactNode` | |
87
+ | `onClose` | Callback invoked when tray close action occurs | `(id: string | number) => void` |
88
+
89
+ Notes
90
+ - The `Tray` component reads its state from context (`getTrayState(id)`) and returns `null` if not open. See [`Tray.tsx`](packages/components/tray/src/Tray.tsx).
91
+ - The provider stores tray entries keyed by id; you can open different trays by using different ids.
92
+ - The close button in the header calls `closeTray(id)` and triggers `TrayData.onClose` if provided.
93
+ - `Tray` accepts normal div props because `TrayProps` extends HTML div attributes. See [`Tray.types.ts`](packages/components/tray/src/Tray.types.ts).
@@ -0,0 +1,3 @@
1
+ export declare const WRAPPER_HEIGHT = 562;
2
+ export declare const FOOTER_HEIGHT = 48;
3
+ export declare const HEADER_HEIGHT = 56;
@@ -0,0 +1,3 @@
1
+ export var WRAPPER_HEIGHT = 562;
2
+ export var FOOTER_HEIGHT = 48;
3
+ export var HEADER_HEIGHT = 56;
package/dist/Tray.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { TrayProps } from './Tray.types';
3
+ declare const Tray: ({ id, ...htmlAttributes }: TrayProps) => React.JSX.Element | null;
4
+ export default Tray;
package/dist/Tray.js ADDED
@@ -0,0 +1,40 @@
1
+ var _excluded = ["id"];
2
+ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
3
+ import React from 'react';
4
+ import Button from '@synerise/ds-button';
5
+ import Icon, { CloseM } from '@synerise/ds-icon';
6
+ import * as S from './Tray.styles';
7
+ import { useTrayContext } from './hooks/useTrayContext';
8
+ var Tray = function Tray(_ref) {
9
+ var id = _ref.id,
10
+ htmlAttributes = _objectWithoutPropertiesLoose(_ref, _excluded);
11
+ var _useTrayContext = useTrayContext(),
12
+ getTrayState = _useTrayContext.getTrayState,
13
+ closeTray = _useTrayContext.closeTray;
14
+ var _getTrayState = getTrayState(id),
15
+ isOpen = _getTrayState.isOpen,
16
+ data = _getTrayState.data;
17
+ if (!isOpen) {
18
+ return null;
19
+ }
20
+ var content = data.content,
21
+ title = data.title,
22
+ footer = data.footer,
23
+ headerRightSide = data.headerRightSide,
24
+ onClose = data.onClose;
25
+ return /*#__PURE__*/React.createElement(S.TrayWrapper, htmlAttributes, /*#__PURE__*/React.createElement(S.TrayHeader, null, /*#__PURE__*/React.createElement(S.TrayHeaderLeft, null, /*#__PURE__*/React.createElement(S.TrayTitle, {
26
+ level: 4
27
+ }, title)), /*#__PURE__*/React.createElement(S.TrayHeaderRight, null, headerRightSide, /*#__PURE__*/React.createElement(Button, {
28
+ type: "ghost",
29
+ mode: "single-icon",
30
+ onClick: function onClick() {
31
+ closeTray(id);
32
+ onClose == null || onClose(id);
33
+ }
34
+ }, /*#__PURE__*/React.createElement(Icon, {
35
+ component: /*#__PURE__*/React.createElement(CloseM, null)
36
+ })))), /*#__PURE__*/React.createElement(S.TrayContent, null, /*#__PURE__*/React.createElement(S.TrayScrollbar, {
37
+ absolute: true
38
+ }, content)), footer && /*#__PURE__*/React.createElement(S.TrayFooter, null, footer));
39
+ };
40
+ export default Tray;
@@ -0,0 +1,8 @@
1
+ export declare const TrayContent: import("styled-components").StyledComponent<"div", any, {}, never>;
2
+ export declare const TrayScrollbar: import("styled-components").StyledComponent<import("react").ForwardRefExoticComponent<(import("@synerise/ds-scrollbar").ScrollbarProps | import("@synerise/ds-scrollbar").VirtualScrollbarProps) & import("react").RefAttributes<HTMLElement>>, any, {}, never>;
3
+ export declare const TrayWrapper: import("styled-components").StyledComponent<"div", any, {}, never>;
4
+ export declare const TrayHeader: import("styled-components").StyledComponent<"div", any, {}, never>;
5
+ export declare const TrayHeaderLeft: import("styled-components").StyledComponent<"div", any, {}, never>;
6
+ export declare const TrayHeaderRight: import("styled-components").StyledComponent<"div", any, {}, never>;
7
+ export declare const TrayFooter: import("styled-components").StyledComponent<"div", any, {}, never>;
8
+ export declare const TrayTitle: import("styled-components").StyledComponent<({ level, withoutMargin, children, className, ellipsis, ...antdProps }: import("@synerise/ds-typography/dist/Title.types").Props) => React.JSX.Element, any, {}, never>;
@@ -0,0 +1,48 @@
1
+ import styled from 'styled-components';
2
+ import Scrollbar from '@synerise/ds-scrollbar';
3
+ import { Title } from '@synerise/ds-typography';
4
+ import { FOOTER_HEIGHT, HEADER_HEIGHT, WRAPPER_HEIGHT } from './Tray.const';
5
+ export var TrayContent = styled.div.withConfig({
6
+ displayName: "Traystyles__TrayContent",
7
+ componentId: "sc-1v5sbw5-0"
8
+ })(["flex:1 1 auto;min-height:0;position:relative;display:flex;flex-direction:column;"]);
9
+ export var TrayScrollbar = styled(Scrollbar).withConfig({
10
+ displayName: "Traystyles__TrayScrollbar",
11
+ componentId: "sc-1v5sbw5-1"
12
+ })(["min-height:0;display:flex;flex-direction:column;.scrollbar-container{min-height:0;}"]);
13
+ export var TrayWrapper = styled.div.withConfig({
14
+ displayName: "Traystyles__TrayWrapper",
15
+ componentId: "sc-1v5sbw5-2"
16
+ })(["position:fixed;top:0;right:0;z-index:", ";box-shadow:", ";width:400px;border-radius:3px;max-height:", "px;display:flex;flex-direction:column;background:", ";"], function (props) {
17
+ return props.theme.variables['zindex-tooltip'];
18
+ }, function (props) {
19
+ return props.theme.variables['box-shadow-2'];
20
+ }, WRAPPER_HEIGHT, function (props) {
21
+ return props.theme.palette.white;
22
+ });
23
+ export var TrayHeader = styled.div.withConfig({
24
+ displayName: "Traystyles__TrayHeader",
25
+ componentId: "sc-1v5sbw5-3"
26
+ })(["height:", "px;flex:0 0 ", "px;padding-left:18px;padding-right:12px;border-bottom:solid 1px ", ";display:flex;align-items:center;justify-content:space-between;"], HEADER_HEIGHT, HEADER_HEIGHT, function (props) {
27
+ return props.theme.palette['grey-200'];
28
+ });
29
+ export var TrayHeaderLeft = styled.div.withConfig({
30
+ displayName: "Traystyles__TrayHeaderLeft",
31
+ componentId: "sc-1v5sbw5-4"
32
+ })([""]);
33
+ export var TrayHeaderRight = styled.div.withConfig({
34
+ displayName: "Traystyles__TrayHeaderRight",
35
+ componentId: "sc-1v5sbw5-5"
36
+ })(["display:flex;gap:8px;align-items:center;"]);
37
+ export var TrayFooter = styled.div.withConfig({
38
+ displayName: "Traystyles__TrayFooter",
39
+ componentId: "sc-1v5sbw5-6"
40
+ })(["border-top:solid 1px ", ";background:", ";height:", "px;display:flex;align-items:center;padding:0 8px;flex:0 0 ", "px;"], function (props) {
41
+ return props.theme.palette['grey-100'];
42
+ }, function (props) {
43
+ return props.theme.palette['grey-050'];
44
+ }, FOOTER_HEIGHT, FOOTER_HEIGHT);
45
+ export var TrayTitle = styled(Title).withConfig({
46
+ displayName: "Traystyles__TrayTitle",
47
+ componentId: "sc-1v5sbw5-7"
48
+ })(["margin:0;"]);
@@ -0,0 +1,4 @@
1
+ import { type WithHTMLAttributes } from '@synerise/ds-utils';
2
+ export type TrayProps = WithHTMLAttributes<HTMLDivElement, {
3
+ id: string;
4
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import React, { type ReactNode } from 'react';
2
+ export type TrayData = {
3
+ content: ReactNode;
4
+ title: ReactNode;
5
+ headerRightSide?: ReactNode;
6
+ onClose?: (id: string) => void;
7
+ footer?: ReactNode;
8
+ };
9
+ export type TrayProviderProps = {
10
+ children: ReactNode;
11
+ };
12
+ export declare const TrayProvider: ({ children }: TrayProviderProps) => React.JSX.Element;
@@ -0,0 +1,39 @@
1
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
+ import React, { useState } from 'react';
3
+ import { TrayContext } from '../contexts/TrayContext';
4
+ export var TrayProvider = function TrayProvider(_ref) {
5
+ var children = _ref.children;
6
+ var _useState = useState({}),
7
+ trayState = _useState[0],
8
+ setTrayState = _useState[1];
9
+ var openTray = function openTray(id, data) {
10
+ setTrayState(function (prev) {
11
+ var _extends2;
12
+ return _extends({}, prev, (_extends2 = {}, _extends2[id] = {
13
+ isOpen: true,
14
+ data: data
15
+ }, _extends2));
16
+ });
17
+ };
18
+ var closeTray = function closeTray(id) {
19
+ setTrayState(function (prev) {
20
+ var _extends3;
21
+ return _extends({}, prev, (_extends3 = {}, _extends3[id] = _extends({}, prev[id], {
22
+ isOpen: false
23
+ }), _extends3));
24
+ });
25
+ };
26
+ var getTrayState = function getTrayState(id) {
27
+ return trayState[id] || {
28
+ isOpen: false,
29
+ data: null
30
+ };
31
+ };
32
+ return /*#__PURE__*/React.createElement(TrayContext.Provider, {
33
+ value: {
34
+ openTray: openTray,
35
+ closeTray: closeTray,
36
+ getTrayState: getTrayState
37
+ }
38
+ }, children);
39
+ };
@@ -0,0 +1,10 @@
1
+ import { type TrayData } from '../components/TrayProvider';
2
+ export type TrayContextType = {
3
+ openTray: (id: string, data: TrayData) => void;
4
+ closeTray: (id: string) => void;
5
+ getTrayState: (id: string) => {
6
+ isOpen: boolean;
7
+ data: TrayData;
8
+ };
9
+ };
10
+ export declare const TrayContext: import("react").Context<TrayContextType | undefined>;
@@ -0,0 +1,2 @@
1
+ import { createContext } from 'react';
2
+ export var TrayContext = /*#__PURE__*/createContext(undefined);
@@ -0,0 +1,4 @@
1
+ export declare const useTray: () => {
2
+ open: (id: string, data: import("../components/TrayProvider").TrayData) => void;
3
+ close: (id: string) => void;
4
+ };
@@ -0,0 +1,10 @@
1
+ import { useTrayContext } from './useTrayContext';
2
+ export var useTray = function useTray() {
3
+ var _useTrayContext = useTrayContext(),
4
+ openTray = _useTrayContext.openTray,
5
+ closeTray = _useTrayContext.closeTray;
6
+ return {
7
+ open: openTray,
8
+ close: closeTray
9
+ };
10
+ };
@@ -0,0 +1,2 @@
1
+ import { type TrayContextType } from '../contexts/TrayContext';
2
+ export declare const useTrayContext: () => TrayContextType;
@@ -0,0 +1,9 @@
1
+ import { useContext } from 'react';
2
+ import { TrayContext } from '../contexts/TrayContext';
3
+ export var useTrayContext = function useTrayContext() {
4
+ var context = useContext(TrayContext);
5
+ if (!context) {
6
+ throw new Error('useTrayContext must be used within a TrayProvider');
7
+ }
8
+ return context;
9
+ };
@@ -0,0 +1,4 @@
1
+ export { default } from './Tray';
2
+ export type { TrayProps } from './Tray.types';
3
+ export { TrayProvider } from './components/TrayProvider';
4
+ export { useTray } from './hooks/useTray';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default } from './Tray';
2
+ export { TrayProvider } from './components/TrayProvider';
3
+ export { useTray } from './hooks/useTray';
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@synerise/ds-tray",
3
+ "version": "1.1.0",
4
+ "description": "Tray UI Component for the Synerise Design System",
5
+ "license": "ISC",
6
+ "repository": "Synerise/synerise-design",
7
+ "main": "dist/index.js",
8
+ "files": [
9
+ "/dist",
10
+ "CHANGELOG.md",
11
+ "README.md",
12
+ "package.json",
13
+ "LICENSE.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "npm run build:js && npm run build:css && npm run defs",
20
+ "build:css": "node ../../../scripts/style/less.js",
21
+ "build:js": "babel --delete-dir-on-start --root-mode upward src --out-dir dist --extensions '.js,.ts,.tsx'",
22
+ "build:watch": "npm run build:js -- --watch",
23
+ "defs": "tsc --declaration --outDir dist/ --emitDeclarationOnly",
24
+ "prepublish": "npm run build",
25
+ "types": "tsc --noEmit",
26
+ "pack:ci": "npm pack --pack-destination ../../storybook/storybook-static/static",
27
+ "test": "jest",
28
+ "test:watch": "npm run test -- --watchAll",
29
+ "upgrade:ds": "ncu -f \"@synerise/ds-*\" -u"
30
+ },
31
+ "sideEffects": [
32
+ "dist/style/*",
33
+ "*.less"
34
+ ],
35
+ "types": "dist/index.d.ts",
36
+ "dependencies": {
37
+ "@synerise/ds-button": "^1.4.9",
38
+ "@synerise/ds-icon": "^1.7.1",
39
+ "@synerise/ds-scrollbar": "^1.1.7",
40
+ "@synerise/ds-typography": "^1.0.19",
41
+ "@synerise/ds-utils": "^1.4.1"
42
+ },
43
+ "peerDependencies": {
44
+ "@synerise/ds-core": "*",
45
+ "react": ">=16.9.0 <= 18.3.1",
46
+ "styled-components": "^5.3.3"
47
+ },
48
+ "gitHead": "7f119fa17e645f1d800aea95c313fe22f348439c"
49
+ }