@scality/data-browser-library 1.0.7 → 1.0.8

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.
@@ -5,7 +5,7 @@ import { cleanup, render, screen } from "@testing-library/react";
5
5
  import { MemoryRouter, Route, Routes } from "react-router";
6
6
  import { DataBrowserUICustomizationProvider } from "../../../../contexts/DataBrowserUICustomizationContext.js";
7
7
  var __webpack_modules__ = {
8
- ".." (module) {
8
+ "../index" (module) {
9
9
  module.exports = __rspack_external__index_js_95fdb65a;
10
10
  }
11
11
  };
@@ -19,7 +19,7 @@ function __webpack_require__(moduleId) {
19
19
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
20
20
  return module.exports;
21
21
  }
22
- var external_index_js_ = __webpack_require__("..");
22
+ var external_index_js_ = __webpack_require__("../index");
23
23
  jest.mock('../ObjectSummary', ()=>({
24
24
  ObjectSummary: ()=>/*#__PURE__*/ jsx("div", {
25
25
  "data-testid": "object-summary",
@@ -398,7 +398,7 @@ describe('ObjectDetails', ()=>{
398
398
  expect(screen.getByTestId('object-summary')).toBeInTheDocument();
399
399
  });
400
400
  it('should throw error when useObjectDetailsContext is used outside provider', ()=>{
401
- const { useObjectDetailsContext } = __webpack_require__("..");
401
+ const { useObjectDetailsContext } = __webpack_require__("../index");
402
402
  const TestComponent = ()=>{
403
403
  useObjectDetailsContext();
404
404
  return null;
@@ -9,7 +9,7 @@ const DropZone = styled_components.div`
9
9
  flex: 1;
10
10
  display: flex;
11
11
  flex-direction: column;
12
- height: 300px;
12
+ height: 400px;
13
13
  width: 500px;
14
14
  padding: ${spacing.r20};
15
15
  border-width: ${spacing.r2};
@@ -18,8 +18,10 @@ const DropZone = styled_components.div`
18
18
  border-style: dashed;
19
19
  `;
20
20
  const Files = styled_components.div`
21
- height: 250px;
22
- overflow-y: scroll;
21
+ flex: 1;
22
+ min-height: 0;
23
+ overflow-y: auto;
24
+ overflow-x: hidden;
23
25
  margin: ${spacing.r8} 0px;
24
26
  `;
25
27
  const EmptyFile = styled_components.div`
@@ -38,6 +40,10 @@ const FileRow = styled_components.div`
38
40
  `;
39
41
  const FileInfo = styled_components.div`
40
42
  flex: 1;
43
+ min-width: 0;
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
41
47
  `;
42
48
  const RemoveButton = styled_components.button`
43
49
  background: none;
@@ -52,7 +58,12 @@ const RemoveButton = styled_components.button`
52
58
  `;
53
59
  const maybePluralize = (count, word)=>1 === count ? `1 ${word}` : `${count} ${word}s`;
54
60
  const getTitle = (fileCount)=>0 === fileCount ? 'Upload Files' : `Upload ${maybePluralize(fileCount, 'file')}`;
55
- const FileList = ({ acceptedFiles, open, removeFile })=>/*#__PURE__*/ jsxs("div", {
61
+ const FileListWrapper = styled_components.div`
62
+ display: flex;
63
+ flex-direction: column;
64
+ height: 100%;
65
+ `;
66
+ const FileList = ({ acceptedFiles, open, removeFile })=>/*#__PURE__*/ jsxs(FileListWrapper, {
56
67
  children: [
57
68
  /*#__PURE__*/ jsx(Button, {
58
69
  icon: /*#__PURE__*/ jsx(Icon, {
@@ -65,18 +76,17 @@ const FileList = ({ acceptedFiles, open, removeFile })=>/*#__PURE__*/ jsxs("div"
65
76
  /*#__PURE__*/ jsx(Files, {
66
77
  children: acceptedFiles.map((file)=>/*#__PURE__*/ jsxs(FileRow, {
67
78
  children: [
68
- /*#__PURE__*/ jsx(FileInfo, {
69
- children: /*#__PURE__*/ jsxs("div", {
70
- children: [
71
- file.name,
72
- /*#__PURE__*/ jsx("br", {}),
73
- /*#__PURE__*/ jsx("small", {
74
- children: /*#__PURE__*/ jsx(PrettyBytes, {
75
- bytes: file.size
76
- })
79
+ /*#__PURE__*/ jsxs(FileInfo, {
80
+ title: file.name,
81
+ children: [
82
+ file.name,
83
+ /*#__PURE__*/ jsx("br", {}),
84
+ /*#__PURE__*/ jsx("small", {
85
+ children: /*#__PURE__*/ jsx(PrettyBytes, {
86
+ bytes: file.size
77
87
  })
78
- ]
79
- })
88
+ })
89
+ ]
80
90
  }),
81
91
  /*#__PURE__*/ jsx(RemoveButton, {
82
92
  onClick: ()=>removeFile(file.name),
@@ -0,0 +1,96 @@
1
+ import { coreUIAvailableThemes } from "@scality/core-ui/dist/style/theme";
2
+ import { resolveBrandingTheme } from "../resolveBrandingTheme.js";
3
+ describe('resolveBrandingTheme', ()=>{
4
+ it('loads base preset and returns complete theme', ()=>{
5
+ const config = {
6
+ base: 'darkRebrand',
7
+ logo: '/logo.png',
8
+ brandPrimary: '#2563EB',
9
+ brandSecondary: '#1E3A5F'
10
+ };
11
+ const result = resolveBrandingTheme(config);
12
+ expect(result).toHaveProperty('statusHealthy');
13
+ expect(result).toHaveProperty('statusWarning');
14
+ expect(result).toHaveProperty('statusCritical');
15
+ expect(result).toHaveProperty('backgroundLevel1');
16
+ expect(result.backgroundLevel1).toBe(coreUIAvailableThemes.darkRebrand.backgroundLevel1);
17
+ });
18
+ it('applies brandPrimary to selectedActive and buttonPrimary tokens', ()=>{
19
+ const config = {
20
+ base: 'darkRebrand',
21
+ logo: '/logo.png',
22
+ brandPrimary: '#2563EB',
23
+ brandSecondary: '#1E3A5F'
24
+ };
25
+ const result = resolveBrandingTheme(config);
26
+ expect(result.selectedActive).toBe('#2563EB');
27
+ expect(result.buttonPrimary).toBe('#2563EB');
28
+ });
29
+ it('derives highlight color from brandPrimary at 20% opacity', ()=>{
30
+ const config = {
31
+ base: 'darkRebrand',
32
+ logo: '/logo.png',
33
+ brandPrimary: '#2563EB',
34
+ brandSecondary: '#1E3A5F'
35
+ };
36
+ const result = resolveBrandingTheme(config);
37
+ expect(result.highlight).toBe('rgba(37, 99, 235, 0.2)');
38
+ });
39
+ it('applies brandSecondary to navbarBackground', ()=>{
40
+ const config = {
41
+ base: 'darkRebrand',
42
+ logo: '/logo.png',
43
+ brandPrimary: '#2563EB',
44
+ brandSecondary: '#1E3A5F'
45
+ };
46
+ const result = resolveBrandingTheme(config);
47
+ expect(result.navbarBackground).toBe('#1E3A5F');
48
+ });
49
+ it('applies optional overrides on top of generated theme', ()=>{
50
+ const config = {
51
+ base: 'darkRebrand',
52
+ logo: '/logo.png',
53
+ brandPrimary: '#2563EB',
54
+ brandSecondary: '#1E3A5F',
55
+ overrides: {
56
+ textLink: '#71AEFF',
57
+ border: '#333333'
58
+ }
59
+ };
60
+ const result = resolveBrandingTheme(config);
61
+ expect(result.buttonPrimary).toBe('#2563EB');
62
+ expect(result.navbarBackground).toBe('#1E3A5F');
63
+ expect(result.textLink).toBe('#71AEFF');
64
+ expect(result.border).toBe('#333333');
65
+ });
66
+ it('handles 3-character hex shorthand colors', ()=>{
67
+ const config = {
68
+ base: 'darkRebrand',
69
+ logo: '/logo.png',
70
+ brandPrimary: '#FFF',
71
+ brandSecondary: '#000'
72
+ };
73
+ const result = resolveBrandingTheme(config);
74
+ expect(result.buttonPrimary).toBe('#FFF');
75
+ expect(result.highlight).toBe('rgba(255, 255, 255, 0.2)');
76
+ expect(result.navbarBackground).toBe('#000');
77
+ });
78
+ it('throws error when base theme name does not exist', ()=>{
79
+ const config = {
80
+ base: 'foobar',
81
+ logo: '/logo.png',
82
+ brandPrimary: '#2563EB',
83
+ brandSecondary: '#1E3A5F'
84
+ };
85
+ expect(()=>resolveBrandingTheme(config)).toThrow('Unknown base theme "foobar"');
86
+ });
87
+ it('throws error on invalid hex color format', ()=>{
88
+ const config = {
89
+ base: 'darkRebrand',
90
+ logo: '/logo.png',
91
+ brandPrimary: 'not-a-color',
92
+ brandSecondary: '#1E3A5F'
93
+ };
94
+ expect(()=>resolveBrandingTheme(config)).toThrow('Invalid hex color format');
95
+ });
96
+ });
@@ -0,0 +1,16 @@
1
+ import { type CoreUITheme } from '@scality/core-ui/dist/style/theme';
2
+ import type { BrandingModeConfig } from './types';
3
+ /**
4
+ * Resolves a lightweight branding configuration into a complete CoreUITheme.
5
+ *
6
+ * Takes a base preset and brand colors, then generates a full theme by:
7
+ * - Loading all tokens from the specified base preset
8
+ * - Applying brandPrimary to button and active states
9
+ * - Applying brandSecondary to navbar background
10
+ * - Auto-deriving highlight color from brandPrimary
11
+ * - Applying any optional token overrides
12
+ *
13
+ * @param config - Branding configuration with base preset, colors, and optional overrides
14
+ * @returns Complete CoreUITheme ready to use with styled-components ThemeProvider
15
+ */
16
+ export declare function resolveBrandingTheme(config: BrandingModeConfig): CoreUITheme;
@@ -0,0 +1,23 @@
1
+ import { coreUIAvailableThemes } from "@scality/core-ui/dist/style/theme";
2
+ function hexToRgba(hex, opacity) {
3
+ let cleanHex = hex.replace('#', '');
4
+ if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(cleanHex)) throw new Error(`Invalid hex color format: "${hex}". Expected format: #RGB or #RRGGBB`);
5
+ if (3 === cleanHex.length) cleanHex = cleanHex.split('').map((char)=>char + char).join('');
6
+ const r = Number.parseInt(cleanHex.substring(0, 2), 16);
7
+ const g = Number.parseInt(cleanHex.substring(2, 4), 16);
8
+ const b = Number.parseInt(cleanHex.substring(4, 6), 16);
9
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
10
+ }
11
+ function resolveBrandingTheme(config) {
12
+ const baseTheme = coreUIAvailableThemes[config.base];
13
+ if (!baseTheme) throw new Error(`Unknown base theme "${config.base}". Available themes: ${Object.keys(coreUIAvailableThemes).join(', ')}`);
14
+ return {
15
+ ...baseTheme,
16
+ selectedActive: config.brandPrimary,
17
+ buttonPrimary: config.brandPrimary,
18
+ highlight: hexToRgba(config.brandPrimary, 0.2),
19
+ navbarBackground: config.brandSecondary,
20
+ ...config.overrides
21
+ };
22
+ }
23
+ export { resolveBrandingTheme };
@@ -2,6 +2,7 @@
2
2
  * Runtime configuration types
3
3
  */
4
4
  import type { Bucket } from '@aws-sdk/client-s3';
5
+ import type { CoreUITheme, CoreUIThemeName } from '@scality/core-ui/dist/style/theme';
5
6
  import type { TableItem } from '../components/objects/ObjectList';
6
7
  /**
7
8
  * Proxy configuration for development or custom proxy setups
@@ -264,4 +265,39 @@ export interface DataBrowserUIProps {
264
265
  }
265
266
  export type S3EventType = 's3:ObjectCreated:*' | 's3:ObjectCreated:Put' | 's3:ObjectCreated:Post' | 's3:ObjectCreated:Copy' | 's3:ObjectCreated:CompleteMultipartUpload' | 's3:ObjectRemoved:*' | 's3:ObjectRemoved:Delete' | 's3:ObjectRemoved:DeleteMarkerCreated' | 's3:ObjectRestore:*' | 's3:ObjectRestore:Post' | 's3:ObjectRestore:Completed' | 's3:ObjectRestore:Delete' | 's3:LifecycleExpiration:*' | 's3:LifecycleExpiration:Delete' | 's3:LifecycleExpiration:DeleteMarkerCreated' | 's3:LifecycleTransition' | 's3:Replication:*' | 's3:Replication:OperationFailedReplication' | 's3:Replication:OperationMissedThreshold' | 's3:Replication:OperationReplicatedAfterThreshold' | 's3:Replication:OperationNotTracked' | 's3:ObjectTagging:*' | 's3:ObjectTagging:Put' | 's3:ObjectTagging:Delete' | 's3:ReducedRedundancyLostObject' | 's3:IntelligentTiering' | 's3:ObjectAcl:Put' | 's3:TestEvent';
266
267
  export type S3EventCategory = 'Object Creation' | 'Object Deletion' | 'Object Restoration' | 'Lifecycle' | 'Replication' | 'Object Tagging' | 'Storage & Access' | 'Testing';
268
+ /**
269
+ * Branding configuration for a single theme mode (dark or light).
270
+ *
271
+ * Simplifies theme customization by requiring only a base preset and two brand colors.
272
+ * The branding engine auto-generates a complete theme by:
273
+ * - Loading all tokens from the base preset
274
+ * - Applying brandPrimary to buttons and active states
275
+ * - Applying brandSecondary to navbar background
276
+ * - Auto-deriving hover states and highlights
277
+ * - Allowing optional token overrides for fine-tuning
278
+ */
279
+ export interface BrandingModeConfig {
280
+ /** Base theme preset that provides default values for all tokens */
281
+ base: CoreUIThemeName;
282
+ /** Path to brand logo displayed in navbar. Supports separate logos for dark/light modes. */
283
+ logo: string;
284
+ /** Main brand color applied to buttons, active states, and auto-generates hover color */
285
+ brandPrimary: string;
286
+ /** Brand color applied to navbar background for immediate brand recognition */
287
+ brandSecondary: string;
288
+ /** Optional overrides for individual design tokens when fine-tuning is needed */
289
+ overrides?: Partial<CoreUITheme>;
290
+ }
291
+ /**
292
+ * Complete branding configuration supporting dark and/or light modes.
293
+ *
294
+ * At least one mode must be provided (dark, light, or both).
295
+ */
296
+ export type BrandingConfig = {
297
+ dark: BrandingModeConfig;
298
+ light?: BrandingModeConfig;
299
+ } | {
300
+ dark?: BrandingModeConfig;
301
+ light: BrandingModeConfig;
302
+ };
267
303
  export {};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './components';
2
2
  export * from './config/factory';
3
+ export * from './config/resolveBrandingTheme';
3
4
  export * from './config/types';
4
5
  export * from './hooks';
5
6
  export * from './types';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./components/index.js";
2
2
  export * from "./config/factory.js";
3
+ export * from "./config/resolveBrandingTheme.js";
3
4
  export * from "./config/types.js";
4
5
  export * from "./hooks/index.js";
5
6
  export * from "./types/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scality/data-browser-library",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "A modular React component library for browsing S3 buckets and objects",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "react-hook-form": "^7.48.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@scality/core-ui": ">=0.197.0",
43
+ "@scality/core-ui": ">=0.198.0",
44
44
  "react": ">=18.0.0",
45
45
  "react-dom": ">=18.0.0",
46
46
  "react-router": ">=7.1.3",