@godxjp/ui 31.7.0 → 31.9.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 (46) hide show
  1. package/agent/START-HERE.md +4 -4
  2. package/agent/components/ColorPicker.json +16 -8
  3. package/agent/components/Select.json +22 -0
  4. package/agent/components/SplitPane.json +8 -1
  5. package/agent/components.json +46 -9
  6. package/agent/index.json +4 -4
  7. package/agent/llms.txt +5 -5
  8. package/agent/tokens.json +24 -0
  9. package/dist/components/data-entry/chat-composer.js +11 -4
  10. package/dist/components/data-entry/chat-suggestion.js +2 -0
  11. package/dist/components/data-entry/color-picker.d.ts +2 -2
  12. package/dist/components/data-entry/color-picker.js +150 -37
  13. package/dist/components/data-entry/command-palette.js +2 -1
  14. package/dist/components/data-entry/date-picker.js +2 -0
  15. package/dist/components/data-entry/index.d.ts +1 -1
  16. package/dist/components/data-entry/number-input.js +2 -1
  17. package/dist/components/data-entry/search-select.js +55 -17
  18. package/dist/components/data-entry/select.js +1 -1
  19. package/dist/components/data-entry/time-picker.js +3 -0
  20. package/dist/components/feedback/non-modal-layer.js +2 -1
  21. package/dist/components/general/typography.js +5 -1
  22. package/dist/components/layout/split-pane.d.ts +16 -1
  23. package/dist/components/layout/split-pane.js +7 -2
  24. package/dist/components/navigation/pagination.js +2 -1
  25. package/dist/components/ui/tag-input.js +2 -1
  26. package/dist/contracts/measurement.json +1 -1
  27. package/dist/i18n/messages/en.json +4 -2
  28. package/dist/i18n/messages/ja.json +4 -2
  29. package/dist/i18n/messages/vi.json +4 -2
  30. package/dist/lib/ime.d.ts +12 -0
  31. package/dist/lib/ime.js +7 -0
  32. package/dist/props/components/data-entry.prop.d.ts +66 -1
  33. package/dist/props/components/index.d.ts +1 -1
  34. package/dist/props/registry.d.ts +15 -0
  35. package/dist/props/registry.js +17 -0
  36. package/dist/styles/control.css +79 -0
  37. package/dist/styles/layers.json +1 -1
  38. package/dist/styles/layout.css +45 -0
  39. package/dist/tokens/components/control.css +8 -0
  40. package/docs/data-entry/color-picker.tsx +72 -0
  41. package/docs/data-entry/select.tsx +38 -0
  42. package/docs/i18n/messages/en.json +28 -0
  43. package/docs/i18n/messages/ja.json +28 -0
  44. package/docs/i18n/messages/vi.json +28 -0
  45. package/docs/layout/split-pane.tsx +65 -0
  46. package/package.json +2 -2
@@ -56,7 +56,8 @@
56
56
  },
57
57
  "colorPicker": {
58
58
  "ariaLabel": "色を選択",
59
- "hexLabel": "カラーコード(HEX)"
59
+ "hexLabel": "カラーコード(HEX)",
60
+ "presetEmpty": "空"
60
61
  },
61
62
  "upload": {
62
63
  "dropzoneLabel": "ファイルをアップロード",
@@ -123,7 +124,8 @@
123
124
  "clear": "選択をクリア",
124
125
  "loading": "読み込み中…",
125
126
  "empty": "該当なし",
126
- "error": "選択肢を読み込めませんでした"
127
+ "error": "選択肢を読み込めませんでした",
128
+ "create": "「{value}」を作成"
127
129
  },
128
130
  "selection": {
129
131
  "overflow": "他{count}件"
@@ -56,7 +56,8 @@
56
56
  },
57
57
  "colorPicker": {
58
58
  "ariaLabel": "Chọn màu",
59
- "hexLabel": "Mã màu hex"
59
+ "hexLabel": "Mã màu hex",
60
+ "presetEmpty": "Trống"
60
61
  },
61
62
  "upload": {
62
63
  "dropzoneLabel": "Tải file lên",
@@ -123,7 +124,8 @@
123
124
  "clear": "Xóa lựa chọn",
124
125
  "loading": "Đang tải…",
125
126
  "empty": "Không có kết quả",
126
- "error": "Không tải được lựa chọn"
127
+ "error": "Không tải được lựa chọn",
128
+ "create": "Tạo “{value}”"
127
129
  },
