@nordmanai/nif 0.2.11 → 0.2.13

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 (28) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2735 -2713
  3. package/dist/styles.css +1 -1
  4. package/dist/ui/components/nm-button/nm-button.d.ts +3 -0
  5. package/dist/ui/components/nm-composer/nm-composer.d.ts +3 -0
  6. package/dist/ui/components/nm-sidebar/nm-sidebar.d.ts +8 -2
  7. package/dist/ui/components/nm-small-icon-button/nm-small-icon-button.d.ts +5 -0
  8. package/dist/ui/components/nordman-icon-button/nordman-icon-button.d.ts +3 -0
  9. package/dist/ui/config/nm-button.d.ts +1 -0
  10. package/dist/ui/config/nm-composer.d.ts +2 -4
  11. package/dist/ui/config/nm-icon-button.d.ts +4 -2
  12. package/dist/ui/config/nm-shape.d.ts +11 -0
  13. package/package.json +1 -1
  14. package/registry/index.json +28 -1
  15. package/registry/ui/components/nm-button/nm-button.css +3 -0
  16. package/registry/ui/components/nm-button/nm-button.tsx +5 -1
  17. package/registry/ui/components/nm-composer/nm-composer.css +9 -2
  18. package/registry/ui/components/nm-composer/nm-composer.tsx +6 -1
  19. package/registry/ui/components/nm-sidebar/nm-sidebar.css +2 -1
  20. package/registry/ui/components/nm-sidebar/nm-sidebar.tsx +22 -3
  21. package/registry/ui/components/nm-small-icon-button/nm-small-icon-button.tsx +5 -0
  22. package/registry/ui/components/nordman-icon-button/nordman-icon-button.css +3 -0
  23. package/registry/ui/components/nordman-icon-button/nordman-icon-button.tsx +7 -1
  24. package/registry/ui/config/nm-button.ts +1 -0
  25. package/registry/ui/config/nm-composer.ts +3 -0
  26. package/registry/ui/config/nm-icon-button.ts +11 -5
  27. package/registry/ui/config/nm-shape.ts +11 -0
  28. package/registry/ui/styles/tokens.css +2 -0
@@ -1,12 +1,15 @@
1
1
  import { type ButtonHTMLAttributes, type MouseEvent, type ReactNode } from 'react';
2
2
  import type { NmIconId } from '../../config/nm-icons.ts';
3
3
  import { NM_BUTTON_SIZES, NM_BUTTON_TONES, type NmButtonSize, type NmButtonTone } from '../../config/nm-button.ts';
4
+ import { type NmControlShape } from '../../config/nm-shape.ts';
4
5
  export type NmButtonProps = {
5
6
  label: string;
6
7
  /** Content of the button in place of the icon and label, for a button that holds more than text. The label is still its accessible name when icon only. */
7
8
  children?: ReactNode;
8
9
  tone: NmButtonTone;
9
10
  size?: NmButtonSize;
11
+ /** Soft follows the themed radius; round is a pill. */
12
+ shape?: NmControlShape;
10
13
  block?: boolean;
11
14
  type?: 'button' | 'submit';
12
15
  disabled?: boolean;
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode, type Ref, type TextareaHTMLAttributes } from 'react';
2
2
  import { type NmComposerAttachment, type NmComposerQueued } from '../../config/nm-composer.ts';
3
+ import type { NmControlShape } from '../../config/nm-shape.ts';
3
4
  import type { NmIconId } from '../../config/nm-icons.ts';
4
5
  import type { OrbAdapter } from 'orb-ui';
5
6
  import type { NmVoiceOrbAgentState } from '../../config/nm-voice-orb.ts';
@@ -72,5 +73,7 @@ export type NmComposerProps = {
72
73
  formless?: boolean;
73
74
  /** The host owns the text: the field is not controlled by `value`, and `value` only says whether there is any. */
74
75
  uncontrolled?: boolean;
76
+ /** Shape of every button in the bar, own and hosted. Round makes them circles. */
77
+ buttonShape?: NmControlShape;
75
78
  };
76
79
  export declare function NmComposer(props: NmComposerProps): ReactNode;
@@ -30,6 +30,8 @@ type SidebarContextValue = {
30
30
  setCollapsible: (mode: NmSidebarCollapsible) => void;
31
31
  };
32
32
  /** The sidebar's state: expanded or collapsed, peeking, on a desktop or a phone, and the function that toggles it. */
33
+ /** The sidebar context when there is one. For a part that lives in a bar shared by pages with and without a sidebar. */
34
+ export declare function useOptionalSidebar(): SidebarContextValue | null;
33
35
  export declare function useSidebar(): SidebarContextValue;
