@gcsa/console-react 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcsa/console-react",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "GCSA console UI — React components (Ark UI + TanStack + GCSA styles)",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -34,8 +34,8 @@
34
34
  "@tanstack/react-form": "^1.12.2",
35
35
  "@tanstack/react-table": "^8.21.3",
36
36
  "zod": "^3.24.4",
37
- "@gcsa/console-styles": "0.1.2",
38
- "@gcsa/console-core": "0.1.2"
37
+ "@gcsa/console-core": "0.1.3",
38
+ "@gcsa/console-styles": "0.1.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": "^18.0.0 || ^19.0.0",
@@ -76,6 +76,7 @@ export function GcsaCascader({
76
76
  arrow={false}
77
77
  portalled={portalled}
78
78
  gutter={6}
79
+ fitViewport
79
80
  className="gcsa-cascader-popover"
80
81
  trigger={
81
82
  <button
@@ -22,6 +22,8 @@ export type GcsaPopoverProps = {
22
22
  /** 受控开关;不传则为非受控 */
23
23
  open?: boolean;
24
24
  onOpenChange?: (open: boolean) => void;
25
+ /** 启用 size middleware,写入 --reference-width */
26
+ fitViewport?: boolean;
25
27
  /** 预设尺寸;与 minWidth / maxWidth / width 可同时使用,inline 优先生效 */
26
28
  size?: GcsaPopoverSize;
27
29
  /** 面板宽度,如 560 或 '560px' */
@@ -46,6 +48,7 @@ export function GcsaPopover({
46
48
  gutter = 8,
47
49
  open,
48
50
  onOpenChange,
51
+ fitViewport = false,
49
52
  size = 'default',
50
53
  width,
51
54
  minWidth,
@@ -107,7 +110,7 @@ export function GcsaPopover({
107
110
  open={resolvedOpen}
108
111
  lazyMount
109
112
  unmountOnExit
110
- positioning={createGcsaPopupPositioning({ placement, gutter })}
113
+ positioning={createGcsaPopupPositioning({ placement, gutter, fitViewport })}
111
114
  onOpenChange={details => handleOpenChange(details.open)}
112
115
  >
113
116
  <Popover.Trigger asChild>{triggerNode}</Popover.Trigger>
@@ -1,5 +1,6 @@
1
- import { useMemo, useState } from 'react';
1
+ import { useMemo, useState, type KeyboardEvent } from 'react';
2
2
  import type { GcsaTransferOption } from '@gcsa/console-core';
3
+ import { GcsaCheckbox } from './GcsaCheckbox';
3
4
  import { GcsaInput } from './GcsaInput';
4
5
  import { useGcsaLocaleMessages } from './hooks/useGcsaLocale';
5
6
 
@@ -29,6 +30,7 @@ export function GcsaTransfer({
29
30
  const [targetSelected, setTargetSelected] = useState<string[]>([]);
30
31
  const [sourceQuery, setSourceQuery] = useState('');
31
32
  const [targetQuery, setTargetQuery] = useState('');
33
+
32
34
  const availableItems = useMemo(
33
35
  () =>
34
36
  options.filter(
@@ -38,6 +40,7 @@ export function GcsaTransfer({
38
40
  ),
39
41
  [options, sourceQuery, value]
40
42
  );
43
+
41
44
  const targetItems = useMemo(
42
45
  () =>
43
46
  options.filter(
@@ -47,18 +50,28 @@ export function GcsaTransfer({
47
50
  ),
48
51
  [options, targetQuery, value]
49
52
  );
53
+
50
54
  const update = (next: string[]) => {
51
55
  onValueChange?.(next);
52
56
  onChange?.(next);
53
57
  setSourceSelected([]);
54
58
  setTargetSelected([]);
55
59
  };
56
- const toggle = (key: string, side: 'source' | 'target') => {
60
+
61
+ const setSelected = (key: string, side: 'source' | 'target', checked: boolean) => {
62
+ const setter = side === 'source' ? setSourceSelected : setTargetSelected;
63
+ setter(current => {
64
+ if (checked) return current.includes(key) ? current : [...current, key];
65
+ return current.filter(item => item !== key);
66
+ });
67
+ };
68
+
69
+ const onItemActivate = (item: GcsaTransferOption, side: 'source' | 'target') => {
70
+ if (disabled || item.disabled) return;
57
71
  const current = side === 'source' ? sourceSelected : targetSelected;
58
- const next = current.includes(key) ? current.filter(item => item !== key) : [...current, key];
59
- if (side === 'source') setSourceSelected(next);
60
- else setTargetSelected(next);
72
+ setSelected(item.value, side, !current.includes(item.value));
61
73
  };
74
+
62
75
  const panel = (items: GcsaTransferOption[], title: string, side: 'source' | 'target') => (
63
76
  <section className="gcsa-transfer__panel" aria-label={title}>
64
77
  <header className="gcsa-transfer__header">
@@ -77,21 +90,35 @@ export function GcsaTransfer({
77
90
  {items.length ? (
78
91
  items.map(item => {
79
92
  const selected = (side === 'source' ? sourceSelected : targetSelected).includes(item.value);
93
+ const itemDisabled = disabled || Boolean(item.disabled);
94
+ const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
95
+ if (event.key === 'Enter' || event.key === ' ') {
96
+ event.preventDefault();
97
+ onItemActivate(item, side);
98
+ }
99
+ };
100
+ const onRowClick = () => onItemActivate(item, side);
80
101
  return (
81
- <button
102
+ <div
82
103
  key={item.value}
83
- type="button"
84
104
  className={`gcsa-transfer__item gcsa-focus-ring${selected ? ' gcsa-transfer__item--selected' : ''}`}
85
- disabled={disabled || item.disabled}
86
105
  role="option"
106
+ tabIndex={itemDisabled ? -1 : 0}
87
107
  aria-selected={selected}
88
- onClick={() => toggle(item.value, side)}
108
+ aria-disabled={itemDisabled || undefined}
109
+ onClick={onRowClick}
110
+ onKeyDown={onKeyDown}
89
111
  >
90
- <span className="gcsa-transfer__check" aria-hidden="true">
91
- {selected ? '✓' : ''}
112
+ <span className="gcsa-transfer__check-wrap" onClick={event => event.stopPropagation()}>
113
+ <GcsaCheckbox
114
+ checked={selected}
115
+ disabled={itemDisabled}
116
+ ariaLabel={item.label}
117
+ onCheckedChange={checked => setSelected(item.value, side, checked)}
118
+ />
92
119
  </span>
93
- <span>{item.label}</span>
94
- </button>
120
+ <span className="gcsa-transfer__label">{item.label}</span>
121
+ </div>
95
122
  );
96
123
  })
97
124
  ) : (
@@ -100,6 +127,7 @@ export function GcsaTransfer({
100
127
  </div>
101
128
  </section>
102
129
  );
130
+
103
131
  return (
104
132
  <div className={`gcsa-transfer${disabled ? ' gcsa-transfer--disabled' : ''}`}>
105
133
  {panel(availableItems, resolvedSourceTitle, 'source')}
@@ -152,7 +152,7 @@ export function GcsaUpload({
152
152
  onDrop={onDrop}
153
153
  >
154
154
  <span className="gcsa-upload__icon" aria-hidden="true">
155
- <GcsaIcon name="plus" />
155
+ <GcsaIcon name="upload" />
156
156
  </span>
157
157
  <span className="gcsa-upload__title">{resolvedButtonLabel}</span>
158
158
  {resolvedHint ? <p className="gcsa-upload__hint">{resolvedHint}</p> : null}
@@ -18,6 +18,7 @@ export const GCSA_UI_ICON_NAMES = [
18
18
  'close',
19
19
  'menu',
20
20
  'plus',
21
+ 'upload',
21
22
  'resultEmpty'
22
23
  ] as const;
23
24
 
@@ -1,6 +1,7 @@
1
1
  import { GCSA_ICON_NAMES, type GcsaIconName } from './iconNames';
2
2
  import chevronLeftSvg from './svg/chevron-left.svg?raw';
3
3
  import menuSvg from './svg/menu.svg?raw';
4
+ import uploadSvg from './svg/upload.svg?raw';
4
5
 
5
6
  const svgModules = import.meta.glob('./svg/*.svg', {
6
7
  query: '?raw',
@@ -22,7 +23,8 @@ const registry = {
22
23
  Object.entries(svgModules).map(([path, svg]) => [toIconKey(fileStem(path)), svg])
23
24
  ),
24
25
  menu: menuSvg,
25
- chevronLeft: chevronLeftSvg
26
+ chevronLeft: chevronLeftSvg,
27
+ upload: uploadSvg
26
28
  } as Record<GcsaIconName, string>;
27
29
 
28
30
  for (const name of GCSA_ICON_NAMES) {
@@ -1 +1,11 @@
1
- <svg viewBox="0 0 29 36" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path opacity="0.3" d="M2 0C0.895432 0 0 0.895431 0 2V34C0 35.1046 0.895431 36 2 36H27C28.1046 36 29 35.1046 29 34V11H17.9511V0H2Z" fill="currentColor"/><path d="M14.1667 23.5V21.8333H15C16.4167 21.8333 17.5 20.75 17.5 19.3333V18.5C17.5 17.0833 16.4167 16 15 16C13.5833 16 12.5 17.0833 12.5 18.5V19.3333H10.8333V18.5C10.8333 16.1667 12.6667 14.3333 15 14.3333C17.3333 14.3333 19.1667 16.1667 19.1667 18.5V19.3333C19.1667 21.3333 17.75 23 15.8333 23.4167V25.1667H14.1667V23.5ZM14.1667 26H15.8333V27.6667H14.1667V26Z" fill="currentColor"/><path opacity="0.3" d="M20.0234 0V9H29L20.0234 0Z" fill="currentColor"/></svg>
1
+ <svg width="66" height="66" viewBox="0 0 66 66" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <ellipse cx="33" cy="53" rx="33" ry="8" fill="#00BBCC" fill-opacity="0.1"/>
3
+ <path d="M35 19.5L12 26V49L35 47.5V19.5Z" fill="#00BBCC" fill-opacity="0.3"/>
4
+ <path d="M55 46.5V25L35 19.5V42L55 46.5Z" fill="#00BBCC" fill-opacity="0.4"/>
5
+ <path d="M54.4179 7.58906L57 9.00955L54.4106 10.4173L52.9909 13L51.5821 10.4109L49 8.99045L51.5894 7.5827L53.0091 5L54.4179 7.58906Z" fill="#00BBCC" fill-opacity="0.4"/>
6
+ <path d="M32 56.0806V32L55 25V48L32 56.0806Z" fill="#7CDBE4"/>
7
+ <path d="M32 56.0806V32L12 26V49L32 56.0806Z" fill="#97E3EA"/>
8
+ <path d="M25 7.5L35 19.5L12 26L2 14L25 7.5Z" fill="#CCF1F5"/>
9
+ <circle cx="35" cy="17.5" r="9" fill="#00BBCC"/>
10
+ <path d="M34.4 19.875V18.625H35C36.02 18.625 36.8 17.8125 36.8 16.75V16.125C36.8 15.0625 36.02 14.25 35 14.25C33.98 14.25 33.2 15.0625 33.2 16.125V16.75H32V16.125C32 14.375 33.32 13 35 13C36.68 13 38 14.375 38 16.125V16.75C38 18.25 36.98 19.5 35.6 19.8125V21.125H34.4V19.875ZM34.4 21.75H35.6V23H34.4V21.75Z" fill="white"/>
11
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
2
+ <path d="M32.5 20.2C31.4 15.7 27.5 12.5 22.8 12.5C18.6 12.5 15 15 13.5 18.6C10 19.1 7.5 22.1 7.5 25.7C7.5 29.6 10.7 32.8 14.6 32.8H32.2C35.6 32.8 38.5 30 38.5 26.5C38.5 23.3 35.9 20.7 32.5 20.2Z" stroke="currentColor" stroke-width="1.75" stroke-linejoin="round"/>
3
+ <path d="M24 22.5V36" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
4
+ <path d="M19.5 27L24 22.5L28.5 27" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
5
+ </svg>