128
130
  "selection": {
129
131
  "overflow": "+{count}"
@@ -0,0 +1,12 @@
1
+ import type * as React from "react";
2
+ /**
3
+ * TRUE WHILE AN IME IS COMPOSING (gh#1054). Between `compositionstart` and `compositionend` a
4
+ * Japanese / Chinese / Korean IME holds a CANDIDATE, and the Enter that confirms the conversion is
5
+ * delivered as an ordinary `keydown` — acting on it creates a tag, submits a form or picks an option
6
+ * from a half-typed reading. Every Enter (and Escape/Tab) handler in the kit asks this first.
7
+ *
8
+ * `isComposing` is the standards answer; `keyCode === 229` is the legacy one some browsers
9
+ * (Safari after `compositionend`, older Chromium on Windows) still report instead. Accepts a React
10
+ * synthetic event or a native `KeyboardEvent`.
11
+ */
12
+ export declare function isImeComposing(event: React.KeyboardEvent | KeyboardEvent): boolean;
@@ -0,0 +1,7 @@
1
+ function isImeComposing(event) {
2
+ const native = "nativeEvent" in event ? event.nativeEvent : event;
3
+ return native.isComposing || native.keyCode === 229;
4
+ }
5
+ export {
6
+ isImeComposing
7
+ };
@@ -1114,6 +1114,39 @@ export type ColorPickerProp = FieldA11yProps & {
1114
1114
  className?: ClassNameProp;
1115
1115
  id?: IdProp;
1116
1116
  showHexInput?: boolean;
1117
+ /**
1118
+ * antd `presets` (gh#1055) — named groups of fixed swatches under the picker. Each group is a
1119
+ * collapsible section (antd renders a ghost `Collapse`); `defaultOpen` (default `true`) is its
1120
+ * initial state and `key` its stable identity (falls back to the index). Each swatch is a radio
1121
+ * named by its hex; the one equal to `value` (case- and shorthand-insensitive) is checked.
1122
+ * Colours are hex strings (`#rgb`/`#rrggbb`) because the value is — antd also takes a colour
1123
+ * object or a gradient, which this picker has no model for.
1124
+ */
1125
+ presets?: ColorPickerPresetProp[];
1126
+ /**
1127
+ * antd `panelRender` — replace the picker's body. `panel` is the default body (the swatch + hex
1128
+ * row, then the presets); `extra.components` hands back each part on its own, so
1129
+ * `panelRender={(_, { components: { Presets } }) => <Presets />}` is the PRESETS-ONLY mode —
1130
+ * the palette with no free-colour entry (antd's own recipe for it). The hidden `name` input
1131
+ * is outside the panel and always submits.
1132
+ */
1133
+ panelRender?: (panel: React.ReactNode, extra: {
1134
+ components: {
1135
+ Picker: React.FC;
1136
+ Presets: React.FC;
1137
+ };
1138
+ }) => React.ReactNode;
1139
+ };
1140
+ /** One `ColorPicker presets` group — antd `PresetsItem` (gh#1055). */
1141
+ export type ColorPickerPresetProp = {
1142
+ /** Group heading; also names the group's radiogroup. */
1143
+ label: LabelProp;
1144
+ /** Hex colours (`#rgb` / `#rrggbb`), in display order. Empty → antd's "Empty" line. */
1145
+ colors: string[];
1146
+ /** Initial expanded state of the group (antd default `true`). */
1147
+ defaultOpen?: DefaultOpenProp;
1148
+ /** Stable identity of the group (antd `key`); defaults to its index. */
1149
+ key?: React.Key;
1117
1150
  };
1118
1151
  /** A SearchSelect option row. `group` buckets it under an optgroup-style heading. */
1119
1152
  export type SearchSelectOptionProp = {
@@ -1503,6 +1536,14 @@ export type SearchSelectBaseProp = {
1503
1536
  * every call site that has ever relied on the default expects a label search.
1504
1537
  */
1505
1538
  optionFilterProp?: "label" | "value" | "sublabel";
1539
+ /**
1540
+ * Match typed text case-SENSITIVELY (gh#1053). Default `false`: the built-in filter, the
1541
+ * `mode="tags"` create row and a `tokenSeparators` run all compare case-folded, so typing "bug"
1542
+ * when "Bug" exists finds "Bug" and never offers a duplicate. antd's filter folds case too; its
1543
+ * create row compares exactly, which is the deviation `true` restores. A custom `filterOption`
1544
+ * owns its own comparison and is not affected.
1545
+ */
1546
+ caseSensitive?: boolean;
1506
1547
  };
1507
1548
  /**
1508
1549
  * Single-select (the default): one `string` in, one `string` out — `""` means nothing selected.
@@ -1578,7 +1619,7 @@ export type SearchSelectMultipleProp = {
1578
1619
  * a token only counts when it matches a row; in `mode="tags"` it is accepted as it stands.
1579
1620
  */
1580
1621
  tokenSeparators?: string[];
1581
- };
1622
+ } & SelectTagsCreateProp;
1582
1623
  /**
1583
1624
  * antd `labelInValue`, single. The value carries its own label, so a screen that never sees the
1584
1625
  * option list can still render what was picked.
@@ -1612,6 +1653,30 @@ export type SelectLabelInValueMultipleProp = {
1612
1653
  index: number;
1613
1654
  disabled: boolean;
1614
1655
  }) => React.ReactNode;
1656
+ } & SelectTagsCreateProp;
1657
+ /**
1658
+ * `mode="tags"` — how typed text becomes a NEW value (gh#1052). antd has no equivalent of these
1659
+ * three: its create row is the bare text and nothing reports that a value was invented. Ignored in
1660
+ * `mode="multiple"`, which never creates.
1661
+ */
1662
+ export type SelectTagsCreateProp = {
1663
+ /**
1664
+ * Whether typed text may become a new value. `false` (or a predicate returning `false` for the
1665
+ * text) hides the create row and drops unknown `tokenSeparators` tokens — values already held
1666
+ * stay listed so they can still be removed. Default `true`.
1667
+ */
1668
+ allowCreate?: boolean | ((text: string) => boolean);
1669
+ /**
1670
+ * The create row's content. Default: the localised `Create "{text}"` (「{text}」を作成). The
1671
+ * value committed is the text itself either way; this only changes what the row says.
1672
+ */
1673
+ createLabel?: (text: string) => React.ReactNode;
1674
+ /**
1675
+ * Fired when a value that is NOT one of the options joins the selection — from the create row,
1676
+ * Enter on it, or a `tokenSeparators` run. Fires beside `onSelect`/`onValueChange`, so a consumer
1677
+ * can tell invented text apart from an option id.
1678
+ */
1679
+ onCreate?: (text: string) => void;
1615
1680
  };