34
36
  export type NmSidebarProviderProps = ComponentProps<'div'> & {
35
37
  defaultOpen?: boolean;
@@ -73,7 +75,9 @@ export declare function NmSidebarRail({ className, label, ...props }: ComponentP
73
75
  label?: string;
74
76
  }): ReactNode;
75
77
  /** The main area beside the sidebar. With the inset variant it is the raised card. */
76
- export declare function NmSidebarInset({ className, ...props }: ComponentProps<'main'>): ReactNode;
78
+ export declare function NmSidebarInset({ className, render, ref, ...props }: ComponentProps<'main'> & {
79
+ render?: RenderProp;
80
+ }): ReactNode;
77
81
  /** A search field in the sidebar. The NIF Input. */
78
82
  export declare function NmSidebarInput({ className, ...props }: ComponentProps<typeof NmInput>): ReactNode;
79
83
  /** Sticky at the top: a brand, a title or a workspace switcher, and the toggle when the sidebar collapses to it. */
@@ -122,10 +126,12 @@ export declare function NmSidebarMenuSkeleton({ className, showIcon, ...props }:
122
126
  /** Rows under a row, indented by a line. */
123
127
  export declare function NmSidebarMenuSub({ className, ...props }: ComponentProps<'ul'>): ReactNode;
124
128
  export declare function NmSidebarMenuSubItem({ className, ...props }: ComponentProps<'li'>): ReactNode;
125
- export declare function NmSidebarMenuSubButton({ render, size, isActive, className, ref, ...props }: ComponentProps<'a'> & {
129
+ export declare function NmSidebarMenuSubButton({ render, size, isActive, disabled, className, ref, ...props }: ComponentProps<'a'> & {
126
130
  render?: RenderProp;
127
131
  size?: Exclude<NmSidebarButtonSize, 'lg'>;
128
132
  isActive?: boolean;
133
+ /** Not available. A link gets aria-disabled; a rendered button gets disabled. */
134
+ disabled?: boolean;
129
135
  }): ReactNode;
130
136
  /** A chevron at the end of a row. In a collapsible row it points right and turns down when the row is open. With `menu` it points down, for a row that opens a menu. */
131
137
  export declare function NmSidebarChevron({ className, menu, ...props }: Omit<ComponentProps<typeof NmIcon>, 'id'> & {
@@ -1,10 +1,13 @@
1
1
  import { type ButtonHTMLAttributes, type MouseEvent, type ReactNode } from 'react';
2
2
  import type { NmIconId } from '../../config/nm-icons.ts';
3
3
  import { type NmSmallIconButtonTone } from '../../config/nm-small-icon-button.ts';
4
+ import type { NmControlShape } from '../../config/nm-shape.ts';
4
5
  export type NmSmallIconButtonProps = {
5
6
  label: string;
6
7
  icon: NmIconId;
7
8
  tone?: NmSmallIconButtonTone;
9
+ /** Soft follows the themed radius; round is a full circle. */
10
+ shape?: NmControlShape;
8
11
  active?: boolean;
9
12
  iconContent?: ReactNode;
10
13
  testId?: string;
@@ -14,6 +17,8 @@ export declare const NmSmallIconButton: import("react").ForwardRefExoticComponen
14
17
  label: string;
15
18
  icon: NmIconId;
16
19
  tone?: NmSmallIconButtonTone;
20
+ /** Soft follows the themed radius; round is a full circle. */
21
+ shape?: NmControlShape;
17
22
  active?: boolean;
18
23
  iconContent?: ReactNode;
19
24
  testId?: string;
@@ -1,5 +1,6 @@
1
1
  import { type ButtonHTMLAttributes, type ReactNode } from 'react';
2
2
  import type { NmIconId } from '../../config/nm-icons.ts';
3
+ import { type NmControlShape } from '../../config/nm-shape.ts';
3
4
  import { NM_ICON_BUTTON_TONES, type NmIconButtonTone } from '../../config/nm-icon-button.ts';
4
5
  import { type NordmanChromeIconId } from '../../config/nordman-chrome.ts';
5
6
  export declare const NORDMAN_ICON_BUTTON_VARIANTS: {
@@ -11,6 +12,8 @@ export declare const NordmanIconButton: import("react").ForwardRefExoticComponen
11
12
  label: string;
12
13
  href?: string;
13
14
  tone?: NmIconButtonTone;
15
+ /** Soft follows the themed radius; round is a full circle. */
16
+ shape?: NmControlShape;
14
17
  variant?: NordmanIconButtonVariant;
15
18
  testId?: string;
16
19
  imageSrc?: string;
@@ -32,6 +32,7 @@ export declare const NM_BUTTON_CLASSES: {
32
32
  readonly large: 'nm-button--large';
33
33
  readonly block: 'nm-button--block';
34
34
  readonly icon: 'nm-button--icon';
35
+ readonly round: 'nm-button--round';
35
36
  };
36
37
  /** The button's class names, for triggers that must stay a Base UI part, such as a menu or popover trigger. */
37
38
  export declare function nmButtonClassName(tone: NmButtonTone, size?: NmButtonSize, iconOnly?: boolean): string;
@@ -1,7 +1,3 @@
1
- /**
2
- * Composer: the message box. Built from the Nordman AI design:
3
- * one framed box, the text on top, actions in a row beneath. Add on the left, dictate and voice on the right.
4
- */
5
1
  export declare const NM_COMPOSER_COPY: {
6
2
  readonly placeholder: 'Ask Nordman';
7
3
  readonly attach: 'Add';
@@ -42,6 +38,8 @@ export declare const NM_COMPOSER_CLASSES: {
42
38
  readonly primary: 'nm-composer__button--primary';
43
39
  readonly stopMark: 'nm-composer__stop-mark';
44
40
  };
41
+ /** Set on the root with a shape value; every button in the bar, own or hosted, takes that shape. */
42
+ export declare const NM_COMPOSER_SHAPE_ATTRIBUTE = "data-button-shape";
45
43
  export type NmComposerAttachment = {
46
44
  id: string;
47
45
  name: string;
@@ -1,3 +1,4 @@
1
+ import { type NmControlShape } from './nm-shape.ts';
1
2
  export declare const NM_ICON_BUTTON_TONES: {
2
3
  readonly line: 'line';
3
4
  readonly raised: 'raised';
@@ -17,8 +18,9 @@ export declare const NM_ICON_BUTTON_CLASSES: {
17
18
  readonly solid: 'nm-icon-button--solid';
18
19
  readonly panel: 'nm-icon-button--panel';
19
20
  readonly invert: 'nm-icon-button--invert';
21
+ readonly round: 'nm-icon-button--round';
20
22
  };
21
23
  /** Shared geometry hook for send / stop / during-run primary composer actions. */
22
24
  export declare const NM_CHATBOX_ACTION_CLASS = "nm-chatbox__send";
23
- export declare function nmIconButtonClassName(tone?: NmIconButtonTone): string;
24
- export declare function nmChatboxActionButtonClassName(): string;
25
+ export declare function nmIconButtonClassName(tone?: NmIconButtonTone, shape?: NmControlShape): string;
26
+ export declare function nmChatboxActionButtonClassName(shape?: NmControlShape): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Control shape. Soft is the kit default: corners follow the themed radius scale.
3
+ * Round is a full circle (or pill): it ignores the radius scale, a circle has no corner to scale.
4
+ */
5
+ export declare const NM_CONTROL_SHAPES: {
6
+ readonly soft: 'soft';
7
+ readonly round: 'round';
8
+ };
9
+ export type NmControlShape = (typeof NM_CONTROL_SHAPES)[keyof typeof NM_CONTROL_SHAPES];
10
+ /** The data attribute a container sets to round every control inside it. */
11
+ export declare const NM_SHAPE_ATTRIBUTE = "data-button-shape";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nordmanai/nif",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Nordman Interface Framework: the one UI and UX system for every Nordman product. Base UI, in the shadcn composition, styled by NIF tokens.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.11",
2
+ "version": "0.2.13",
3
3
  "versions": {
4
4
  "@animate-ui/react": "^2.4.2",
5
5
  "@base-ui/react": "^1.9.0",
@@ -268,6 +268,7 @@
268
268
  "config/nm-icons-extended.ts",
269
269
  "config/nm-icons.ts",
270
270
  "config/nm-message.ts",
271
+ "config/nm-shape.ts",
271
272
  "config/nordman-chrome.ts",
272
273
  "kit-logger.ts",
273
274
  "lib/logger.ts",
@@ -494,6 +495,7 @@
494
495
  "config/nm-icon-button.ts",
495
496
  "config/nm-icons-extended.ts",
496
497
  "config/nm-icons.ts",
498
+ "config/nm-shape.ts",
497
499
  "config/nordman-chrome.ts",
498
500
  "kit-logger.ts",
499
501
  "lib/bar-overflow.ts",
@@ -810,6 +812,7 @@
810
812
  "config/nm-button.ts",
811
813
  "config/nm-icons-extended.ts",
812
814
  "config/nm-icons.ts",
815
+ "config/nm-shape.ts",
813
816
  "kit-logger.ts",
814
817
  "lib/logger.ts",
815
818
  "lib/nm-motion.ts"
@@ -1137,6 +1140,7 @@
1137
1140
  "config/nm-icon-button.ts",
1138
1141
  "config/nm-icons-extended.ts",
1139
1142
  "config/nm-icons.ts",
1143
+ "config/nm-shape.ts",
1140
1144
  "kit-logger.ts",
1141
1145
  "lib/logger.ts"
1142
1146
  ],
@@ -1590,6 +1594,7 @@
1590
1594
  "config/nm-icon-button.ts",
1591
1595
  "config/nm-icons-extended.ts",
1592
1596
  "config/nm-icons.ts",
1597
+ "config/nm-shape.ts",
1593
1598
  "config/nordman-chrome.ts",
1594
1599
  "kit-logger.ts",
1595
1600
  "lib/logger.ts",
@@ -1803,6 +1808,7 @@
1803
1808
  "config/nm-icon-button.ts",
1804
1809
  "config/nm-icons-extended.ts",
1805
1810
  "config/nm-icons.ts",
1811
+ "config/nm-shape.ts",
1806
1812
  "config/nm-side-panel.ts",
1807
1813
  "config/nm-small-icon-button.ts",
1808
1814
  "config/nordman-chrome.ts",
@@ -2311,6 +2317,7 @@
2311
2317
  "config/nm-composer.ts",
2312
2318
  "config/nm-icons-extended.ts",
2313
2319
  "config/nm-icons.ts",
2320
+ "config/nm-shape.ts",
2314
2321
  "config/nm-site-palette.ts",
2315
2322
  "config/nm-voice-orb.ts",
2316
2323
  "kit-logger.ts",
@@ -2677,6 +2684,7 @@
2677
2684
  "config/nm-icon-button.ts",
2678
2685
  "config/nm-icons-extended.ts",
2679
2686
  "config/nm-icons.ts",
2687
+ "config/nm-shape.ts",
2680
2688
  "config/nordman-chrome.ts",
2681
2689
  "kit-logger.ts",
2682
2690
  "lib/logger.ts",
@@ -3020,6 +3028,7 @@
3020
3028
  "config/nm-icon-button.ts",
3021
3029
  "config/nm-icons-extended.ts",
3022
3030
  "config/nm-icons.ts",
3031
+ "config/nm-shape.ts",
3023
3032
  "config/nm-side-panel.ts",
3024
3033
  "config/nordman-chrome.ts",
3025
3034
  "kit-logger.ts",
@@ -3371,6 +3380,7 @@
3371
3380
  "config/nm-icon-button.ts",
3372
3381
  "config/nm-icons-extended.ts",
3373
3382
  "config/nm-icons.ts",
3383
+ "config/nm-shape.ts",
3374
3384
  "config/nordman-chrome.ts",
3375
3385
  "kit-logger.ts",
3376
3386
  "lib/logger.ts",
@@ -3579,6 +3589,7 @@
3579
3589
  "config/nm-icons-extended.ts",
3580
3590
  "config/nm-icons.ts",
3581
3591
  "config/nm-list-item.ts",
3592
+ "config/nm-shape.ts",
3582
3593
  "config/nordman-chrome.ts",
3583
3594
  "kit-logger.ts",
3584
3595
  "lib/logger.ts",
@@ -3770,6 +3781,7 @@
3770
3781
  "config/nm-icons-extended.ts",
3771
3782
  "config/nm-icons.ts",
3772
3783
  "config/nm-modal.ts",
3784
+ "config/nm-shape.ts",
3773
3785
  "config/nordman-chrome.ts",
3774
3786
  "content/nm-modal.ts",
3775
3787
  "kit-logger.ts",
@@ -4086,6 +4098,7 @@
4086
4098
  "config/nm-icons.ts",
4087
4099
  "config/nm-panel-bar.ts",
4088
4100
  "config/nm-segmented.ts",
4101
+ "config/nm-shape.ts",
4089
4102
  "kit-logger.ts",
4090
4103
  "lib/logger.ts"
4091
4104
  ],
@@ -4313,6 +4326,7 @@
4313
4326
  "config/nm-icons-extended.ts",
4314
4327
  "config/nm-icons.ts",
4315
4328
  "config/nm-questionnaire.ts",
4329
+ "config/nm-shape.ts",
4316
4330
  "config/nm-side-panel.ts",
4317
4331
  "config/nordman-chrome.ts",
4318
4332
  "kit-logger.ts",
@@ -4657,6 +4671,7 @@
4657
4671
  "config/nm-icons-extended.ts",
4658
4672
  "config/nm-icons.ts",
4659
4673
  "config/nm-segmented.ts",
4674
+ "config/nm-shape.ts",
4660
4675
  "kit-logger.ts",
4661
4676
  "lib/logger.ts"
4662
4677
  ],
@@ -4972,6 +4987,7 @@
4972
4987
  "config/nm-icon-button.ts",
4973
4988
  "config/nm-icons-extended.ts",
4974
4989
  "config/nm-icons.ts",
4990
+ "config/nm-shape.ts",
4975
4991
  "config/nm-side-panel.ts",
4976
4992
  "kit-logger.ts",
4977
4993
  "lib/logger.ts",
@@ -5144,6 +5160,7 @@
5144
5160
  "config/nm-icon-button.ts",
5145
5161
  "config/nm-icons-extended.ts",
5146
5162
  "config/nm-icons.ts",
5163
+ "config/nm-shape.ts",
5147
5164
  "config/nm-sidebar.ts",
5148
5165
  "config/nm-small-icon-button.ts",
5149
5166
  "config/nordman-chrome.ts",
@@ -5332,6 +5349,7 @@
5332
5349
  "config/nm-icon-button.ts",
5333
5350
  "config/nm-icons-extended.ts",
5334
5351
  "config/nm-icons.ts",
5352
+ "config/nm-shape.ts",
5335
5353
  "config/nm-small-icon-button.ts",
5336
5354
  "config/nordman-chrome.ts",
5337
5355
  "kit-logger.ts",
@@ -5693,6 +5711,7 @@
5693
5711
  "config/nm-icon-button.ts",
5694
5712
  "config/nm-icons-extended.ts",
5695
5713
  "config/nm-icons.ts",
5714
+ "config/nm-shape.ts",
5696
5715
  "config/nm-side-panel.ts",
5697
5716
  "kit-logger.ts",
5698
5717
  "lib/logger.ts",
@@ -5864,6 +5883,7 @@
5864
5883
  "config/nm-icon-button.ts",
5865
5884
  "config/nm-icons-extended.ts",
5866
5885
  "config/nm-icons.ts",
5886
+ "config/nm-shape.ts",
5867
5887
  "kit-logger.ts",
5868
5888
  "lib/logger.ts",
5869
5889
  "lib/nm-class.ts",
@@ -6041,6 +6061,7 @@
6041
6061
  "config/nm-icon-button.ts",
6042
6062
  "config/nm-icons-extended.ts",
6043
6063
  "config/nm-icons.ts",
6064
+ "config/nm-shape.ts",
6044
6065
  "config/nordman-chrome.ts",
6045
6066
  "kit-logger.ts",
6046
6067
  "lib/logger.ts",
@@ -6202,6 +6223,7 @@
6202
6223
  "config/nm-button.ts",
6203
6224
  "config/nm-icons-extended.ts",
6204
6225
  "config/nm-icons.ts",
6226
+ "config/nm-shape.ts",
6205
6227
  "config/nordman-login.ts",
6206
6228
  "content/nordman-login.ts",
6207
6229
  "kit-logger.ts",
@@ -6383,6 +6405,7 @@
6383
6405
  "config/nm-button.ts",
6384
6406
  "config/nm-icons-extended.ts",
6385
6407
  "config/nm-icons.ts",
6408
+ "config/nm-shape.ts",
6386
6409
  "config/nordman-login.ts",
6387
6410
  "content/nordman-login.ts",
6388
6411
  "kit-logger.ts",
@@ -6540,6 +6563,7 @@
6540
6563
  "config/nm-button.ts",
6541
6564
  "config/nm-icons-extended.ts",
6542
6565
  "config/nm-icons.ts",
6566
+ "config/nm-shape.ts",
6543
6567
  "content/nordman-login.ts",
6544
6568
  "kit-logger.ts",
6545
6569
  "lib/logger.ts",
@@ -6704,6 +6728,7 @@
6704
6728
  "config/nm-button.ts",
6705
6729
  "config/nm-icons-extended.ts",
6706
6730
  "config/nm-icons.ts",
6731
+ "config/nm-shape.ts",
6707
6732
  "config/nordman-login.ts",
6708
6733
  "content/nordman-login.ts",
6709
6734
  "kit-logger.ts",
@@ -6863,6 +6888,7 @@
6863
6888
  "config/nm-button.ts",
6864
6889
  "config/nm-icons-extended.ts",
6865
6890
  "config/nm-icons.ts",
6891
+ "config/nm-shape.ts",
6866
6892
  "config/nordman-login.ts",
6867
6893
  "content/nordman-login.ts",
6868
6894
  "kit-logger.ts",
@@ -7035,6 +7061,7 @@
7035
7061
  "config/nm-button.ts",
7036
7062
  "config/nm-icons-extended.ts",
7037
7063
  "config/nm-icons.ts",
7064
+ "config/nm-shape.ts",
7038
7065
  "config/nordman-login.ts",
7039
7066
  "content/nordman-login.ts",
7040
7067
  "kit-logger.ts",
@@ -30,6 +30,9 @@
30
30
  height: var(--control-h-lg);
31
31
  }
32
32
 
33
+ .nm-button--round {
34
+ border-radius: var(--nm-radius-round);
35
+ }
33
36
  .nm-button--block {
34
37
  width: 100%;
35
38
  }
@@ -7,6 +7,7 @@ import {
7
7
  type NmButtonSize,
8
8
  type NmButtonTone,
9
9
  } from '../../config/nm-button.ts';
10
+ import { NM_CONTROL_SHAPES, type NmControlShape } from '../../config/nm-shape.ts';
10
11
  import { createLogger } from '../../kit-logger.ts';
11
12
  import { AnimateIcon, Button as MotionButton } from '../../lib/nm-motion.ts';
12
13
  import { NmIcon } from '../nm-icon/nm-icon.tsx';
@@ -19,6 +20,8 @@ export type NmButtonProps = {
19
20
  children?: ReactNode;
20
21
  tone: NmButtonTone;
21
22
  size?: NmButtonSize;
23
+ /** Soft follows the themed radius; round is a pill. */
24
+ shape?: NmControlShape;
22
25
  block?: boolean;
23
26
  type?: 'button' | 'submit';
24
27
  disabled?: boolean;
@@ -34,12 +37,13 @@ export type NmButtonProps = {
34
37
  type NmButtonNativeProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof NmButtonProps | 'children' | 'aria-pressed'>;
35
38
 
36
39
  export const NmButton = forwardRef<HTMLButtonElement, NmButtonProps & NmButtonNativeProps>(function NmButton(props, ref): ReactNode {
37
- const { label, children, tone, size: sizeProp, block, type, disabled, pressed, icon, iconOnly, testId, onClick, className: extra, ...rest } = props;
40
+ const { label, children, tone, size: sizeProp, shape, block, type, disabled, pressed, icon, iconOnly, testId, onClick, className: extra, ...rest } = props;
38
41
  const size = sizeProp ?? NM_BUTTON_SIZES.default;
39
42
  const className = [
40
43
  NM_BUTTON_CLASSES.root,
41
44
  NM_BUTTON_CLASSES[tone],
42
45
  NM_BUTTON_CLASSES[size],
46
+ shape === NM_CONTROL_SHAPES.round ? NM_BUTTON_CLASSES.round : null,
43
47
  block ? NM_BUTTON_CLASSES.block : null,
44
48
  iconOnly ? NM_BUTTON_CLASSES.icon : null,
45
49
  extra,
@@ -185,18 +185,25 @@
185
185
  outline: none;
186
186
  }
187
187
 
188
+ /* Send, Stop, Done and the voice button are the primary action: they take the theme accent. */
188
189
  .nm-composer__button--primary {
189
- background: var(--nm-fg);
190
+ background: var(--nm-accent);
190
191
  color: var(--nm-canvas);
191
192
  }
192
193
 
193
194
  .nm-composer__button--primary:hover:not(:disabled),
194
195
  .nm-composer__button--primary:focus-visible {
195
- background: var(--nm-fg);
196
+ background: var(--nm-accent);
196
197
  color: var(--nm-canvas);
197
198
  opacity: 0.88;
198
199
  }
199
200
 
201
+ /* Round shape: every button in the bar, the composer's own and any hosted icon button, is a circle. */
202
+ .nm-composer[data-button-shape='round'] .nm-composer__button,
203
+ .nm-composer[data-button-shape='round'] .nm-icon-button {
204
+ border-radius: var(--nm-radius-round);
205
+ }
206
+
200
207
  .nm-composer__button:disabled {
201
208
  cursor: default;
202
209
  opacity: 0.4;
@@ -6,7 +6,9 @@ import {
6
6
  NM_COMPOSER_INLINE_MARK as MARK,
7
7
  type NmComposerAttachment,
8
8
  type NmComposerQueued,
9
+ NM_COMPOSER_SHAPE_ATTRIBUTE,
9
10
  } from '../../config/nm-composer.ts';
11
+ import type { NmControlShape } from '../../config/nm-shape.ts';
10
12
  import { NmIcon } from '../nm-icon/nm-icon.tsx';
11
13
  import type { NmIconId } from '../../config/nm-icons.ts';
12
14
  import { NmVoiceOrb } from '../nm-voice-orb/nm-voice-orb.tsx';
@@ -78,6 +80,8 @@ export type NmComposerProps = {
78
80
  formless?: boolean;
79
81
  /** The host owns the text: the field is not controlled by `value`, and `value` only says whether there is any. */
80
82
  uncontrolled?: boolean;
83
+ /** Shape of every button in the bar, own and hosted. Round makes them circles. */
84
+ buttonShape?: NmControlShape;
81
85
  };
82
86
 
83
87
  type InlinePart = { kind: 'text'; value: string } | { kind: 'chip'; label: string; occurrence: number };
@@ -337,7 +341,7 @@ export function NmComposer(props: NmComposerProps): ReactNode {
337
341
  );
338
342
  } else {
339
343
  action = (
340
- <button type="button" className={C.button} aria-label={COPY.voice} data-testid="voice-button" disabled={props.disabled} onClick={props.onVoice}>
344
+ <button type="button" className={`${C.button} ${C.primary}`} aria-label={COPY.voice} data-testid="voice-button" disabled={props.disabled} onClick={props.onVoice}>
341
345
  <NmIcon id="audio-lines" />
342
346
  </button>
343
347
  );
@@ -348,6 +352,7 @@ export function NmComposer(props: NmComposerProps): ReactNode {
348
352
  <Root
349
353
  ref={rootRef as never}
350
354
  className={C.root}
355
+ {...{ [NM_COMPOSER_SHAPE_ATTRIBUTE]: props.buttonShape }}
351
356
  data-disabled={props.disabled ? 'true' : undefined}
352
357
  data-testid={props.testId}
353
358
  {...(props.formless
@@ -80,7 +80,8 @@
80
80
  The column keeps its collapsed width in the flow, and the inner panel floats out over the page. */
81
81
  .nm-sidebar[data-peek] {
82
82
  overflow: visible;
83
- z-index: 1;
83
+ /* Over the main area and its top bar, under menus. */
84
+ z-index: var(--nm-rail-z);
84
85
  }
85
86
  .nm-sidebar[data-mode='offcanvas'][data-peek] {
86
87
  width: 0;
@@ -63,6 +63,10 @@ const SidebarContext = createContext<SidebarContextValue | null>(null);
63
63
  const PEEK_KEEPS = '[data-slot="sidebar"], [data-slot="sidebar-trigger"], [data-slot="tooltip-content"], [role="menu"], [role="dialog"]';
64
64
 
65
65
  /** The sidebar's state: expanded or collapsed, peeking, on a desktop or a phone, and the function that toggles it. */
66
+ /** The sidebar context when there is one. For a part that lives in a bar shared by pages with and without a sidebar. */
67
+ export function useOptionalSidebar(): SidebarContextValue | null {
68
+ return useContext(SidebarContext);
69
+ }
66
70
  export function useSidebar(): SidebarContextValue {
67
71
  const context = useContext(SidebarContext);
68
72
  if (!context) {
@@ -345,8 +349,13 @@ export function NmSidebarRail({ className, label = 'Toggle sidebar', ...props }:
345
349
  }
346
350
 
347
351
  /** The main area beside the sidebar. With the inset variant it is the raised card. */
348
- export function NmSidebarInset({ className, ...props }: ComponentProps<'main'>): ReactNode {
349
- return <main data-slot="sidebar-inset" className={nmClass('nm-sidebar__inset', className)} {...props} />;
352
+ export function NmSidebarInset({ className, render, ref, ...props }: ComponentProps<'main'> & { render?: RenderProp }): ReactNode {
353
+ return useRender({
354
+ render,
355
+ ref,
356
+ defaultTagName: 'main',
357
+ props: { 'data-slot': 'sidebar-inset', className: nmClass('nm-sidebar__inset', className), ...props },
358
+ });
350
359
  }
351
360
 
352
361
  /** A search field in the sidebar. The NIF Input. */
@@ -507,10 +516,17 @@ export function NmSidebarMenuSubButton({
507
516
  render,
508
517
  size = 'default',
509
518
  isActive = false,
519
+ disabled = false,
510
520
  className,
511
521
  ref,
512
522
  ...props
513
- }: ComponentProps<'a'> & { render?: RenderProp; size?: Exclude<NmSidebarButtonSize, 'lg'>; isActive?: boolean }): ReactNode {
523
+ }: ComponentProps<'a'> & {
524
+ render?: RenderProp;
525
+ size?: Exclude<NmSidebarButtonSize, 'lg'>;
526
+ isActive?: boolean;
527
+ /** Not available. A link gets aria-disabled; a rendered button gets disabled. */
528
+ disabled?: boolean;
529
+ }): ReactNode {
514
530
  return useRender({
515
531
  render,
516
532
  ref,
@@ -519,6 +535,9 @@ export function NmSidebarMenuSubButton({
519
535
  'data-slot': 'sidebar-menu-sub-button',
520
536
  'data-size': size,
521
537
  'data-active': isActive ? '' : undefined,
538
+ 'data-disabled': disabled ? '' : undefined,
539
+ 'aria-disabled': disabled ? true : undefined,
540
+ disabled: disabled ? true : undefined,
522
541
  'aria-current': isActive ? 'page' : undefined,
523
542
  className: nmClass('nm-sidebar__menu-sub-button', className),
524
543
  ...props,
@@ -6,6 +6,7 @@ import {
6
6
  } from '../../config/nm-small-icon-button.ts';
7
7
  import { NM_ICON_BUTTON_TONES, type NmIconButtonTone } from '../../config/nm-icon-button.ts';
8
8
  import { NordmanIconButton } from '../nordman-icon-button/nordman-icon-button.tsx';
9
+ import type { NmControlShape } from '../../config/nm-shape.ts';
9
10
  import { createLogger } from '../../kit-logger.ts';
10
11
 
11
12
  const logger = createLogger('interface-nm-small-icon-button');
@@ -19,6 +20,8 @@ export type NmSmallIconButtonProps = {
19
20
  label: string;
20
21
  icon: NmIconId;
21
22
  tone?: NmSmallIconButtonTone;
23
+ /** Soft follows the themed radius; round is a full circle. */
24
+ shape?: NmControlShape;
22
25
  active?: boolean;
23
26
  iconContent?: ReactNode;
24
27
  testId?: string;
@@ -31,6 +34,7 @@ export const NmSmallIconButton = forwardRef<HTMLButtonElement, NmSmallIconButton
31
34
  label,
32
35
  icon,
33
36
  tone = NM_SMALL_ICON_BUTTON_TONES.solid,
37
+ shape,
34
38
  active = false,
35
39
  iconContent,
36
40
  testId,
@@ -47,6 +51,7 @@ export const NmSmallIconButton = forwardRef<HTMLButtonElement, NmSmallIconButton
47
51
  glyph={icon}
48
52
  label={label}
49
53
  tone={SMALL_ICON_BUTTON_TONE[tone]}
54
+ shape={shape}
50
55
  active={active}
51
56
  disabled={disabled}
52
57
  testId={testId}
@@ -93,6 +93,9 @@
93
93
  cursor: pointer;
94
94
  }
95
95
 
96
+ .nm-icon-button--round {
97
+ border-radius: var(--nm-radius-round);
98
+ }
96
99
  .nm-icon-button--invert.nm-chatbox__send:not(:disabled),
97
100
  .nm-icon-button--invert.nm-chatbox__send:not(:disabled):hover,
98
101
  .nm-icon-button--invert.nm-chatbox__send:not(:disabled):focus-visible {
@@ -1,5 +1,6 @@
1
1
  import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';
2
2
  import type { NmIconId } from '../../config/nm-icons.ts';
3
+ import { NM_CONTROL_SHAPES, type NmControlShape } from '../../config/nm-shape.ts';
3
4
  import {
4
5
  NM_ICON_BUTTON_CLASSES,
5
6
  NM_ICON_BUTTON_TONES,
@@ -24,6 +25,8 @@ type NordmanIconButtonProps = {
24
25
  label: string;
25
26
  href?: string;
26
27
  tone?: NmIconButtonTone;
28
+ /** Soft follows the themed radius; round is a full circle. */
29
+ shape?: NmControlShape;
27
30
  variant?: NordmanIconButtonVariant;
28
31
  testId?: string;
29
32
  imageSrc?: string;
@@ -55,12 +58,14 @@ function iconButtonTone(
55
58
 
56
59
  function iconButtonClass(
57
60
  tone: NmIconButtonTone,
61
+ shape: NmControlShape | undefined,
58
62
  active: boolean,
59
63
  className: string | undefined,
60
64
  ): string {
61
65
  return [
62
66
  NM_ICON_BUTTON_CLASSES.root,
63
67
  tone === NM_ICON_BUTTON_TONES.line ? '' : NM_ICON_BUTTON_CLASSES[tone],
68
+ shape === NM_CONTROL_SHAPES.round ? NM_ICON_BUTTON_CLASSES.round : '',
64
69
  active ? NM_ICON_BUTTON_CLASSES.active : '',
65
70
  className ?? '',
66
71
  ]
@@ -127,6 +132,7 @@ export const NordmanIconButton = forwardRef<HTMLButtonElement, NordmanIconButton
127
132
  label,
128
133
  href,
129
134
  tone,
135
+ shape,
130
136
  variant,
131
137
  active = false,
132
138
  pressed,
@@ -142,7 +148,7 @@ export const NordmanIconButton = forwardRef<HTMLButtonElement, NordmanIconButton
142
148
  ...rest
143
149
  } = props;
144
150
  const resolvedTone = iconButtonTone(tone, variant);
145
- const buttonClass = iconButtonClass(resolvedTone, active, className);
151
+ const buttonClass = iconButtonClass(resolvedTone, shape, active, className);
146
152
  const marked = pressed ?? checked ?? (active ? true : undefined);
147
153
  logger.debug('render', glyph ?? icon ?? label, { href: Boolean(href), tone: resolvedTone });
148
154
  const face = (
@@ -36,6 +36,7 @@ export const NM_BUTTON_CLASSES = {
36
36
  large: 'nm-button--large',
37
37
  block: 'nm-button--block',
38
38
  icon: 'nm-button--icon',
39
+ round: 'nm-button--round',
39
40
  } as const;
40
41
 
41
42
  /** The button's class names, for triggers that must stay a Base UI part, such as a menu or popover trigger. */