1616
1681
  /** @see Select — the searchable engine. Single by default; `mode="multiple"` switches the shape. */
1617
1682
  export type SearchSelectProp = SearchSelectBaseProp & (SearchSelectSingleProp | SearchSelectMultipleProp);
@@ -1,6 +1,6 @@
1
1
  export type { AppLauncherApp, AppLauncherGroup, AppLauncherLabels, AppLauncherProp, AppShellProp, AuthFooterProp, AuthIdentityProp, OrgSwitcherLabels, OrgSwitcherOrganization, OrgSwitcherProp, DragAxisProp, DragBoundsProp, DraggablePanelLabels, DraggablePanelPlacementProp, DraggablePanelPositionProp, DraggablePanelProp, PageContainerExtraProp, PageContainerProp, PageInsetProp, SidebarItemProp, SidebarProductProp, SidebarProp, SidebarSectionProp, TopbarProp, } from "./layout.prop.js";
2
2
  export type { ButtonProp } from "./general.prop.js";
3
- export type { CommandProp, InputProp, TextareaProp, FormFieldProp, SearchInputProp, CheckboxProp, CheckboxGroupProp, ChoiceOptionProp, RadioProp, RadioGroupProp, SwitchProp, SliderProp, CalendarProp, DatePickerProp, TimePickerProp, ColorPickerProp, UploadProp, UploadFileItemProp, UploadVariantProp, TreeOptionProp, TreeFieldNamesProp, CascaderProp, TreeSelectProp, ShowCheckedStrategyProp, TransferProp, TransferItemProp, } from "./data-entry.prop.js";
3
+ export type { CommandProp, InputProp, TextareaProp, FormFieldProp, SearchInputProp, CheckboxProp, CheckboxGroupProp, ChoiceOptionProp, RadioProp, RadioGroupProp, SwitchProp, SliderProp, CalendarProp, DatePickerProp, TimePickerProp, ColorPickerProp, ColorPickerPresetProp, UploadProp, UploadFileItemProp, UploadVariantProp, TreeOptionProp, TreeFieldNamesProp, CascaderProp, TreeSelectProp, ShowCheckedStrategyProp, TransferProp, TransferItemProp, } from "./data-entry.prop.js";
4
4
  export type { AvatarProp, EmptyStateProp, DescriptionsProp, DescriptionsItemProp, BadgeProp, DataTableProp, QrCodeProp, } from "./data-display.prop.js";
5
5
  export type { ChartDatum, ChartSeriesProp, LineChartProp, BarChartProp, AreaChartProp, PieChartProp, } from "./charts.prop.js";
6
6
  export type { AlertQueryErrorProp, AuthExpiryProviderProp, AlertProp, DialogProp, DialogContentProp, AlertTitleProp, AlertContentProp, AlertDescriptionProp, AlertActionsProp, SheetResponsiveProp, SkeletonRowsProp, } from "./feedback.prop.js";
@@ -1744,6 +1744,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
1744
1744
  readonly file: "components/data-entry.prop.ts";
1745
1745
  readonly vocabulary: readonly ["ValueProp", "OnValueChangeProp", "DisabledProp", "IdProp"];
1746
1746
  };
1747
+ readonly ColorPickerPresetProp: {
1748
+ readonly group: "data-entry";
1749
+ readonly file: "components/data-entry.prop.ts";
1750
+ readonly vocabulary: readonly ["LabelProp", "DefaultOpenProp"];
1751
+ };
1747
1752
  readonly SearchSelectBaseProp: {
1748
1753
  readonly group: "data-entry";
1749
1754
  readonly file: "components/data-entry.prop.ts";
@@ -1762,6 +1767,16 @@ export declare const COMPONENT_PROP_REGISTRY: {
1762
1767
  readonly vocabulary: readonly ["ValueProp", "DefaultValueProp", "OnValueChangeProp", "MaxTagCountProp", "MaxTagPlaceholderProp"];
1763
1768
  readonly note: 'Internal — the `mode="multiple"` value shape of `<Select options>`; use Select.';
1764
1769
  };
1770
+ readonly SelectTagsCreateProp: {
1771
+ readonly group: "data-entry";
1772
+ readonly file: "components/data-entry.prop.ts";
1773
+ readonly vocabulary: readonly [{
1774
+ readonly field: "onCreate";
1775
+ readonly local: true;
1776
+ readonly reason: "An EVENT, not a value channel: fires once per invented text as it joins the selection, beside onSelect/onValueChange, so a consumer can tell typed-new text from an option id (antd has no equivalent, gh#1052).";
1777
+ }];
1778
+ readonly note: 'Internal — `mode="tags"` create-row knobs (allowCreate / createLabel / onCreate, gh#1052) shared by both multi-value shapes of `<Select options>`.';
1779
+ };
1765
1780
  readonly SearchSelectProp: {
1766
1781
  readonly group: "data-entry";
1767
1782
  readonly file: "components/data-entry.prop.ts";
@@ -2013,6 +2013,11 @@ const COMPONENT_PROP_REGISTRY = {
2013
2013
  file: "components/data-entry.prop.ts",
2014
2014
  vocabulary: ["ValueProp", "OnValueChangeProp", "DisabledProp", "IdProp"]
2015
2015
  },
2016
+ ColorPickerPresetProp: {
2017
+ group: "data-entry",
2018
+ file: "components/data-entry.prop.ts",
2019
+ vocabulary: ["LabelProp", "DefaultOpenProp"]
2020
+ },
2016
2021
  SearchSelectBaseProp: {
2017
2022
  group: "data-entry",
2018
2023
  file: "components/data-entry.prop.ts",
@@ -2054,6 +2059,18 @@ const COMPONENT_PROP_REGISTRY = {
2054
2059
  ],
2055
2060
  note: 'Internal \u2014 the `mode="multiple"` value shape of `<Select options>`; use Select.'
2056
2061
  },
2062
+ SelectTagsCreateProp: {
2063
+ group: "data-entry",
2064
+ file: "components/data-entry.prop.ts",
2065
+ vocabulary: [
2066
+ {
2067
+ field: "onCreate",
2068
+ local: true,
2069
+ reason: "An EVENT, not a value channel: fires once per invented text as it joins the selection, beside onSelect/onValueChange, so a consumer can tell typed-new text from an option id (antd has no equivalent, gh#1052)."
2070
+ }
2071
+ ],
2072
+ note: 'Internal \u2014 `mode="tags"` create-row knobs (allowCreate / createLabel / onCreate, gh#1052) shared by both multi-value shapes of `<Select options>`.'
2073
+ },
2057
2074
  SearchSelectProp: {
2058
2075
  group: "data-entry",
2059
2076
  file: "components/data-entry.prop.ts",
@@ -738,6 +738,85 @@
738
738
  text-transform: uppercase;
739
739
  }
740
740
 
741
+ .ui-color-picker[data-panel] {
742
+ flex-direction: column;
743
+ align-items: stretch;
744
+ }
745
+
746
+ .ui-color-picker-picker {
747
+ display: flex;
748
+ align-items: center;
749
+ gap: var(--control-gap);
750
+ }
751
+
752
+ .ui-color-picker-presets {
753
+ display: flex;
754
+ flex-direction: column;
755
+ gap: var(--color-picker-presets-gap);
756
+ }
757
+
758
+ .ui-color-picker-presets-trigger {
759
+ display: inline-flex;
760
+ align-items: center;
761
+ gap: var(--space-1);
762
+ min-block-size: var(--touch-target-min);
763
+ color: hsl(var(--muted-foreground));
764
+ font-size: var(--color-picker-presets-font-size);
765
+ text-align: start;
766
+ }
767
+
768
+ .ui-color-picker-presets-chevron {
769
+ width: var(--control-icon-size);
770
+ height: var(--control-icon-size);
771
+ flex-shrink: 0;
772
+ transition: rotate 150ms;
773
+ }
774
+
775
+ .ui-color-picker-presets-trigger[data-state="closed"] .ui-color-picker-presets-chevron {
776
+ rotate: -90deg;
777
+ }
778
+
779
+ .ui-color-picker-presets-trigger[data-state="closed"]:dir(rtl) .ui-color-picker-presets-chevron {
780
+ rotate: 90deg;
781
+ }
782
+
783
+ .ui-color-picker-presets-items {
784
+ display: flex;
785
+ flex-wrap: wrap;
786
+ gap: var(--color-picker-preset-gap);
787
+ padding-block: var(--space-1);
788
+ }
789
+
790
+ .ui-color-picker-presets-color {
791
+ display: inline-block;
792
+ flex-shrink: 0;
793
+ inline-size: var(--color-picker-preset-size);
794
+ block-size: var(--color-picker-preset-size);
795
+ border: var(--stroke-hairline) solid hsl(var(--input) / var(--input-alpha, 100%));
796
+ border-radius: var(--control-radius);
797
+ background-color: var(--color-picker-preset-color, transparent);
798
+ cursor: pointer;
799
+ forced-color-adjust: none;
800
+ }
801
+
802
+ .ui-color-picker-presets-color[data-selected] {
803
+ box-shadow:
804
+ 0 0 0 var(--stroke-md) hsl(var(--background)),
805
+ 0 0 0 calc(var(--stroke-md) * 2) hsl(var(--foreground));
806
+ }
807
+
808
+ .ui-color-picker-presets-color[data-disabled] {
809
+ cursor: not-allowed;
810
+ opacity: var(--disabled-opacity);
811
+ }
812
+
813
+ .ui-color-picker-presets-empty {
814
+ display: block;
815
+ padding-block: var(--space-1);
816
+ color: hsl(var(--muted-foreground));
817
+ font-size: var(--color-picker-presets-font-size);
818
+ }
819
+
741
820
  .ui-query-refetch-icon[data-fetching="true"] {
742
821
  animation: spin 1s linear infinite;
743
822
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.7.0",
2
+ "version": "31.9.0",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -608,6 +608,10 @@
608
608
  min-block-size: 0;
609
609
  }
610
610
 
611
+ .ui-split-pane[data-fill="true"][data-stack-order="aside-first"]:not([data-aside="closed"]) {
612
+ grid-template-rows: auto minmax(0, 1fr);
613
+ }
614
+
611
615
  .ui-resizable-panel-group {
612
616
  display: flex;
613
617
  min-height: 0;
@@ -662,12 +666,53 @@
662
666
  .ui-split-pane[data-aside-width="md"] {
663
667
  grid-template-columns: minmax(0, 1fr) 22rem;
664
668
  }
669
+
670
+ .ui-split-pane[data-stack-order="aside-first"]:is(
671
+ [data-aside-width="sm"],
672
+ [data-aside-width="md"]
673
+ )
674
+ > .ui-split-pane-main {
675
+ grid-column: 1;
676
+ grid-row: 1;
677
+ }
678
+
679
+ .ui-split-pane[data-stack-order="aside-first"]:is(
680
+ [data-aside-width="sm"],
681
+ [data-aside-width="md"]
682
+ )
683
+ > .ui-split-pane-aside {
684
+ grid-column: 2;
685
+ grid-row: 1;
686
+ }
687
+
688
+ .ui-split-pane[data-fill="true"][data-stack-order="aside-first"]:is(
689
+ [data-aside-width="sm"],
690
+ [data-aside-width="md"]
691
+ ):not([data-aside="closed"]) {
692
+ grid-template-rows: minmax(0, 1fr);
693
+ }
665
694
  }
666
695
 
667
696
  @container split-pane (min-width: 64rem) {
668
697
  .ui-split-pane[data-aside-width="lg"] {
669
698
  grid-template-columns: minmax(0, 1fr) 30rem;
670
699
  }
700
+
701
+ .ui-split-pane[data-stack-order="aside-first"][data-aside-width="lg"] > .ui-split-pane-main {
702
+ grid-column: 1;
703
+ grid-row: 1;
704
+ }
705
+
706
+ .ui-split-pane[data-stack-order="aside-first"][data-aside-width="lg"] > .ui-split-pane-aside {
707
+ grid-column: 2;
708
+ grid-row: 1;
709
+ }
710
+
711
+ .ui-split-pane[data-fill="true"][data-stack-order="aside-first"][data-aside-width="lg"]:not(
712
+ [data-aside="closed"]
713
+ ) {
714
+ grid-template-rows: minmax(0, 1fr);
715
+ }
671
716
  }
672
717
 
673
718
  .ui-split-pane-main,
@@ -157,6 +157,10 @@
157
157
 
158
158
  --color-picker-input-width: 6.5rem;
159
159
 
160
+ --color-picker-preset-size: var(--touch-target-min);
161
+ --color-picker-preset-gap: var(--space-2);
162
+ --color-picker-presets-gap: var(--space-1);
163
+
160
164
  --command-list-max-height: min(300px, 50vh);
161
165
  --command-input-padding-x: var(--space-3);
162
166
  --command-group-padding: var(--space-1);
@@ -192,6 +196,10 @@
192
196
  --font-size-xs,
193
197
  calc(var(--font-size-base) / var(--font-size-ratio))
194
198
  );
199
+ --color-picker-presets-font-size: var(
200
+ --font-size-xs,
201
+ calc(var(--font-size-base) / var(--font-size-ratio))
202
+ );
195
203
  --command-group-heading-font-size: var(
196
204
  --font-size-xs,
197
205
  calc(var(--font-size-base) / var(--font-size-ratio))
@@ -3,19 +3,33 @@ import { useState } from "react";
3
3
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
4
4
  import { ColorPicker, FormField } from "@godxjp/ui/data-entry";
5
5
  import { Text } from "@godxjp/ui/general";
6
+ import { useTranslation } from "@godxjp/ui/i18n";
6
7
  import { Flex, PageContainer } from "@godxjp/ui/layout";
7
8
 
9
+ /** A fixed tag palette (gh#1055): tags pick from it, never from free hex. */
10
+ const TAG_PALETTE = ["#dc2626", "#ea580c", "#ca8a04", "#16a34a", "#0891b2", "#2563eb", "#9333ea"];
11
+
8
12
  /**
9
13
  * ColorPicker — hex color swatch + optional editable hex input.
10
14
  * Always initialize state to a valid 3- or 6-digit hex string.
11
15
  * Use controlled mode (value + onValueChange); no uncontrolled path.
12
16
  * Composed only from real @godxjp/ui components.
17
+ *
18
+ * `presets` / `panelRender` port antd ColorPicker (gh#1055). Deviations from antd, on purpose:
19
+ * - `colors` are hex strings only — the value is hex; antd also takes colour objects/gradients.
20
+ * - There is no popover: the picker is inline, so `panelRender` replaces the inline body. The
21
+ * presets-only recipe is antd's own: `panelRender={(_, { components: { Presets } }) => <Presets />}`.
22
+ * - Each swatch is a real radio (APG radio group, named by its hex, arrow keys select); antd's
23
+ * preset blocks are click-only divs.
13
24
  */
14
25
  export default function Demo() {
15
26
  const [brandColor, setBrandColor] = useState("#2563eb");
16
27
  const [accentColor, setAccentColor] = useState("#16a34a");
17
28
  const [categoryColor, setCategoryColor] = useState("#dc2626");
18
29
  const [validatedColor, setValidatedColor] = useState("#9333ea");
30
+ const [labelColor, setLabelColor] = useState("#16a34a");
31
+ const [tagColor, setTagColor] = useState("#2563eb");
32
+ const { t } = useTranslation();
19
33
 
20
34
  return (
21
35
  <PageContainer title="ColorPicker" subtitle="カラーピッカー · スウォッチ+Hex入力">
@@ -75,6 +89,64 @@ export default function Demo() {
75
89
  </CardContent>
76
90
  </Card>
77
91
 
92
+ <Card>
93
+ <CardHeader>
94
+ <CardTitle level={2}>{t("showcase.colorPicker.presetsTitle")}</CardTitle>
95
+ <CardDescription>{t("showcase.colorPicker.presetsDescription")}</CardDescription>
96
+ </CardHeader>
97
+ <CardContent>
98
+ <FormField id="label-color" label={t("showcase.colorPicker.paletteLabel")}>
99
+ <ColorPicker
100
+ id="label-color"
101
+ value={labelColor}
102
+ onValueChange={setLabelColor}
103
+ presets={[
104
+ {
105
+ key: "recommended",
106
+ label: t("showcase.colorPicker.recommendedGroup"),
107
+ colors: TAG_PALETTE,
108
+ },
109
+ {
110
+ key: "recent",
111
+ label: t("showcase.colorPicker.recentGroup"),
112
+ colors: ["#2563eb", "#16a34a"],
113
+ defaultOpen: false,
114
+ },
115
+ ]}
116
+ />
117
+ </FormField>
118
+ </CardContent>
119
+ </Card>
120
+
121
+ <Card>
122
+ <CardHeader>
123
+ <CardTitle level={2}>{t("showcase.colorPicker.presetsOnlyTitle")}</CardTitle>
124
+ <CardDescription>{t("showcase.colorPicker.presetsOnlyDescription")}</CardDescription>
125
+ </CardHeader>
126
+ <CardContent>
127
+ <FormField
128
+ id="tag-color"
129
+ label={t("showcase.colorPicker.tagColorLabel")}
130
+ helper={t("showcase.colorPicker.tagColorHelper")}
131
+ >
132
+ <ColorPicker
133
+ id="tag-color"
134
+ name="tag_color"
135
+ value={tagColor}
136
+ onValueChange={setTagColor}
137
+ presets={[
138
+ {
139
+ key: "tags",
140
+ label: t("showcase.colorPicker.recommendedGroup"),
141
+ colors: TAG_PALETTE,
142
+ },
143
+ ]}
144
+ panelRender={(_, { components: { Presets } }) => <Presets />}
145
+ />
146
+ </FormField>
147
+ </CardContent>
148
+ </Card>
149
+
78
150
  <Card>
79
151
  <CardHeader>
80
152
  <CardTitle level={2}>Hex バリデーションと確定動作</CardTitle>
@@ -23,6 +23,7 @@ import {
23
23
  SelectValue,
24
24
  } from "@godxjp/ui/data-entry";
25
25
  import { Button } from "@godxjp/ui/general";
26
+ import { useTranslation } from "@godxjp/ui/i18n";
26
27
  import { Flex, PageContainer } from "@godxjp/ui/layout";
27
28
  import { X } from "lucide-react";
28
29
 
@@ -32,6 +33,7 @@ import { X } from "lucide-react";
32
33
  * trigger. Never a raw <select>. Composed only from real @godxjp/ui components.
33
34
  */
34
35
  export default function Demo() {
36
+ const { t, locale } = useTranslation();
35
37
  const [status, setStatus] = useState("paid");
36
38
  const [currency, setCurrency] = useState("JPY");
37
39
  const [priority, setPriority] = useState("medium");
@@ -43,6 +45,8 @@ export default function Demo() {
43
45
  const [reviewerLabel, setReviewerLabel] = useState("tanaka");
44
46
  const [prefixed, setPrefixed] = useState("JPY");
45
47
  const [tags, setTags] = useState<string[]>(["至急", "社内便"]);
48
+ const [labels, setLabels] = useState<string[]>([]);
49
+ const [created, setCreated] = useState<string[]>([]);
46
50
  // antd `labelInValue`: the record arrives carrying its own label, before any option list loads.
47
51
  const [assignedTo, setAssignedTo] = useState<
48
52
  { value: string; label: React.ReactNode } | undefined
@@ -481,6 +485,40 @@ export default function Demo() {
481
485
  </CardContent>
482
486
  </Card>
483
487
 
488
+ <Card>
489
+ <CardHeader>
490
+ <CardTitle level={2}>{t("showcase.select.createTitle")}</CardTitle>
491
+ <CardDescription>{t("showcase.select.createDescription")}</CardDescription>
492
+ </CardHeader>
493
+ <CardContent>
494
+ <FormField
495
+ id="issue-labels"
496
+ label={t("showcase.select.createField")}
497
+ helper={
498
+ created.length
499
+ ? t("showcase.select.createdHelper", {
500
+ list: new Intl.ListFormat(locale).format(created),
501
+ })
502
+ : t("showcase.select.createdNone")
503
+ }
504
+ >
505
+ <Select
506
+ id="issue-labels"
507
+ mode="tags"
508
+ value={labels}
509
+ onValueChange={setLabels}
510
+ onCreate={(text) => setCreated((previous) => [...previous, text])}
511
+ allowCreate={(text) => text.length <= 12}
512
+ options={[
513
+ { value: "bug", label: "Bug" },
514
+ { value: "feature", label: "Feature" },
515
+ { value: "docs", label: "Docs" },
516
+ ]}
517
+ />
518
+ </FormField>
519
+ </CardContent>
520
+ </Card>
521
+
484
522
  <Card>
485
523
  <CardHeader>
486
524
  <CardTitle level={2}>labelInValue · 読み込み前でも名前が出る</CardTitle>
@@ -543,6 +543,34 @@
543
543
  "longIdTitle": "Long identifiers and +N",
544
544
  "ledgerLabel": "Ledger ID",
545
545
  "ledgerHelper": "A 71-character identifier still stays inside the control"
546
+ },
547
+ "select": {
548
+ "createTitle": "Tags: create row, onCreate and case-insensitive matching",
549
+ "createDescription": "Typed text that matches no option is offered as an explicit “Create” row. onCreate reports only invented text, never an option id. Typing “bug” finds “Bug” instead of creating a duplicate (caseSensitive restores exact matching). Unlike antd, the create row is labelled and text longer than 12 characters is refused through allowCreate.",
550
+ "createField": "Labels",
551
+ "createdHelper": "Created in this session: {list}",
552
+ "createdNone": "Nothing created yet"
553
+ },
554
+ "colorPicker": {
555
+ "presetsTitle": "Tag colour from a palette",
556
+ "presetsDescription": "presets — fixed swatch groups under the picker; each group collapses, and the free picker stays available.",
557
+ "paletteLabel": "Label colour",
558
+ "recommendedGroup": "Recommended",
559
+ "recentGroup": "Recently used",
560
+ "presetsOnlyTitle": "Palette only",
561
+ "presetsOnlyDescription": "panelRender returns only Presets — people pick from the palette and cannot type an arbitrary hex.",
562
+ "tagColorLabel": "Tag colour",
563
+ "tagColorHelper": "Arrow keys move between swatches; the selected one is announced."
564
+ },
565
+ "splitPaneStackOrder": {
566
+ "title": "stackOrder — the aside first when the panes stack",
567
+ "lead": "stackOrder=\"aside-first\" puts the aside above main once the pane is too narrow to split, and first in the DOM, so Tab reaches it first too. Wide enough to split, it sits in the trailing column exactly as it does by default. The embed below is narrower than the split width, so the properties card sits above the thread.",
568
+ "propertiesTitle": "Properties",
569
+ "status": "Status",
570
+ "statusValue": "In progress",
571
+ "assignee": "Assignee",
572
+ "threadTitle": "Comments",
573
+ "threadLead": "Long threads no longer push the properties below the fold on a phone."
546
574
  }
547
575
  },
548
576
  "serviceLauncherShowcase": {
@@ -537,6 +537,34 @@
537
537
  "longIdTitle": "長い識別子と「+N」",
538
538
  "ledgerLabel": "台帳ID",
539
539
  "ledgerHelper": "71文字の識別子でも行からはみ出さない"
540
+ },
541
+ "select": {
542
+ "createTitle": "タグ: 作成行・onCreate・大文字小文字を区別しない照合",
543
+ "createDescription": "どの選択肢にも一致しない入力は「作成」行として明示されます。onCreate は新しく作られたテキストだけを通知し、選択肢の ID では発火しません。「bug」と入力すると「Bug」が見つかり、重複は作られません(caseSensitive で完全一致に戻せます)。antd と異なり作成行にはラベルが付き、12 文字を超える入力は allowCreate で拒否しています。",
544
+ "createField": "ラベル",
545
+ "createdHelper": "このセッションで作成: {list}",
546
+ "createdNone": "まだ作成されていません"
547
+ },
548
+ "colorPicker": {
549
+ "presetsTitle": "パレットからタグの色を選ぶ",
550
+ "presetsDescription": "presets — ピッカーの下に固定スウォッチのグループを表示。グループは折りたためて、自由入力も残る。",
551
+ "paletteLabel": "ラベルの色",
552
+ "recommendedGroup": "おすすめ",
553
+ "recentGroup": "最近使った色",
554
+ "presetsOnlyTitle": "パレットのみ",
555
+ "presetsOnlyDescription": "panelRender で Presets だけを返すと、パレットからの選択のみになり任意の HEX は入力できない。",
556
+ "tagColorLabel": "タグの色",
557
+ "tagColorHelper": "矢印キーでスウォッチ間を移動し、選択中の色が読み上げられる。"
558
+ },
559
+ "splitPaneStackOrder": {
560
+ "title": "stackOrder — 縦積み時にサイドを先頭へ",
561
+ "lead": "stackOrder=\"aside-first\" は、分割できない幅になったときにサイドをメインの上に置き、DOM でも先頭にするため Tab でも先に届きます。分割できる幅では既定と同じく後方の列に並びます。下の埋め込みは分割幅より狭いため、プロパティがスレッドの上に来ます。",
562
+ "propertiesTitle": "プロパティ",
563
+ "status": "ステータス",
564
+ "statusValue": "対応中",
565
+ "assignee": "担当者",
566
+ "threadTitle": "コメント",
567
+ "threadLead": "スマートフォンでも、長いスレッドがプロパティを画面外へ押し出しません。"
540
568
  }
541
569
  },
542
570
  "serviceLauncherShowcase": {